packaging 0.88.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +17 -0
  3. data/README-Solaris.md +117 -0
  4. data/README.md +977 -0
  5. data/lib/packaging.rb +32 -0
  6. data/lib/packaging/archive.rb +126 -0
  7. data/lib/packaging/artifactory.rb +651 -0
  8. data/lib/packaging/artifactory/extensions.rb +94 -0
  9. data/lib/packaging/config.rb +492 -0
  10. data/lib/packaging/config/params.rb +387 -0
  11. data/lib/packaging/config/validations.rb +13 -0
  12. data/lib/packaging/deb.rb +28 -0
  13. data/lib/packaging/deb/repo.rb +264 -0
  14. data/lib/packaging/gem.rb +70 -0
  15. data/lib/packaging/metrics.rb +15 -0
  16. data/lib/packaging/nuget.rb +39 -0
  17. data/lib/packaging/paths.rb +376 -0
  18. data/lib/packaging/platforms.rb +507 -0
  19. data/lib/packaging/repo.rb +155 -0
  20. data/lib/packaging/retrieve.rb +75 -0
  21. data/lib/packaging/rpm.rb +5 -0
  22. data/lib/packaging/rpm/repo.rb +254 -0
  23. data/lib/packaging/sign.rb +8 -0
  24. data/lib/packaging/sign/deb.rb +9 -0
  25. data/lib/packaging/sign/dmg.rb +41 -0
  26. data/lib/packaging/sign/ips.rb +57 -0
  27. data/lib/packaging/sign/msi.rb +124 -0
  28. data/lib/packaging/sign/rpm.rb +115 -0
  29. data/lib/packaging/tar.rb +163 -0
  30. data/lib/packaging/util.rb +146 -0
  31. data/lib/packaging/util/date.rb +20 -0
  32. data/lib/packaging/util/execution.rb +85 -0
  33. data/lib/packaging/util/file.rb +125 -0
  34. data/lib/packaging/util/git.rb +174 -0
  35. data/lib/packaging/util/git_tags.rb +73 -0
  36. data/lib/packaging/util/gpg.rb +66 -0
  37. data/lib/packaging/util/jenkins.rb +95 -0
  38. data/lib/packaging/util/misc.rb +69 -0
  39. data/lib/packaging/util/net.rb +410 -0
  40. data/lib/packaging/util/os.rb +17 -0
  41. data/lib/packaging/util/platform.rb +40 -0
  42. data/lib/packaging/util/rake_utils.rb +112 -0
  43. data/lib/packaging/util/serialization.rb +19 -0
  44. data/lib/packaging/util/ship.rb +300 -0
  45. data/lib/packaging/util/tool.rb +41 -0
  46. data/lib/packaging/util/version.rb +334 -0
  47. data/spec/fixtures/config/ext/build_defaults.yaml +2 -0
  48. data/spec/fixtures/config/ext/project_data.yaml +2 -0
  49. data/spec/fixtures/configs/components/test_file.json +1 -0
  50. data/spec/fixtures/configs/components/test_file_2.json +0 -0
  51. data/spec/fixtures/configs/components/test_file_not_tagged.json +1 -0
  52. data/spec/fixtures/configs/components/test_file_wrong_ext.txt +0 -0
  53. data/spec/fixtures/configs/components/test_file_wrong_ext.wrong +0 -0
  54. data/spec/fixtures/util/pre_tasks.yaml +4 -0
  55. data/spec/lib/packaging/artifactory_spec.rb +221 -0
  56. data/spec/lib/packaging/config_spec.rb +576 -0
  57. data/spec/lib/packaging/deb/repo_spec.rb +157 -0
  58. data/spec/lib/packaging/deb_spec.rb +52 -0
  59. data/spec/lib/packaging/gem_spec.rb +86 -0
  60. data/spec/lib/packaging/paths_spec.rb +418 -0
  61. data/spec/lib/packaging/platforms_spec.rb +178 -0
  62. data/spec/lib/packaging/repo_spec.rb +135 -0
  63. data/spec/lib/packaging/retrieve_spec.rb +100 -0
  64. data/spec/lib/packaging/rpm/repo_spec.rb +133 -0
  65. data/spec/lib/packaging/sign_spec.rb +133 -0
  66. data/spec/lib/packaging/tar_spec.rb +116 -0
  67. data/spec/lib/packaging/util/execution_spec.rb +56 -0
  68. data/spec/lib/packaging/util/file_spec.rb +139 -0
  69. data/spec/lib/packaging/util/git_spec.rb +160 -0
  70. data/spec/lib/packaging/util/git_tag_spec.rb +36 -0
  71. data/spec/lib/packaging/util/gpg_spec.rb +64 -0
  72. data/spec/lib/packaging/util/jenkins_spec.rb +112 -0
  73. data/spec/lib/packaging/util/misc_spec.rb +31 -0
  74. data/spec/lib/packaging/util/net_spec.rb +259 -0
  75. data/spec/lib/packaging/util/os_spec.rb +31 -0
  76. data/spec/lib/packaging/util/rake_utils_spec.rb +70 -0
  77. data/spec/lib/packaging/util/ship_spec.rb +199 -0
  78. data/spec/lib/packaging/util/version_spec.rb +123 -0
  79. data/spec/lib/packaging_spec.rb +19 -0
  80. data/spec/spec_helper.rb +22 -0
  81. data/static_artifacts/PackageInfo.plist +3 -0
  82. data/tasks/00_utils.rake +214 -0
  83. data/tasks/30_metrics.rake +33 -0
  84. data/tasks/apple.rake +268 -0
  85. data/tasks/archive.rake +69 -0
  86. data/tasks/build.rake +12 -0
  87. data/tasks/clean.rake +5 -0
  88. data/tasks/config.rake +35 -0
  89. data/tasks/deb.rake +129 -0
  90. data/tasks/deb_repos.rake +28 -0
  91. data/tasks/deprecated.rake +130 -0
  92. data/tasks/doc.rake +20 -0
  93. data/tasks/education.rake +57 -0
  94. data/tasks/fetch.rake +60 -0
  95. data/tasks/gem.rake +159 -0
  96. data/tasks/jenkins.rake +538 -0
  97. data/tasks/jenkins_dynamic.rake +202 -0
  98. data/tasks/load_extras.rake +21 -0
  99. data/tasks/mock.rake +348 -0
  100. data/tasks/nightly_repos.rake +286 -0
  101. data/tasks/pe_deb.rake +12 -0
  102. data/tasks/pe_rpm.rake +13 -0
  103. data/tasks/pe_ship.rake +226 -0
  104. data/tasks/pe_sign.rake +13 -0
  105. data/tasks/pe_tar.rake +5 -0
  106. data/tasks/retrieve.rake +52 -0
  107. data/tasks/rpm.rake +66 -0
  108. data/tasks/rpm_repos.rake +29 -0
  109. data/tasks/ship.rake +692 -0
  110. data/tasks/sign.rake +154 -0
  111. data/tasks/tag.rake +8 -0
  112. data/tasks/tar.rake +28 -0
  113. data/tasks/update.rake +16 -0
  114. data/tasks/vanagon.rake +35 -0
  115. data/tasks/vendor_gems.rake +117 -0
  116. data/tasks/version.rake +33 -0
  117. data/tasks/z_data_dump.rake +65 -0
  118. data/templates/README +1 -0
  119. data/templates/downstream.xml.erb +47 -0
  120. data/templates/msi.xml.erb +197 -0
  121. data/templates/packaging.xml.erb +346 -0
  122. data/templates/repo.xml.erb +117 -0
  123. metadata +287 -0
