apt_stage_artifacts 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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: []