summaryrefslogtreecommitdiff
path: root/qa/qa/git/repository.rb
blob: 27a88534258a531320be63e99fae533c15df073c (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
168
169
170
171
172
# frozen_string_literal: true

require 'cgi'
require 'uri'
require 'open3'
require 'fileutils'
require 'tmpdir'

module QA
  module Git
    class Repository
      include Scenario::Actable

      attr_writer :password
      attr_accessor :env_vars

      def initialize
        # We set HOME to the current working directory (which is a
        # temporary directory created in .perform()) so the temporarily dropped
        # .netrc can be utilised
        self.env_vars = [%Q{HOME="#{File.dirname(netrc_file_path)}"}]
      end

      def self.perform(*args)
        Dir.mktmpdir do |dir|
          Dir.chdir(dir) { super }
        end
      end

      def uri=(address)
        @uri = URI(address)
      end

      def username=(username)
        @username = username
        @uri.user = username
      end

      def use_default_credentials
        self.username, self.password = default_credentials

        add_credentials_to_netrc unless ssh_key_set?
      end

      def clone(opts = '')
        run("git clone #{opts} #{uri} ./")
      end

      def checkout(branch_name)
        run(%Q{git checkout "#{branch_name}"})
      end

      def checkout_new_branch(branch_name)
        run(%Q{git checkout -b "#{branch_name}"})
      end

      def shallow_clone
        clone('--depth 1')
      end

      def configure_identity(name, email)
        run(%Q{git config user.name #{name}})
        run(%Q{git config user.email #{email}})

        add_credentials_to_netrc
      end

      def commit_file(name, contents, message)
        add_file(name, contents)
        commit(message)
      end

      def add_file(name, contents)
        ::File.write(name, contents)

        run(%Q{git add #{name}})
      end

      def commit(message)
        run(%Q{git commit -m "#{message}"})
      end

      def push_changes(branch = 'master')
        run("git push #{uri} #{branch}")
      end

      def commits
        run('git log --oneline').split("\n")
      end

      def use_ssh_key(key)
        @private_key_file = Tempfile.new("id_#{SecureRandom.hex(8)}")
        File.binwrite(private_key_file, key.private_key)
        File.chmod(0700, private_key_file)

        @known_hosts_file = Tempfile.new("known_hosts_#{SecureRandom.hex(8)}")
        keyscan_params = ['-H']
        keyscan_params << "-p #{uri.port}" if uri.port
        keyscan_params << uri.host
        run("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}")

        self.env_vars << %Q{GIT_SSH_COMMAND="ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path}"}
      end

      def delete_ssh_key
        return unless ssh_key_set?

        private_key_file.close(true)
        known_hosts_file.close(true)
      end

      private

      attr_reader :uri, :username, :password, :known_hosts_file, :private_key_file

      def ssh_key_set?
        !private_key_file.nil?
      end

      def run(command_str)
        command = [env_vars, command_str, '2>&1'].compact.join(' ')
        Runtime::Logger.debug "Git: command=[#{command}]"

        output, _ = Open3.capture2(command)
        output = output.chomp.gsub(/\s+$/, '')
        Runtime::Logger.debug "Git: output=[#{output}]"

        output
      end

      def default_credentials
        if ::QA::Runtime::User.ldap_user?
          [Runtime::User.ldap_username, Runtime::User.ldap_password]
        else
          [Runtime::User.username, Runtime::User.password]
        end
      end

      def tmp_netrc_directory
        @tmp_netrc_directory ||= File.join(Dir.tmpdir, "qa-netrc-credentials", $$.to_s)
      end

      def netrc_file_path
        @netrc_file_path ||= File.join(tmp_netrc_directory, '.netrc')
      end

      def netrc_content
        "machine #{uri.host} login #{username} password #{password}"
      end

      def netrc_already_contains_content?
        File.exist?(netrc_file_path) &&
          File.readlines(netrc_file_path).grep(/^#{netrc_content}$/).any?
      end

      def add_credentials_to_netrc
        # Despite libcurl supporting a custom .netrc location through the
        # CURLOPT_NETRC_FILE environment variable, git does not support it :(
        # Info: https://curl.haxx.se/libcurl/c/CURLOPT_NETRC_FILE.html
        #
        # This will create a .netrc in the correct working directory, which is
        # a temporary directory created in .perform()
        #
        return if netrc_already_contains_content?

        FileUtils.mkdir_p(tmp_netrc_directory)
        File.open(netrc_file_path, 'a') { |file| file.puts(netrc_content) }
        File.chmod(0600, netrc_file_path)
      end
    end
  end
end