@@ -0,0 +1,73 @@
1
+ module Pkg::Util
2
+ class Git_tag
3
+ attr_reader :address, :ref, :ref_name, :ref_type, :branch_name
4
+
5
+ GIT = Pkg::Util::Tool::GIT
6
+ DEVNULL = Pkg::Util::OS::DEVNULL
7
+
8
+ # A SHA1 sum is 20 characters long, but Git will match on
9
+ # the first ~8 or so. And 8 is long enough for fun test sums
10
+ # like 'cafebeef' or 'deadfeed`.
11
+ SHA1 = /[0-9A-F]{8,20}/i
12
+
13
+ def initialize(address, reference)
14
+ @address = address
15
+ @ref = reference
16
+ parse_ref!
17
+ end
18
+
19
+ # Parse ref in one of three ways: if the ref is already in a good format just grab
20
+ # the ref type from the string. if it's not, check if it's a sha, if that is true
21
+ # then list it as a sha. finally if it's neither of those fetch the full ref and
22
+ # parse that.
23
+ def parse_ref!
24
+ if ref?
25
+ split_ref(ref)
26
+ elsif sha?
27
+ @ref_type = "sha"
28
+ else
29
+ split_ref(fetch_full_ref)
30
+ end
31
+ end
32
+
33
+ # Split the ref based on slashes, set ref_name and ref_type based on the last two
34
+ # items from the split. i.e. refs/tags/1.1.1 would return:
35
+ # @ref_name => 1.1.1 @ref_type => tags
36
+ def split_ref(ref)
37
+ ref_parts = ref.split('/', 3)
38
+ @ref_name = ref_parts.pop
39
+ @ref_type = ref_parts.pop
40
+ [@ref_type, @ref_name]
41
+ end
42
+
43
+ # Fetch the full ref using ls-remote, this should raise an error if it returns non-zero
44
+ # because that means this ref doesn't exist in the repo
45
+ def fetch_full_ref
46
+ stdout, _, _ = Pkg::Util::Execution.capture3("#{GIT} ls-remote --tags --heads --exit-code #{address} #{ref}")
47
+ stdout.split.last
48
+ rescue RuntimeError => e
49
+ raise "ERROR : Not a ref or sha!\n#{e}"
50
+ end
51
+
52
+ def branch_name
53
+ branch? ? ref_name : nil
54
+ end
55
+
56
+ def ref?
57
+ `#{GIT} check-ref-format #{ref} >#{DEVNULL} 2>&1`
58
+ $?.success?
59
+ end
60
+
61
+ def branch?
62
+ ref_type.downcase == "heads"
63
+ end
64
+
65
+ def tag?
66
+ ref_type.downcase == "tags"
67
+ end
68
+
69
+ def sha?
70
+ !!(ref =~ SHA1)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,66 @@
1
+ module Pkg::Util::Gpg
2
+ class << self
3
+
4
+ # Please note that this method is not used in determining what key is used
5
+ # to sign the debian repos. That is defined in the freight config that
6
+ # lives on our internal repo staging host. The debian conf/distribution
7
+ # files that are generated with this repo use the default gpg key to
8
+ # reflect that.
9
+ def key
10
+ fail "You need to set `gpg_key` in your build defaults." unless Pkg::Config.gpg_key && !Pkg::Config.gpg_key.empty?
11
+ Pkg::Config.gpg_key
12
+ end
13
+
14
+ def keychain
15
+ if @keychain.nil?
16
+ @keychain = Pkg::Util::Tool.find_tool('keychain')
17
+ else
18
+ @keychain
19
+ end
20
+ end
21
+
22
+ def load_keychain
23
+ unless @keychain_loaded
24
+ unless ENV['RPM_GPG_AGENT']
25
+ kill_keychain
26
+ start_keychain
27
+ end
28
+ @keychain_loaded = true
29
+ end
30
+ end
31
+
32
+ def kill_keychain
33
+ if keychain
34
+ stdout, _, _ = Pkg::Util::Execution.capture3("#{keychain} -k mine")
35
+ stdout
36
+ end
37
+ end
38
+
39
+ def start_keychain
40
+ if keychain
41
+ keychain_output, _, _ = Pkg::Util::Execution.capture3("#{keychain} -q --agents gpg --eval #{key}")
42
+ keychain_output.chomp!
43
+ new_env = keychain_output.match(/GPG_AGENT_INFO=([^;]*)/)
44
+ ENV["GPG_AGENT_INFO"] = new_env[1]
45
+ else
46
+ fail "Keychain is not installed, it is required to autosign using gpg."
47
+ end
48
+ end
49
+
50
+ def sign_file(file)
51
+ gpg ||= Pkg::Util::Tool.find_tool('gpg')
52
+
53
+ if gpg
54
+ if File.exist? "#{file}.asc"
55
+ warn "Signature on #{file} exists, skipping..."
56
+ return true
57
+ end
58
+ use_tty = "--no-tty --use-agent" if ENV['RPM_GPG_AGENT']
59
+ stdout, _, _ = Pkg::Util::Execution.capture3("#{gpg} #{use_tty} --armor --detach-sign -u #{key} #{file}")
60
+ stdout
61
+ else
62
+ fail "No gpg available. Cannot sign #{file}."
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,95 @@
1
+ # Utility methods for handling Jenkins
2
+ require 'net/http'
3
+ require 'json'
4
+
5
+ module Pkg::Util::Jenkins
6
+
7
+ class << self
8
+
9
+ # Use the curl to create a jenkins job from a valid XML
10
+ # configuration file.
11
+ # Returns the URL to the job
12
+ def create_jenkins_job(name, xml_file)
13
+ create_url = "http://#{Pkg::Config.jenkins_build_host}/createItem?name=#{name}"
14
+ form_args = ["-H", '"Content-Type: application/xml"', "--data-binary", "@#{xml_file}"]
15
+ Pkg::Util::Net.curl_form_data(create_url, form_args)
16
+ "http://#{Pkg::Config.jenkins_build_host}/job/#{name}"
17
+ end
18
+
19
+ # Use the curl to check of a named job is defined on the jenkins server. We
20
+ # curl the config file rather than just checking if the job exists by curling
21
+ # the job url and passing --head because jenkins will mistakenly return 200 OK
22
+ # if you issue multiple very fast requests just requesting the header.
23
+ def jenkins_job_exists?(name)
24
+ job_url = "http://#{Pkg::Config.jenkins_build_host}/job/#{name}/config.xml"
25
+ form_args = ["--silent", "--fail"]
26
+ output, retval = Pkg::Util::Net.curl_form_data(job_url, form_args, :quiet => true)
27
+ return output if retval.nil?
28
+ return Pkg::Util::Execution.success?(retval)
29
+ end
30
+
31
+ # Wait for last build of job to finish.
32
+ #
33
+ # @param build_url [String] Valid build uri of a Jenkins job.
34
+ # @param polling_interval [Int] Timeout in seconds between HTTP GET on given
35
+ # build_uri.
36
+ # @param log_frequency [Int] Frequency in seconds of polling log
37
+ #
38
+ def wait_for_build(build_url, polling_interval = 2, log_frequency = 60)
39
+ $stdout.sync = true
40
+ build_hash = get_jenkins_info(build_url)
41
+ total_time = 0
42
+ while build_hash['building']
43
+ build_hash = get_jenkins_info(build_url)
44
+ sleep polling_interval
45
+ total_time += polling_interval
46
+ if total_time >= log_frequency
47
+ $stdout.puts "Polling #{build_url}..."
48
+ total_time = 0
49
+ end
50
+ end
51
+ $stdout.sync = false
52
+ return build_hash
53
+ end
54
+
55
+ # Query jenkins api and return a hash parsed from the JSON response if
56
+ # response is usable. Raise Runtime Error if response code is other than
57
+ # HTTP 200.
58
+ #
59
+ # @param url [String] Valid url of a Jenkins job.
60
+ #
61
+ def get_jenkins_info(url)
62
+ uri = URI("#{url}/api/json")
63
+ response = Net::HTTP.get_response(uri)
64
+ unless response.code == '200'
65
+ raise "Unable to query #{uri}, please check that it is valid."
66
+ end
67
+ return JSON.parse(response.body)
68
+ end
69
+
70
+ # Poll the job at the given url until it is finished, then return the final
71
+ # map of build information for calling context to do with as it pleases.
72
+ #
73
+ # Note that this method uses the build specified by the job api's lastBuild
74
+ # parameter.
75
+ #
76
+ # @param job_url [String] Valid url of a Jenkins job.
77
+ #
78
+ def poll_jenkins_job(job_url)
79
+ job_hash = get_jenkins_info(job_url)
80
+
81
+ ##
82
+ # Sometimes we get a nil because we get here too soon after the jenkins
83
+ # job's build was triggered. This is kind of an ugly workaround but
84
+ # whatever.
85
+ #
86
+ while job_hash['lastBuild'].nil?
87
+ job_hash = get_jenkins_info(job_url)
88
+ sleep 1
89
+ end
90
+
91
+ wait_for_build job_hash['lastBuild']['url']
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,69 @@
1
+ # A collection of utility methods that don't belong in any of our other top level Pkg::Util modules
2
+ # and probably won't anytime soon.
3
+
4
+ module Pkg::Util::Misc
5
+ class << self
6
+ # This method takes a string and a list of tokens and variables and it replaces
7
+ # the listed tokens with the matched variable if it exists. All values will
8
+ # be explicitly coerced to strings.
9
+ def search_and_replace(search_string, replacements)
10
+ raise ArgumentError "replacements must respond to #each_pair" unless
11
+ replacements.respond_to? :each_pair
12
+
13
+ replacements.each_pair do |token, value|
14
+ unless value
15
+ warn "replacement value for '#{token}' probably shouldn't be nil"
16
+ next
17
+ end
18
+
19
+ search_string.gsub!(token.to_s, value.to_s)
20
+ end
21
+
22
+ search_string
23
+ end
24
+
25
+ # Loads and parses json from a file. Will treat the keys in the
26
+ # json as methods to invoke on the component in question
27
+ #
28
+ # @param file [String] Path to the json file
29
+ # @raise [RuntimeError] exceptions are raised if there is no file, if it refers to methods that don't exist, or if it does not contain a Hash
30
+ def load_from_json(file)
31
+ data = JSON.parse(File.read(file))
32
+ unless data.is_a?(Hash)
33
+ raise "Hash required. Got '#{data.class}' when parsing '#{file}'"
34
+ end
35
+ # We explicity return data here b/c the unless clause above will cause the
36
+ # Function to return nil.
37
+ # -Sean P. M. 05/11/2016
38
+ data
39
+ end
40
+
41
+ # @param gem the name of the gem to see if it is installed
42
+ # @return boolean for whether or not the gem is installed
43
+ def check_gem(gem)
44
+ if %x(gem list -q #{gem}).chomp.empty?
45
+ return false
46
+ else
47
+ return true
48
+ end
49
+ end
50
+
51
+ # This loads your ~/.gem/credentials file and uses your api key
52
+ # to query rubygems.org for which gems you own. There may be better ways
53
+ # to query this but having to pull this information using curl is sort of
54
+ # gross. It works though, so ¯\_(ツ)_/¯
55
+ # @param gem_name the gem to check if you are an owner
56
+ # @return boolean whether or not you own the gem
57
+ def check_rubygems_ownership(gem_name)
58
+ require 'yaml'
59
+ credentials = YAML.load_file("#{ENV['HOME']}/.gem/credentials")
60
+ gems = YAML.load(%x(curl -H 'Authorization:#{credentials[:rubygems_api_key]}' https://rubygems.org/api/v1/gems.yaml))
61
+ gems.each do |gem|
62
+ if gem['name'] == gem_name
63
+ return true
64
+ end
65
+ end
66
+ return false
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,410 @@
1
+ # Utility methods for handling network calls and interactions
2
+
3
+ module Pkg::Util::Net
4
+
5
+ class << self
6
+
7
+ # This simple method does an HTTP get of a URI and writes it to a file
8
+ # in a slightly more platform agnostic way than curl/wget
9
+ def fetch_uri(uri, target)
10
+ require 'open-uri'
11
+ if Pkg::Util::File.file_writable?(File.dirname(target))
12
+ File.open(target, 'w') { |f| f.puts(open(uri).read) }
13
+ end
14
+ end
15
+
16
+ # Get the hostname of the current host
17
+ def hostname
18
+ require 'socket'
19
+ Socket.gethostname
20
+ end
21
+
22
+ # Check that the current host matches the one we think it should
23
+ def check_host(host, options = { required: true })
24
+ return true if hostname == host
25
+
26
+ fail "Error: #{hostname} does not match #{host}" if options[:required]
27
+ return nil
28
+ end
29
+
30
+ # @param hosts - An array of hosts to try ssh-ing into
31
+ # If the host needs a special username it should be passed
32
+ # in as user@host
33
+ # @return an array of hosts where ssh access failed. Empty array if
34
+ # successful
35
+ def check_host_ssh(hosts)
36
+ errs = []
37
+ Array(hosts).flatten.each do |host|
38
+ begin
39
+ remote_execute(host, 'exit', { extra_options: '-oBatchMode=yes' })
40
+ rescue
41
+ errs << host
42
+ end
43
+ end
44
+ return errs
45
+ end
46
+
47
+ # @param hosts - An array of hosts to check for gpg keys
48
+ # If the host needs a special username it should be passed
49
+ # in as user@host
50
+ # @param gpg - The gpg secret key to look for
51
+ # @return an array of hosts where ssh access failed. Empty array if
52
+ # successful
53
+ def check_host_gpg(hosts, gpg)
54
+ errs = []
55
+ Array(hosts).flatten.each do |host|
56
+ begin
57
+ remote_execute(host, "gpg --list-secret-keys #{gpg} > /dev/null 2&>1",
58
+ { extra_options: '-oBatchMode=yes' })
59
+ rescue
60
+ errs << host
61
+ end
62
+ end
63
+ return errs
64
+ end
65
+
66
+ def remote_execute(target_host, command, user_options = {})
67
+ option_defaults = {
68
+ capture_output: false,
69
+ extra_options: '',
70
+ fail_fast: true,
71
+ trace: false
72
+ }
73
+ options = option_defaults.merge(user_options)
74
+
75
+ ssh = Pkg::Util::Tool.check_tool('ssh')
76
+
77
+ # we pass some pretty complicated commands in via ssh. We need this to fail
78
+ # if any part of the remote ssh command fails.
79
+ shell_flags = ''
80
+ shell_flags += 'set -e;' if options[:fail_fast]
81
+ shell_flags += 'set -x;' if options[:trace]
82
+ shell_commands = "#{shell_flags}#{command}"
83
+
84
+ remote_command = "#{ssh} #{options[:extra_options]} -t #{target_host} " +
85
+ "'#{shell_commands.gsub("'", "'\\\\''")}'"
86
+
87
+ # This is NOT a good way to support this functionality.
88
+ if ENV['DRYRUN']
89
+ puts "[DRY-RUN] Executing '#{command}' on #{target}"
90
+ puts "[DRY-RUN] #{cmd}"
91
+ return ''
92
+ end
93
+
94
+ # We're forced to make different calls depending on the capture_output option
95
+ # because something about our #capture3 method screws up gpg. This should
96
+ # be untangled.
97
+ if options[:capture_output]
98
+ stdout, stderr, exitstatus = Pkg::Util::Execution.capture3(remote_command)
99
+ Pkg::Util::Execution.success?(exitstatus) or
100
+ raise "Remote ssh command (\"#{remote_command}\") failed."
101
+ return stdout, stderr
102
+ end
103
+
104
+ # Pkg::Util::Execution.capture3 reports its command but Kernel.system does not
105
+ # Let's print it out for some amount of consistency.
106
+ puts "Remote Execute: '#{remote_command}'"
107
+ Kernel.system(remote_command)
108
+ Pkg::Util::Execution.success? or
109
+ raise "Remote ssh command (\"#{remote_command}\") failed."
110
+ end
111
+
112
+ ###
113
+ ### Deprecated method implemented as a shim to the new `remote_execute` method
114
+ ###
115
+ def remote_ssh_cmd(target, command, capture_output = false, extra_options = '', fail_fast = true, trace = false) # rubocop:disable Style/ParameterLists
116
+ puts "Warn: \"remote_ssh_cmd\" call in packaging is deprecated. Use \"remote_execute\" instead."
117
+ remote_execute(target, command, {
118
+ capture_output: capture_output,
119
+ extra_options: extra_options,
120
+ fail_fast: fail_fast,
121
+ trace: trace })
122
+ end
123
+
124
+ # Construct a valid rsync command
125
+ # @return [String] a rsync command that can be used in shell or ssh methods
126
+ # @param [String, Pathname] origin_path the path to sync from; if opts[:target_path]
127
+ # is not passed, then the parent directory of `origin_path` will be used to
128
+ # construct a target path to sync to.
129
+ # @param [Hash] opts additional options that can be used to construct
130
+ # the rsync command.
131
+ # @option opts [String] :bin ('rsync') the path to rsync
132
+ # (can be relative or fully qualified).
133
+ # @option opts [String] :origin_host the remote host to sync data from; cannot
134
+ # be specified alongside :target_host
135
+ # @option opts [String] :target_host the remote host to sync data to; cannot
136
+ # be specified alongside :origin_host.
137
+ # @option opts [String] :extra_flags (["--ignore-existing"]) extra flags to
138
+ # use when constructing an rsync command
139
+ # @option opts [String] :dryrun (false) tell rsync to perform a trial run
140
+ # with no changes made.
141
+ # @raise [ArgumentError] if opts[:origin_host] and opts[:target_host] names
142
+ # are both defined.
143
+ # @raise [ArgumentError] if :origin_path exists without opts[:target_path],
144
+ # opts[:origin_host], remote target is defined.
145
+ def rsync_cmd(origin_path, opts = {})
146
+ options = {
147
+ bin: 'rsync',
148
+ origin_host: nil,
149
+ target_path: nil,
150
+ target_host: nil,
151
+ extra_flags: nil,
152
+ dryrun: false }.merge(opts)
153
+ origin = Pathname.new(origin_path)
154
+ target = options[:target_path] || origin.parent
155
+
156
+ raise(ArgumentError, "Cannot sync between two remote hosts") if
157
+ options[:origin_host] && options[:target_host]
158
+
159
+ raise(ArgumentError, "Cannot sync path '#{origin}' because both origin_host and target_host are nil. Perhaps you need to set TEAM=release ?") unless
160
+ options[:origin_host] || options[:target_host]
161
+
162
+ cmd = %W(
163
+ #{options[:bin]}
164
+ --recursive
165
+ --hard-links
166
+ --links
167
+ --verbose
168
+ --omit-dir-times
169
+ --no-perms
170
+ --no-owner
171
+ --no-group
172
+ ) + [*options[:extra_flags]]
173
+
174
+ cmd << '--dry-run' if options[:dryrun]
175
+ cmd << Pkg::Util.pseudo_uri(path: origin, host: options[:origin_host])
176
+ cmd << Pkg::Util.pseudo_uri(path: target, host: options[:target_host])
177
+
178
+ cmd.uniq.compact.join("\s")
179
+ end
180
+
181
+ # A generic rsync execution method that wraps rsync_cmd in a
182
+ # call to Pkg::Util::Execution#capture3()
183
+ def rsync_exec(source, opts = {})
184
+ options = {
185
+ bin: Pkg::Util::Tool.check_tool('rsync'),
186
+ origin_host: nil,
187
+ target_path: nil,
188
+ target_host: nil,
189
+ extra_flags: nil,
190
+ dryrun: ENV['DRYRUN'] }.merge(opts.delete_if { |_, value| value.nil? })
191
+
192
+ stdout, _, _ = Pkg::Util::Execution.capture3(rsync_cmd(source, options), true)
193
+ stdout
194
+ end
195
+
196
+ # A wrapper method to maintain the existing interface for executing
197
+ # outbound rsync commands with minimal changes to existing code.
198
+ def rsync_to(source, target_host, dest, opts = { extra_flags: ["--ignore-existing"] })
199
+ rsync_exec(
200
+ source,
201
+ target_host: target_host,
202
+ target_path: dest,
203
+ extra_flags: opts[:extra_flags],
204
+ dryrun: opts[:dryrun],
205
+ bin: opts[:bin],
206
+ )
207
+ end
208
+
209
+ # A wrapper method to maintain the existing interface for executing
210
+ # incoming rsync commands with minimal changes to existing code.
211
+ def rsync_from(source, origin_host, dest, opts = {})
212
+ rsync_exec(
213
+ source,
214
+ origin_host: origin_host,
215
+ target_path: dest,
216
+ extra_flags: opts[:extra_flags],
217
+ dryrun: opts[:dryrun],
218
+ bin: opts[:bin],
219
+ )
220
+ end
221
+
222
+ def s3sync_to(source, target_bucket, target_directory = "", flags = [])
223
+ s3cmd = Pkg::Util::Tool.check_tool('s3cmd')
224
+
225
+ if Pkg::Util::File.file_exists?(File.join(ENV['HOME'], '.s3cfg'))
226
+ stdout, _, _ = Pkg::Util::Execution.capture3("#{s3cmd} sync #{flags.join(' ')} '#{source}' s3://#{target_bucket}/#{target_directory}/")
227
+ stdout
228
+ else
229
+ fail "#{File.join(ENV['HOME'], '.s3cfg')} does not exist. It is required to ship files using s3cmd."
230
+ end
231
+ end
232
+
233
+ # This is fairly absurd. We're implementing curl by shelling out. What do I
234
+ # wish we were doing? Using a sweet ruby wrapper around curl, such as Curb or
235
+ # Curb-fu. However, because we're using clean build systems and trying to
236
+ # make this portable with minimal system requirements, we can't very well
237
+ # depend on libraries that aren't in the ruby standard libaries. We could
238
+ # also do this using Net::HTTP but that set of libraries is a rabbit hole to
239
+ # go down when what we're trying to accomplish is posting multi-part form
240
+ # data that includes file uploads to jenkins. It gets hairy fairly quickly,
241
+ # but, as they say, pull requests accepted.
242
+ #
243
+ # This method takes three arguments
244
+ # 1) String - the URL to post to
245
+ # 2) Array - Ordered array of name=VALUE curl form parameters
246
+ # 3) Hash - Options to be set
247
+ def curl_form_data(uri, form_data = [], options = {})
248
+ curl = Pkg::Util::Tool.check_tool("curl")
249
+ #
250
+ # Begin constructing the post string.
251
+ # First, assemble the form_data arguments
252
+ #
253
+ post_string = "-i "
254
+ form_data.each do |param|
255
+ post_string << "#{param} "
256
+ end
257
+
258
+ # Add the uri
259
+ post_string << "'#{uri}'"
260
+
261
+ # If this is quiet, we're going to silence all output
262
+ begin
263
+ stdout, _, retval = Pkg::Util::Execution.capture3("#{curl} #{post_string}")
264
+ if options[:quiet]
265
+ stdout = ''
266
+ end
267
+ return stdout, retval
268
+ rescue RuntimeError => e
269
+ puts e
270
+ return false
271
+ end
272
+ end
273
+
274
+ def uri_status_code(uri)
275
+ data = [
276
+ '--request GET',
277
+ '--silent',
278
+ '--location',
279
+ '--write-out "%{http_code}"',
280
+ '--output /dev/null'
281
+ ]
282
+ stdout, _ = Pkg::Util::Net.curl_form_data(uri, data)
283
+ stdout
284
+ end
285
+
286
+ # Use the provided URL string to print important information with
287
+ # ASCII emphasis
288
+ def print_url_info(url_string)
289
+ puts "\n////////////////////////////////////////////////////////////////////////////////\n\n
290
+ Build submitted. To view your build progress, go to\n#{url_string}\n\n
291
+ ////////////////////////////////////////////////////////////////////////////////\n\n"
292
+ end
293
+
294
+ def remote_set_ownership(host, owner, group, files)
295
+ remote_cmd = "for file in #{files.join(" ")}; do if [[ -d $file ]] || ! `lsattr $file | grep -q '\\-i\\-'`; then sudo chown #{owner}:#{group} $file; else echo \"$file is immutable\"; fi; done"
296
+ Pkg::Util::Net.remote_execute(host, remote_cmd)
297
+ end
298
+
299
+ def remote_set_permissions(host, permissions, files)
300
+ remote_cmd = "for file in #{files.join(" ")}; do if [[ -d $file ]] || ! `lsattr $file | grep -q '\\-i\\-'`; then sudo chmod #{permissions} $file; else echo \"$file is immutable\"; fi; done"
301
+ Pkg::Util::Net.remote_execute(host, remote_cmd)
302
+ end
303
+
304
+ # Remotely set the immutable bit on a list of files
305
+ def remote_set_immutable(host, files)
306
+ Pkg::Util::Net.remote_execute(host, "sudo chattr +i #{files.join(" ")}")
307
+ end
308
+
309
+ # Create a symlink indicating the latest version of a package
310
+ #
311
+ # @param package_name [String] The name of the package you want to symlink
312
+ # to, e.g. 'puppet-agent', 'facter', etc.
313
+ # @param dir [String] The directory you want to find the latest package and
314
+ # create the symlink in.
315
+ # @param platform_ext [String] The type of files you want to consider, e.g.
316
+ # 'dmg', 'msi', etc.
317
+ # @param options [Hash] Additional optional params:
318
+ # @option :arch [String] Architecture you want to narrow your search by.
319
+ # @option :excludes [Array] Strings you want to exclude from your search,
320
+ # e.g. 'agent' if only searching for 'puppet'.
321
+ def remote_create_latest_symlink(package_name, dir, platform_ext, options = {})
322
+ ls_cmd = "ls -1 *.#{platform_ext} | grep -v latest | grep -v rc | grep -P '#{package_name}-\\d' "
323
+
324
+ # store this in a separate var to avoid side affects
325
+ full_package_name = String.new(package_name)
326
+
327
+ if options[:arch]
328
+ ls_cmd << "| grep #{options[:arch]}"
329
+ full_package_name << "-#{options[:arch]}"
330
+ end
331
+ if options[:excludes]
332
+ options[:excludes].each do |excl|
333
+ ls_cmd << "| grep -v #{excl} "
334
+ end
335
+ end
336
+ ls_cmd << '| sort --version-sort | tail -1'
337
+ cmd = <<-CMD
338
+ if [ ! -d '#{dir}' ] ; then
339
+ echo "directory '#{dir}' does not exist, not creating latest package link"
340
+ exit 0
341
+ fi
342
+ pushd '#{dir}'
343
+ link_target=$(#{ls_cmd})
344
+ if [ -z "$link_target" ] ; then
345
+ echo "Unable to find a link target for '#{full_package_name}' in '#{dir}'; skipping link creation"
346
+ exit 0
347
+ fi
348
+ echo "creating link to '$link_target'"
349
+ ln -sf "$link_target" #{full_package_name}-latest.#{platform_ext}
350
+ CMD
351
+
352
+ _, err = Pkg::Util::Net.remote_execute(
353
+ Pkg::Config.staging_server, cmd, { capture_output: true })
354
+ $stderr.puts err
355
+ end
356
+
357
+ def escape_html(uri)
358
+ require 'cgi'
359
+ CGI.escapeHTML(uri)
360
+ end
361
+
362
+ # Add a parameter to a given uri. If we were sane we'd use
363
+ # encode_www_form(params) of URI, but because we're not, because that will http
364
+ # encode it, which isn't what we want since we're require the encoding provided
365
+ # by escapeHTML of CGI, since this is being transfered in the xml of a jenkins
366
+ # job via curl and DEAR JEEBUS WHAT HAVE WE DONE.
367
+ def add_param_to_uri(uri, param)
368
+ require 'uri'
369
+ uri = URI.parse(uri)
370
+ uri.query = [uri.query, param].compact.join('&')
371
+ uri.to_s
372
+ end
373
+
374
+ # We take a tar argument for cases where `tar` isn't best, e.g. Solaris. We
375
+ # also take an optional argument of the tarball containing the git bundle to
376
+ # use.
377
+ def remote_unpack_git_bundle(host, treeish, tar_cmd = nil, tarball = nil)
378
+ unless tar = tar_cmd
379
+ tar = 'tar'
380
+ end
381
+ tarball ||= Pkg::Util::Git.bundle(treeish)
382
+ tarball_name = File.basename(tarball).gsub('.tar.gz', '')
383
+ Pkg::Util::Net.rsync_to(tarball, host, '/tmp')
384
+ appendix = Pkg::Util.rand_string
385
+ git_bundle_directory = File.join('/tmp', "#{Pkg::Config.project}-#{appendix}")
386
+ command = <<-DOC
387
+ #{tar} -zxvf /tmp/#{tarball_name}.tar.gz -C /tmp/ ;
388
+ git clone --recursive /tmp/#{tarball_name} #{git_bundle_directory} ;
389
+ DOC
390
+ Pkg::Util::Net.remote_execute(host, command)
391
+ return git_bundle_directory
392
+ end
393
+
394
+ def remote_bundle_install_command
395
+ export_packaging_location = ''
396
+ export_packaging_location = "export PACKAGING_LOCATION='#{ENV['PACKAGING_LOCATION']}';" if ENV['PACKAGING_LOCATION'] && !ENV['PACKAGING_LOCATION'].empty?
397
+ command = "source /usr/local/rvm/scripts/rvm; rvm use ruby-2.5.1; #{export_packaging_location} bundle install --path .bundle/gems ;"
398
+ end
399
+
400
+ # Given a BuildInstance object and a host, send its params to the host. Return
401
+ # the remote path to the params.
402
+ def remote_buildparams(host, build)
403
+ params_file = build.config_to_yaml
404
+ params_file_name = File.basename(params_file)
405
+ params_dir = Pkg::Util.rand_string
406
+ Pkg::Util::Net.rsync_to(params_file, host, "/tmp/#{params_dir}/")
407
+ "/tmp/#{params_dir}/#{params_file_name}"
408
+ end
409
+ end
410
+ end