apt_stage_artifacts 0.4.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 24c10c67846c819b0174ac0b94f2a4b7836e73589969335c1fb4b6a2779e59bc
4
+ data.tar.gz: 47be9199cdabcc1bb1f242f6cd4e1aa8bea968d3ed83bc215e17c67ebe8d3f2c
5
+ SHA512:
6
+ metadata.gz: 421d5a2a39379edddffa17bba6925c328d8f2a51d3af858743b771729b337ed74a9d4ce8c48a51798a6371f1b477fb16ab38833966339fe2ac6978650e0a8d4a
7
+ data.tar.gz: 585ec2d56f0959a19cb83fad46d5934265c1eaa1dce02b89155ada6a6d507dd3ab064e8a7447cffc5be3ea5384b4e9354331fdab1faf146a28a31e85c6fedb69
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,152 @@
1
+ ---
2
+
3
+ require: rubocop-rake
4
+
5
+ AllCops:
6
+ Exclude:
7
+ - Rakefile
8
+ - Gemfile
9
+ - bin/*
10
+ - spec/**/*.rb
11
+ - templates/*
12
+
13
+ Gemspec/DateAssignment:
14
+ Enabled: true
15
+
16
+ Layout/FirstArrayElementIndentation:
17
+ Enabled: false
18
+
19
+ Layout/MultilineMethodCallIndentation:
20
+ Enabled: false
21
+
22
+ Layout/SpaceBeforeBrackets:
23
+ Enabled: true
24
+
25
+ Lint/AmbiguousAssignment:
26
+ Enabled: true
27
+
28
+ Lint/DeprecatedConstants:
29
+ Enabled: true
30
+
31
+ Lint/DuplicateBranch:
32
+ Enabled: true
33
+
34
+ Lint/DuplicateRegexpCharacterClassElement:
35
+ Enabled: true
36
+
37
+ Lint/EmptyBlock:
38
+ Enabled: true
39
+
40
+ Lint/EmptyClass:
41
+ Enabled: true
42
+
43
+ Lint/LambdaWithoutLiteralBlock:
44
+ Enabled: true
45
+
46
+ Lint/NoReturnInBeginEndBlocks:
47
+ Enabled: true
48
+
49
+ Lint/NumberedParameterAssignment:
50
+ Enabled: true
51
+
52
+ Lint/OrAssignmentToConstant:
53
+ Enabled: true
54
+
55
+ Lint/RedundantDirGlobSort:
56
+ Enabled: true
57
+
58
+ Lint/SymbolConversion:
59
+ Enabled: true
60
+
61
+ Lint/ToEnumArguments:
62
+ Enabled: true
63
+
64
+ Lint/TripleQuotes:
65
+ Enabled: true
66
+
67
+ Lint/UnexpectedBlockArity:
68
+ Enabled: true
69
+
70
+ Lint/UnmodifiedReduceAccumulator:
71
+ Enabled: true
72
+
73
+ Metrics/AbcSize:
74
+ Enabled: false
75
+
76
+ Metrics/ClassLength:
77
+ Enabled: false
78
+
79
+ Metrics/CyclomaticComplexity:
80
+ Enabled: false
81
+
82
+ Metrics/MethodLength:
83
+ Enabled: false
84
+
85
+ Metrics/ModuleLength:
86
+ Enabled: false
87
+
88
+ Metrics/PerceivedComplexity:
89
+ Enabled: false
90
+
91
+ Style/ArgumentsForwarding:
92
+ Enabled: true
93
+
94
+ Style/CollectionCompact:
95
+ Enabled: true
96
+
97
+ Style/DocumentDynamicEvalDefinition:
98
+ Enabled: true
99
+
100
+ Style/Documentation:
101
+ Enabled: false
102
+
103
+ Style/EachWithObject:
104
+ Enabled: false
105
+
106
+ Style/EndlessMethod:
107
+ Enabled: true
108
+
109
+ Style/FormatString:
110
+ Enabled: false
111
+
112
+ Style/FormatStringToken:
113
+ Enabled: false
114
+
115
+ Style/FrozenStringLiteralComment:
116
+ Enabled: false
117
+
118
+ Style/HashConversion:
119
+ Enabled: true
120
+
121
+ Style/HashExcept:
122
+ Enabled: true
123
+
124
+ Style/IfUnlessModifier:
125
+ Enabled: false
126
+
127
+ Style/IfWithBooleanLiteralBranches:
128
+ Enabled: true
129
+
130
+ Style/MutableConstant:
131
+ Enabled: false
132
+
133
+ Style/NegatedIfElseCondition:
134
+ Enabled: true
135
+
136
+ Style/NilLambda:
137
+ Enabled: true
138
+
139
+ Style/RedundantArgument:
140
+ Enabled: true
141
+
142
+ Style/RedundantReturn:
143
+ Enabled: false
144
+
145
+ Style/StringChars:
146
+ Enabled: true
147
+
148
+ Style/SwapValues:
149
+ Enabled: true
150
+
151
+ Style/CommandLiteral:
152
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.4.0] - 2021-04-28
11
+ ### Added
12
+ - Initial release
13
+
data/CODEOWNERS ADDED
@@ -0,0 +1,3 @@
1
+ # This repo is owned by Puppet Release Engineering
2
+
3
+ * @puppetlabs/release-engineering
data/DEVELOPMENT.md ADDED
@@ -0,0 +1,18 @@
1
+ # Development
2
+
3
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
4
+
5
+ ## Local Build
6
+
7
+ To create a local build of the gem: `bundle exec rake build`
8
+
9
+ ## Local Install
10
+
11
+ To install this gem onto your local machine: `bundle exec rake install`.
12
+
13
+ ## New Release
14
+
15
+ To release a new version:
16
+
17
+ - update the version number in `lib/version.rb`
18
+ - run `bundle exec rake release` This will create a git tag for the version, push git commits and tags, and push the `.gem` file to [Puppet Artifactory Rubygems Repo ](https://artifactory.delivery.puppetlabs.net/artifactory/webapp/#/artifacts/browse/tree/General/rubygems).
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://artifactory.delivery.puppetlabs.net/artifactory/api/gems/rubygems'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2021 Puppet, Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # apt_stage_artifacts
2
+
3
+ apt_stage_artifacts is a Ruby gem for Puppet Release Engineering to deliver locally built .deb packages to our freight-based apt repository staging.
4
+
5
+ It only handles internal staging and not the final push to outside customers.
6
+
7
+ The gem provides command-line programs for doing the staging.
8
+
9
+ ## Usage
10
+
11
+ ### apt-stage-artifacts
12
+
13
+ The main user script, `apt-stage-artifacts` expects the current directory to be a vanagon-style build where debian `.deb` files have been built into `output/deb`.
14
+
15
+ After consulting the [build-data](https://github.com/puppetlabs/build-data/blob/release/builder_data.yaml) repo for the name of the staging machine, it packs up the debian artifacts into a tarball and copies it there via scp.
16
+
17
+ It then invokes the remaining scripts on the staging machine to do the rest of the staging:
18
+
19
+ ### apt-stage-from-tarball
20
+
21
+ `apt-stage-from-tarball` runs in the staging server. It unpacks the tarball created by `apt-stage-artifacts` and calls `apt-add-to-freight-library` on each of them. This adds each `.deb` file to the correct freight library (puppet6, puppet7, puppet7-nightly, etc.)
22
+
23
+ Once all .deb files have been added, `apt-stage-from-tarball` calls `apt-update-freight-cache`, which instructs freight to generate the actual APT repo (which freight calls a "cache"), including GPG signing of the resulting cache.
24
+
25
+ When completed, the resulting "caches" are signed, functional APT repos which can sync'd to the outside world.
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'yard'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ YARD::Rake::YardocTask.new
7
+
8
+ task :build => :yard
9
+ task :default => :spec
10
+
11
+ desc 'Run rubocop'
12
+ task :rubocop do
13
+ sh 'rubocop || true'
14
+ end
15
+
16
+ task :clean do
17
+ sh 'rm -f pkg/*.gem'
18
+ end
19
+
20
+ task :devtest => [:clean, :build, :install] do
21
+ test_host = ENV['APT_STAGE_TEST_HOST'].to_s
22
+ if test_host.empty?
23
+ puts 'Error: the "APT_STAGE_TEST_HOST" environment variable is unset.'
24
+ puts 'Set it to the hostname where the test freight environment exists.'
25
+ exit 1
26
+ end
27
+
28
+ sh "ssh #{test_host} rm -f ~/apt_stage_artifacts\*.gem"
29
+ sh "scp pkg/apt_stage_artifacts*.gem #{test_host}:~"
30
+ sh "ssh #{test_host} gem uninstall apt_stage_artifacts"
31
+ sh "ssh #{test_host} gem install apt_stage_artifacts\*.gem"
32
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'lib/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'apt_stage_artifacts'
5
+ spec.version = AptStageArtifacts::VERSION
6
+ spec.authors = ['Puppet Release Engineering']
7
+ spec.email = ['release@puppet.com']
8
+
9
+ spec.summary = 'Stages .deb artifacts to a remote freight repository'
10
+ spec.homepage = 'https://github.com/puppetlabs/apt_stage_artifacts'
11
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
12
+
13
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
14
+
15
+ spec.metadata['homepage_uri'] = spec.homepage
16
+ spec.metadata['source_code_uri'] = spec.homepage
17
+ spec.metadata['changelog_uri'] = File.join(spec.homepage, 'CHANGELOG.md')
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = 'exe'
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ['lib']
27
+
28
+ spec.add_development_dependency 'pry', '~> 0.14'
29
+ spec.add_development_dependency 'pry-byebug', '~> 3.0'
30
+ spec.add_development_dependency 'rake', '~> 13.0'
31
+ spec.add_development_dependency 'rspec', '~> 3.0'
32
+ spec.add_development_dependency 'rubocop-rake'
33
+ spec.add_development_dependency 'rubocop-rspec'
34
+ spec.add_development_dependency 'yard', '~> 0.9'
35
+
36
+ spec.add_runtime_dependency 'docopt'
37
+ end
data/bin/console ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'apt_add_to_freight_library'
5
+ require 'apt_stage_artifacts'
6
+ require 'apt_stage_from_tarball'
7
+ require 'apt_update_freight_cache'
8
+
9
+ require 'irb'
10
+
11
+ puts %{Mininal startup options. Choose one:
12
+ irb> AptAddToFreightLibrary.new(Array.new)
13
+ irb> AptStageFromTarball.new(Array.new)
14
+ irb> AptUpdateFreightCache.new(Array.new)
15
+ irb> AptStageArtifacts.new(Array.new)
16
+ }
17
+
18
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'apt_add_to_freight_library'
4
+
5
+ AptAddToFreightLibrary.new(ARGV).run
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'apt_stage_artifacts'
4
+
5
+ AptStageArtifacts.new(ARGV).run
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'apt_stage_from_tarball'
4
+
5
+ AptStageFromTarball.new(ARGV.first).run
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'apt_update_freight_cache'
4
+
5
+ AptUpdateFreightCache.new(ARGV).run
@@ -0,0 +1,121 @@
1
+ require 'version'
2
+ require 'mixins/logging'
3
+
4
+ require 'English'
5
+ require 'docopt'
6
+ require 'open-uri'
7
+ require 'tmpdir'
8
+
9
+ ## This is a wrapper for https://github.com/freight-team/freight, which creates APT repos
10
+ ## from deb files.
11
+ ##
12
+ ## In our case, we maintain a set of debian repos, based on puppet version and a repo-type
13
+ ## 'nightly', 'archive' and 'stable'.
14
+ ##
15
+ ## freight provides signing services, so this script needs to exist on a machine where GPG
16
+ ## signing has been set up.
17
+ ##
18
+ ## Typical usage would be for a remote process to call this script with a list of .deb
19
+ ## URLS:
20
+ ##
21
+ ## apt-add-to-freight-library --puppet-version=7 --repo-type=nightly --codename=stretch
22
+ ## https://some-server.net/somepackage-a_0.19.2-1stretch_amd64.deb
23
+ ## https://some-server.net/somepackage-b_0.19.2-1stretch_amd64.deb
24
+ ##
25
+ ## Alternately one coule scp a bunch of .deb files to the machine
26
+ ## this is installed on then call this script with all the deb files as arguments. For example:
27
+ ##
28
+ ## apt-add-to-freight-library --puppet-version=7 --repo-type=nightly --codename=buster
29
+ ## /tmp/xxtmpdir/somepackage-a_0.0.0-1buster_amd64.deb
30
+ ## /tmp/xxtmpdir/somepackage-b_1.2.3-5buster_amd64.deb
31
+ ## (etc.)
32
+
33
+ class AptAddToFreightLibrary
34
+ include Logging
35
+
36
+ FREIGHT_COMMAND = '/usr/bin/freight'
37
+ VALID_PUPPET_VERSIONS = %w[6 7 8]
38
+ VALID_REPO_TYPES = %w[archive nightly stable]
39
+ VALID_CODENAMES = %w[bionic jessie stretch buster bullseye bookwork focal xenial]
40
+
41
+ DOCUMENTATION = <<~DOCOPT
42
+ Adds debian .deb files to a freight library.
43
+
44
+ Usage:
45
+ apt-add-to-freight-library [--verbose] --puppet-version=VERSION --repo-type=REPO_TYPE --codename=DEBIAN_CODENAME <deb_file> ...
46
+ apt-add-to-freight-library --help
47
+ apt-add-to-freight-library --version
48
+
49
+ Options:
50
+ -p --puppet-version=VERSION Puppet version (Valid: #{VALID_PUPPET_VERSIONS.join(', ')})
51
+ -r --repo-type=REPO_TYPE Puppet debian repo type (Valid: #{VALID_REPO_TYPES.join(', ')})
52
+ -c --codename=DEBIAN_CODENAME Debian codename to deliver to (Valid: #{VALID_CODENAMES.join(', ')})
53
+
54
+ -v --verbose Be verbose
55
+ -h --help Show this help
56
+ --version Show version
57
+ DOCOPT
58
+
59
+ def initialize(argv)
60
+ @log_level = Logger::INFO
61
+ @user_options = parse_options(argv)
62
+ @verbose = ''
63
+ @verbose = '--verbose' if @user_options['--verbose']
64
+
65
+ @script_name = File.basename($PROGRAM_NAME)
66
+ @version_string = "#{@script_name} version #{AptStageArtifacts::VERSION}"
67
+ if @user_options['--version'] # rubocop:disable Style/GuardClause
68
+ puts @version_string
69
+ exit 0
70
+ end
71
+ end
72
+
73
+ def parse_options(argv = nil)
74
+ Docopt.docopt(DOCUMENTATION, { argv: argv })
75
+ rescue Docopt::Exit => e
76
+ puts e.message
77
+ exit 1
78
+ end
79
+
80
+ def run
81
+ logger.info @version_string
82
+ @puppet_version = @user_options['--puppet-version']
83
+ fatal "Invalid Puppet version '#{@puppet_version}'" unless
84
+ VALID_PUPPET_VERSIONS.include? @puppet_version
85
+
86
+ @repo_type = @user_options['--repo-type']
87
+ fatal "Invalid repo type '#{@repo_type}'" unless VALID_REPO_TYPES.include? @repo_type
88
+
89
+ @codename = @user_options['--codename']
90
+ fatal "Invalid codename '#{@codename}'" unless VALID_CODENAMES.include? @codename
91
+
92
+ freight_config_file = "/etc/freight.conf.d/puppet_#{@puppet_version}_#{@repo_type}.conf"
93
+ fatal "Cannot read '#{freight_config_file} on #{`hostname`.chomp}." unless
94
+ File.readable?(freight_config_file)
95
+
96
+ logger.info "Using '#{freight_config_file}'"
97
+ deb_artifacts = @user_options['<deb_file>']
98
+
99
+ Dir.mktmpdir do |temporary_directory|
100
+ deb_artifacts.each do |artifact_path|
101
+ logger.info "Adding #{artifact_path} to puppet_#{@puppet_version}_#{@repo_type}"
102
+ deb_artifact_path = artifact_path
103
+ if deb_artifact_path.start_with?('https://', 'http://')
104
+ download_data = URI.parse(deb_artifact_path).open
105
+ download_path = File.join(temporary_directory, download_data.base_uri.to_s.split('/').last)
106
+ IO.copy_stream(download_data, download_path)
107
+ deb_artifact_path = download_path
108
+ end
109
+
110
+ freight_add_command = %W[
111
+ sudo --user=jenkins --set-home
112
+ #{FREIGHT_COMMAND} add #{@verbose} --conf="#{freight_config_file}"
113
+ #{deb_artifact_path} "apt/#{@codename}"
114
+ ].join(' ')
115
+
116
+ %x(#{freight_add_command})
117
+ fatal "#{freight_add_command} failed." unless $CHILD_STATUS.success?
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,188 @@
1
+ require 'version'
2
+ require 'mixins/logging'
3
+
4
+ require 'English'
5
+ require 'docopt'
6
+ require 'fileutils'
7
+ require 'open-uri'
8
+ require 'tempfile'
9
+ require 'yaml'
10
+
11
+ class AptStageArtifacts
12
+ include Logging
13
+
14
+ DOCUMENTATION = <<~DOCOPT
15
+ Stages .deb artifacts into a freight library from a Vanagon or packaging directory.
16
+
17
+ Usage:
18
+ apt-stage-artifacts
19
+ apt-stage-artifacts <top_directory>
20
+ DOCOPT
21
+
22
+ def initialize(argv)
23
+ @script_name = File.basename($PROGRAM_NAME)
24
+ @log_level = Logger::INFO
25
+ @user_options = parse_options(argv)
26
+ @version_string = "#{@script_name} version #{AptStageArtifacts::VERSION}"
27
+
28
+ @artifacts_tarball_name = 'artifacts.tgz'
29
+ @local_tarball_path = File.join(Dir.pwd, @artifacts_tarball_name)
30
+
31
+ @builder_data_yaml = 'https://raw.githubusercontent.com/puppetlabs/build-data/release/builder_data.yaml'
32
+
33
+ @manifest_file_name = 'apt_stage.manifest'
34
+ @remote_staging_command = 'apt-stage-from-tarball'
35
+ end
36
+
37
+ def parse_options(argv = nil)
38
+ Docopt.docopt(DOCUMENTATION, { argv: argv })
39
+ rescue Docopt::Exit => e
40
+ puts e.message
41
+ exit 1
42
+ end
43
+
44
+ def run
45
+ logger.info @version_string
46
+ parse_repo_type
47
+ load_builder_data_yaml
48
+ collect_artifacts
49
+ create_remote_staging_directory
50
+ generate_manifest
51
+ create_tarball
52
+ ship_tarball_to_staging_server
53
+ invoke_remote_staging
54
+ cleanup_tarball
55
+ logger.info 'Deb artifact staging successful.'
56
+ end
57
+
58
+ private
59
+
60
+ # Locate all the local debian artifacts to be added to the freight repo.
61
+ # For historical reasons, there is an implied format of:
62
+ # <top_directory>/<debian codename>/<artifact>.deb
63
+ #
64
+ # We rely on <debian codename> being in the first position below <top_directory>
65
+ # in order to properly classify the artifacts. This is definitely brittle.
66
+ def collect_artifacts
67
+ if !@user_options['<top_directory>'].to_s.empty?
68
+ @top_directory = @user_options['<top_directory>']
69
+ elsif File.directory?('./output/deb')
70
+ @top_directory = './output/deb'
71
+ elsif File.directory?('./pkg/deb')
72
+ @top_directory = './pkg/deb'
73
+ else
74
+ logger.error 'Cannot find .deb artifacts. Neither pkg/deb nor output/deb exist.'
75
+ exit 1
76
+ end
77
+
78
+ logger.info "Searching for .deb packages in #{@top_directory}"
79
+ Dir.chdir(@top_directory) do
80
+ @debian_artifacts = Dir['*/*.deb']
81
+ end
82
+ return unless @debian_artifacts.empty?
83
+
84
+ logger.error 'No .deb files found. Nothing to do.'
85
+ exit 0
86
+ end
87
+
88
+ # Create a working directory on the staging server that can then be used to deliver
89
+ # to freight repos.
90
+ def create_remote_staging_directory
91
+ @remote_staging_directory = %x(ssh #{@staging_server} /bin/mktemp --directory).chomp
92
+ fatal "/bin/mktemp --directory on #{@staging_server} failed." unless $CHILD_STATUS.success?
93
+
94
+ # We need to give jenkins read access to the remote_staging_directory
95
+ %x(ssh #{@staging_server} /bin/chmod 755 #{@remote_staging_directory})
96
+ unless $CHILD_STATUS.success?
97
+ fatal "/bin/chmod 755 #{@remote_staging_directory} on #{@staging_server} failed."
98
+ end
99
+
100
+ @remote_tarball_path = File.join(@remote_staging_directory, @artifacts_tarball_name)
101
+ end
102
+
103
+ # Load details from the builder_data.yaml on github
104
+ # Keep only the bare necessities. Right now, that's just the staging server name.
105
+ def load_builder_data_yaml
106
+ yaml_content = URI.parse(@builder_data_yaml).open(&:read)
107
+ yaml_data = YAML.safe_load(yaml_content)
108
+ @staging_server = yaml_data['staging_server']
109
+ logger.info "Artifacts will be staged on '#{@staging_server}'."
110
+ end
111
+
112
+ # A manifest is a staging to-do list. For each '.deb' file create a colon-delimited line
113
+ # containing:
114
+ # <.deb filename>:<puppet-version>:<repo-type>:<debian-codename>
115
+ def generate_manifest
116
+ manifest_temporary_file = Tempfile.new('apt_stage_artifacts')
117
+
118
+ @debian_artifacts.each do |deb_file_path|
119
+ # Ug. We only know the codename because it happens to be the first element
120
+ # of the file path.
121
+ debian_codename = deb_file_path.split('/').first
122
+ manifest_temporary_file.printf(
123
+ "%s:%s:%s:%s\n",
124
+ deb_file_path, @puppet_version, @repo_type, debian_codename
125
+ )
126
+ end
127
+
128
+ manifest_temporary_file.close
129
+ manifest_path = File.join(@top_directory, @manifest_file_name)
130
+ FileUtils.mv(manifest_temporary_file.path, manifest_path)
131
+ logger.info("Staging manifest written to '#{manifest_path}'")
132
+ end
133
+
134
+ # Create a tarball that contains the staging manifest and the associated deb files.
135
+ def create_tarball
136
+ tarball_create_command = %W[
137
+ tar --create --gzip --file #{@local_tarball_path} --directory=#{@top_directory}
138
+ ].join(' ')
139
+ %x(#{tarball_create_command} #{@manifest_file_name} #{@debian_artifacts.join(' ')})
140
+ unless $CHILD_STATUS.success?
141
+ fatal "#{tarball_create_command} #{@manifest_file_name} #{@debian_artifacts.join(' ')} failed."
142
+ end
143
+
144
+ logger.info("'#{@local_tarball_path}' created.")
145
+ end
146
+
147
+ def cleanup_tarball
148
+ File.delete @local_tarball_path
149
+ end
150
+
151
+ # The target apt repository is described in the REPO_NAME environment variable.
152
+ # A better solution is called for, perhaps as part of vanagon config or as a commandline
153
+ # parameter.
154
+ def parse_repo_type
155
+ case ENV['REPO_NAME']
156
+ when /\A\z/, nil
157
+ fatal "The environment variable 'REPO_NAME', containing the target apt repo, is unset.\n" \
158
+ "It should be set to something like 'puppet7' or 'puppet7-nightly'."
159
+ when /\Apuppet(\d+)\z/
160
+ @puppet_version = Regexp.last_match(1)
161
+ @repo_type = 'stable'
162
+ when /\Apuppet(\d+)-nightly\z/
163
+ @puppet_version = Regexp.last_match(1)
164
+ @repo_type = 'nightly'
165
+ else
166
+ fatal "The environment variable 'REPO_NAME' is set to '#{ENV['REPO_NAME']}'\n" \
167
+ "This is not a recognized setting.\n" \
168
+ "It should be set to something like 'puppet7' or 'puppet7-nightly'."
169
+ end
170
+ end
171
+
172
+ # Send the tarball over to the staging server for further processing with freight.
173
+ def ship_tarball_to_staging_server
174
+ logger.info "Sending '#{@local_tarball_path}' to '#{@staging_server}:#{@remote_tarball_path}'"
175
+ %x(scp #{@local_tarball_path} #{@staging_server}:#{@remote_tarball_path})
176
+ return if $CHILD_STATUS.success?
177
+
178
+ fatal "'scp #{@local_tarball_path} #{@staging_server}:#{@remote_tarball_path}' failed."
179
+ end
180
+
181
+ # Invoke the remote staging of the sent artifacts
182
+ def invoke_remote_staging
183
+ %x(ssh #{@staging_server} #{@remote_staging_command} #{@remote_tarball_path})
184
+ return if $CHILD_STATUS.success?
185
+
186
+ fatal "'ssh #{@staging_server} #{@remote_staging_command} #{@remote_tarball_path}' failed."
187
+ end
188
+ end
@@ -0,0 +1,110 @@
1
+ require 'version'
2
+ require 'mixins/logging'
3
+
4
+ require 'English'
5
+ require 'mkmf'
6
+
7
+ class AptStageFromTarball
8
+ include Logging
9
+
10
+ def initialize(tarball_path = nil)
11
+ @tarball_path = tarball_path
12
+ @manifest_file_name = 'apt_stage.manifest'
13
+ @log_level = Logger::INFO
14
+ @repo_types_updated = {}
15
+
16
+ @script_name = File.basename($PROGRAM_NAME)
17
+ @version_string = "#{@script_name} version #{AptStageArtifacts::VERSION}"
18
+
19
+ # External commands
20
+ @apt_add_to_freight_library_command = 'apt-add-to-freight-library'
21
+ @apt_update_freight_cache_command = 'apt-update-freight-cache'
22
+ end
23
+
24
+ def run
25
+ logger.info @version_string
26
+ check_for_external_commands
27
+ extract_tarball
28
+ add_artifacts_to_freight_library
29
+ update_freight_cache
30
+ # cleanup_tarball_directory
31
+ logger.info 'Freight artifact staging successful.'
32
+ end
33
+
34
+ private
35
+
36
+ def check_for_external_commands
37
+ %W[#{@apt_add_to_freight_library_command}
38
+ #{@apt_update_freight_cache_command}].each do |external_command|
39
+ fatal "'#{external_command}' is missing or not executable" unless
40
+ find_executable external_command
41
+ end
42
+ end
43
+
44
+ def extract_tarball
45
+ fatal "'#{@tarball_path}' does not exist." unless File.exist?(@tarball_path)
46
+ fatal "'#{@tarball_path}' is not a regular file." unless File.file?(@tarball_path)
47
+ fatal "'#{@tarball_path}' is not readable." unless File.readable?(@tarball_path)
48
+
49
+ @tarball_directory = File.dirname(@tarball_path)
50
+
51
+ tarball_extract_command = %W[
52
+ tar --directory #{@tarball_directory} --extract --gzip --file #{@tarball_path}
53
+ ].join(' ')
54
+ %x(#{tarball_extract_command})
55
+
56
+ unless $CHILD_STATUS.success?
57
+ fatal "#{tarball_extract_command} failed."
58
+ end
59
+
60
+ @manifest_path = File.join(@tarball_directory, @manifest_file_name)
61
+
62
+ fatal "Staging manifest file '#{@manifest_path}' is missing or unreadable" unless
63
+ File.readable?(@manifest_path)
64
+
65
+ logger.info("'#{@tarball_path}' extracted.")
66
+ end
67
+
68
+ # Read through the staging manifest, adding each line into the correct apt freight
69
+ # library.
70
+ def add_artifacts_to_freight_library
71
+ Dir.chdir(@tarball_directory) do
72
+ File.readlines(@manifest_path).each do |line|
73
+ line.chomp!
74
+ debian_filename, puppet_version, repo_type, codename = line.split(':')
75
+ add_to_freight_library_command = %W[
76
+ #{@apt_add_to_freight_library_command}
77
+ --verbose
78
+ --puppet-version=#{puppet_version}
79
+ --repo-type=#{repo_type}
80
+ --codename=#{codename}
81
+ #{File.join(Dir.pwd, debian_filename)}
82
+ ].join(' ')
83
+
84
+ %x(#{add_to_freight_library_command})
85
+ fatal "#{add_to_freight_library_command} failed." unless $CHILD_STATUS.success?
86
+
87
+ # Keep a hash, key by puppet_version, of repo_types so that we can
88
+ # efficiently build out the freight caches later.
89
+ @repo_types_updated[puppet_version] = [] unless @repo_types_updated.key?(puppet_version)
90
+ @repo_types_updated[puppet_version] |= [repo_type]
91
+ end
92
+ end
93
+ end
94
+
95
+ def update_freight_cache
96
+ @repo_types_updated.each do |puppet_version, repo_types|
97
+ repo_types.each do |repo_type|
98
+ update_freight_cache_command = %W[
99
+ #{@apt_update_freight_cache_command}
100
+ --verbose
101
+ --puppet-version=#{puppet_version}
102
+ --repo-type=#{repo_type}
103
+ ].join(' ')
104
+
105
+ %x(#{update_freight_cache_command})
106
+ fatal "#{update_freight_cache_command} failed." unless $CHILD_STATUS.success?
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,102 @@
1
+ require 'version'
2
+ require 'mixins/logging'
3
+
4
+ require 'English'
5
+ require 'docopt'
6
+ require 'open-uri'
7
+ require 'tempfile'
8
+ require 'yaml'
9
+
10
+ ##
11
+ ## This is a wrapper for https://github.com/freight-team/freight, which creates APT repos
12
+ ## from deb files. This triggers the 'freight cache' command which creates a proper
13
+ ## APT repo (freight calls it a 'cache'), including signing, from a tree of debian
14
+ ## files (freight calls it a 'library').
15
+ ##
16
+ ## In our case, we maintain a set of debian repos, based on puppet version and a repo-type
17
+ ## 'nightly', 'archive' and 'stable'.
18
+ ##
19
+ ## freight provides signing services, so this script needs to exist on a machine where GPG
20
+ ## signing has been set up.
21
+ ##
22
+ ## Example usages:
23
+ ## apt-update-freight-cache --puppet-version=7 --repo-type=stable
24
+ ## apt-update-freight-cache --puppet-version=7 --repo-type=nightly
25
+
26
+ class AptUpdateFreightCache
27
+ include Logging
28
+
29
+ FREIGHT_COMMAND = '/usr/bin/freight'
30
+ VALID_PUPPET_VERSIONS = %w[6 7 8]
31
+ VALID_REPO_TYPES = %w[archive nightly stable]
32
+
33
+ DOCUMENTATION = <<~DOCOPT
34
+ Generates a freight cache from a freight library.
35
+
36
+ Usage:
37
+ apt-update-freight-cache [--verbose] --puppet-version=VERSION --repo-type=REPO_TYPE
38
+ apt-update-freight-cache --help
39
+ apt-update-freight-cache --version
40
+
41
+ Options:
42
+ -p --puppet-version=VERSION Puppet version (Valid: #{VALID_PUPPET_VERSIONS.join(', ')})
43
+ -r --repo-type=REPO_TYPE Puppet debian repo type (Valid: #{VALID_REPO_TYPES.join(', ')})
44
+
45
+ -v --verbose Be verbose
46
+ -h --help Show this help
47
+ --version Show version
48
+ DOCOPT
49
+
50
+ def initialize(argv)
51
+ @log_level = Logger::INFO
52
+ @user_options = parse_options(argv)
53
+ @verbose = ''
54
+ @verbose = '--verbose' if @user_options['--verbose']
55
+ @builder_data_yaml = 'https://raw.githubusercontent.com/puppetlabs/build-data/release/builder_data.yaml'
56
+ @script_name = File.basename($PROGRAM_NAME)
57
+ @version_string = "#{@script_name} version #{AptStageArtifacts::VERSION}"
58
+ if @user_options['--version'] # rubocop:disable Style/GuardClause
59
+ puts @version_string
60
+ exit 0
61
+ end
62
+ end
63
+
64
+ def parse_options(argv = nil)
65
+ Docopt.docopt(DOCUMENTATION, { argv: argv })
66
+ rescue Docopt::Exit => e
67
+ puts e.message
68
+ exit 1
69
+ end
70
+
71
+ def run
72
+ logger.info @version_string
73
+ load_builder_data_yaml
74
+ @puppet_version = @user_options['--puppet-version']
75
+ fatal "Invalid Puppet version '#{@puppet_version}'" unless
76
+ VALID_PUPPET_VERSIONS.include? @puppet_version
77
+
78
+ @repo_type = @user_options['--repo-type']
79
+ fatal "Invalid repo type '#{@repo_type}'" unless VALID_REPO_TYPES.include? @repo_type
80
+
81
+ freight_config_file = "/etc/freight.conf.d/puppet_#{@puppet_version}_#{@repo_type}.conf"
82
+ fatal "Cannot read '#{freight_config_file} on #{`hostname`.chomp}." unless
83
+ File.readable?(freight_config_file)
84
+
85
+ logger.info "Using '#{freight_config_file}'"
86
+ freight_cache_command = %W[
87
+ sudo --user=jenkins --set-home
88
+ /usr/local/bin/freight-cache-wrapper #{@verbose}
89
+ --conf=#{freight_config_file} --gpg-key=#{@gpg_key}
90
+ ].join(' ')
91
+ %x(#{freight_cache_command})
92
+ fatal "#{tarball_extract_command} failed." unless $CHILD_STATUS.success?
93
+ end
94
+
95
+ # Load details from the builder_data.yaml on github
96
+ # Keep only the bare necessities. Right now, that's just the GPG key
97
+ def load_builder_data_yaml
98
+ yaml_content = URI.parse(@builder_data_yaml).open(&:read)
99
+ yaml_data = YAML.safe_load(yaml_content)
100
+ @gpg_key = yaml_data['gpg_key']
101
+ end
102
+ end
@@ -0,0 +1,29 @@
1
+ # Handles all logging output for Tefoji
2
+
3
+ module Logging
4
+ require 'logger'
5
+
6
+ # A cheap hack that will instantiate a logger if necessary before sending a log
7
+ # message. This is the normal interface to logging from outside.
8
+ def logger
9
+ Logging.logger(@log_level)
10
+ end
11
+
12
+ # Another cheap hack to wrap 'logger' above. Called as 'fatal' rather than 'logger.fatal'
13
+ # directly, does the exit for us so we can be lazy.
14
+ def fatal(message)
15
+ logger.fatal(message)
16
+ exit 1
17
+ end
18
+
19
+ # Do the actual logging by wrapping Logger gem.
20
+ def self.logger(log_level)
21
+ return @logger unless @logger.nil?
22
+
23
+ @logger = Logger.new($stderr, progname: File.basename($PROGRAM_NAME), level: log_level)
24
+ @logger.formatter = proc do |severity, _, script_name, message|
25
+ "#{script_name}: #{severity} #{message}\n"
26
+ end
27
+ return @logger
28
+ end
29
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ class AptStageArtifacts
2
+ VERSION = '0.4.0'
3
+ end
metadata ADDED
@@ -0,0 +1,185 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apt_stage_artifacts
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Puppet Release Engineering
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-04-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pry
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry-byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.9'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.9'
111
+ - !ruby/object:Gem::Dependency
112
+ name: docopt
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description:
126
+ email:
127
+ - release@puppet.com
128
+ executables:
129
+ - apt-add-to-freight-library
130
+ - apt-stage-artifacts
131
+ - apt-stage-from-tarball
132
+ - apt-update-freight-cache
133
+ extensions: []
134
+ extra_rdoc_files: []
135
+ files:
136
+ - ".gitignore"
137
+ - ".rspec"
138
+ - ".rubocop.yml"
139
+ - CHANGELOG.md
140
+ - CODEOWNERS
141
+ - DEVELOPMENT.md
142
+ - Gemfile
143
+ - LICENSE.txt
144
+ - README.md
145
+ - Rakefile
146
+ - apt_stage_artifacts.gemspec
147
+ - bin/console
148
+ - bin/setup
149
+ - exe/apt-add-to-freight-library
150
+ - exe/apt-stage-artifacts
151
+ - exe/apt-stage-from-tarball
152
+ - exe/apt-update-freight-cache
153
+ - lib/apt_add_to_freight_library.rb
154
+ - lib/apt_stage_artifacts.rb
155
+ - lib/apt_stage_from_tarball.rb
156
+ - lib/apt_update_freight_cache.rb
157
+ - lib/mixins/logging.rb
158
+ - lib/version.rb
159
+ homepage: https://github.com/puppetlabs/apt_stage_artifacts
160
+ licenses: []
161
+ metadata:
162
+ allowed_push_host: https://rubygems.org
163
+ homepage_uri: https://github.com/puppetlabs/apt_stage_artifacts
164
+ source_code_uri: https://github.com/puppetlabs/apt_stage_artifacts
165
+ changelog_uri: https://github.com/puppetlabs/apt_stage_artifacts/CHANGELOG.md
166
+ post_install_message:
167
+ rdoc_options: []
168
+ require_paths:
169
+ - lib
170
+ required_ruby_version: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: 2.5.0
175
+ required_rubygems_version: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ requirements: []
181
+ rubygems_version: 3.1.4
182
+ signing_key:
183
+ specification_version: 4
184
+ summary: Stages .deb artifacts to a remote freight repository
185
+ test_files: []