aboutsummaryrefslogtreecommitdiff
path: root/vendor/supply_drop/lib/supply_drop/rsync.rb
blob: 732a719ced36a2eb5fa71b37e14bfefa88af079f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
module SupplyDrop
  class Rsync
    class << self
      def command(from, to, options={})
        flags = ['-az']
        flags << '--delete' if options[:delete]
        flags << includes(options[:includes]) if options.has_key?(:includes)
        flags << excludes(options[:excludes]) if options.has_key?(:excludes)
        flags << ssh_options(options[:ssh]) if options.has_key?(:ssh)
        flags << options[:flags] if options.has_key?(:flags)

        "rsync #{flags.compact.join(' ')} #{from} #{to}"
      end

      def remote_address(user, host, path)
        user_with_host = [user, host].compact.join('@')
        [user_with_host, path].join(':')
      end

      def excludes(patterns)
        [patterns].flatten.map { |p| "--exclude='#{p}'" }
      end

      def includes(patterns)
        [patterns].flatten.map { |p| "--include='#{p}'" }
      end

      def ssh_options(options)
        mapped_options = options.map do |key, value|
          next unless value

          #
          # Convert Net::SSH options into OpenSSH options.
          #
          # For a list of the options normally support by Net::SSH (and thus Capistrano), see
          # http://net-ssh.github.com/net-ssh/classes/Net/SSH.html#method-c-start
          #
          # Also, to see how Net::SSH does the opposite of the conversion we are doing here, check out:
          # https://github.com/net-ssh/net-ssh/blob/master/lib/net/ssh/config.rb
          #
          # API mismatch:
          #
          # * many OpenSSH options not supported
          # * some options only make sense for Net::SSH
          # * compression: for Net::SSH, this option is supposed to accept true, false, or algorithm. OpenSSH accepts 'yes' or 'no'
          #
          #
          case key
          when :auth_methods            then opt_auth_methods(value)
          when :bind_address            then opt('BindAddress', value)
          when :compression             then opt('Compression', value ? 'yes' : 'no')
          when :compression_level       then opt('CompressionLevel', value.to_i)
          when :config                  then "-F '#{value}'"
          when :encryption              then opt('Ciphers', [value].flatten.join(','))
          when :forward_agent           then opt('ForwardAgent', value)
          when :global_known_hosts_file then opt('GlobalKnownHostsFile', value)
          when :hmac                    then opt('MACs', [value].flatten.join(','))
          when :host_key                then opt('HostKeyAlgorithms', [value].flatten.join(','))
          when :host_key_alias          then opt('HostKeyAlias', value)
          when :host_name               then opt('HostName', value)
          when :kex                     then opt('KexAlgorithms', [value].flatten.join(','))
          when :key_data                then nil # not supported
          when :keys                    then [value].flatten.select { |k| File.exist?(k) }.map { |k| "-i #{k}" }
          when :keys_only               then opt('IdentitiesOnly', value ? 'yes' : 'no')
          when :languages               then nil # not applicable
          when :logger                  then nil # not applicable
          when :paranoid                then opt('StrictHostKeyChecking', value ? 'yes' : 'no')
          when :passphrase              then nil # not supported
          when :password                then nil # not supported
          when :port                    then "-p #{value.to_i}"
          when :properties              then nil # not applicable
          when :proxy                   then nil # not applicable
          when :rekey_blocks_limit      then nil # not supported
          when :rekey_limit             then opt('RekeyLimit', reverse_interpret_size(value))
          when :rekey_packet_limit      then nil # not supported
          when :timeout                 then opt('ConnectTimeout', value.to_i)
          when :user                    then "-l #{value}"
          when :user_known_hosts_file   then opt('UserKnownHostsFile', value)
          when :verbose                 then opt('LogLevel', interpret_log_level(value))
          end
        end.compact

        %[-e "ssh #{mapped_options.join(' ')}"] unless mapped_options.empty?
      end

      private

      def opt(option_name, option_value)
        "-o #{option_name}='#{option_value}'"
      end

      #
      # In OpenSSH, password and pubkey default to 'yes', hostbased defaults to 'no'.
      # Regardless, if :auth_method is configured, then we explicitly set the auth method.
      #
      def opt_auth_methods(value)
        value = [value].flatten
        opts = []
        if value.any?
          if value.include? 'password'
            opts << opt('PasswordAuthentication', 'yes')
          else
            opts << opt('PasswordAuthentication', 'no')
          end
          if value.include? 'publickey'
            opts << opt('PubkeyAuthentication', 'yes')
          else
            opts << opt('PubkeyAuthentication', 'no')
          end
          if value.include? 'hostbased'
            opts << opt('HostbasedAuthentication', 'yes')
          else
            opts << opt('HostbasedAuthentication', 'no')
          end
        end
        if opts.any?
          return opts.join(' ')
        else
          nil
        end
      end

      #
      # Converts the given integer size in bytes into a string with 'K', 'M', 'G' suffix, as appropriate.
      #
      # reverse of interpret_size in https://github.com/net-ssh/net-ssh/blob/master/lib/net/ssh/config.rb
      #
      def reverse_interpret_size(size)
        size = size.to_i
        if size < 1024
          "#{size}"
        elsif size < 1024 * 1024
          "#{size/1024}K"
        elsif size < 1024 * 1024 * 1024
          "#{size/(1024*1024)}M"
        else
          "#{size/(1024*1024*1024)}G"
        end
      end

      def interpret_log_level(level)
        if level.is_a? Symbol
          case level
            when :debug then "DEBUG"
            when :info then "INFO"
            when :warn then "ERROR"
            when :error then "ERROR"
            when :fatal then "FATAL"
            else "INFO"
          end
        elsif level.is_a? Integer
          case level
            when Logger::DEBUG then "DEBUG"
            when Logger::INFO then "INFO"
            when Logger::WARN then "ERROR"
            when Logger::ERROR then "ERROR"
            when Logger::FATAL then "FATAL"
            else "INFO"
          end
        else
          "INFO"
        end
      end

    end
  end
end