vtasks 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,54 @@
1
+ module Vtasks
2
+ class Docker
3
+ class Image
4
+ # Docker Build class
5
+ class Build
6
+ # Include utility modules
7
+ require 'vtasks/utils/git'
8
+ include Vtasks::Utils::Git
9
+ require 'vtasks/utils/output'
10
+ include Vtasks::Utils::Output
11
+
12
+ attr_reader :image, :path, :build_date, :build_tag
13
+
14
+ def initialize(image, path, args={})
15
+ @image ||= image
16
+ @path ||= path
17
+ @build_date ||= args.fetch(:build_date)
18
+ @build_tag ||= args.fetch(:build_tag)
19
+
20
+ @cmd = 'docker image build'
21
+ end
22
+
23
+ def without_arguments
24
+ info "Pulling #{image}" # to speed up the building process
25
+ system "docker pull #{image}" unless ENV['DOCKER_NO_CACHE']
26
+
27
+ info "Building #{image}:#{build_tag}"
28
+ system "#{@cmd} -t #{image}:#{build_tag} #{path}"
29
+
30
+ if $?.exitstatus != 0
31
+ error 'Build command failed!'
32
+ abort
33
+ end
34
+ end
35
+
36
+ def with_arguments
37
+ build_args = {
38
+ 'BUILD_DATE' => build_date,
39
+ 'VERSION' => build_tag,
40
+ 'VCS_URL' => git_url,
41
+ 'VCS_REF' => git_commit
42
+ }
43
+
44
+ build_args.map do |key, value|
45
+ @cmd += " --build-arg #{key}=#{value}"
46
+ end
47
+
48
+ without_arguments
49
+ end
50
+
51
+ end # class Build
52
+ end # class Image
53
+ end # class Docker
54
+ end # module Vtasks
@@ -0,0 +1,17 @@
1
+ module Vtasks
2
+ class Docker
3
+ class Image
4
+ # Docker Push class
5
+ class Push
6
+ # Include utility modules
7
+ require 'vtasks/utils/output'
8
+ include Vtasks::Utils::Output
9
+
10
+ def initialize(image, tag)
11
+ info "Pushing #{image}:#{tag}"
12
+ system "docker image push #{image}:#{tag}"
13
+ end
14
+ end # class Push
15
+ end # class Image
16
+ end # class Docker
17
+ end # module Vtasks
@@ -0,0 +1,17 @@
1
+ module Vtasks
2
+ class Docker
3
+ class Image
4
+ # Docker Tag module
5
+ class Tag
6
+ # Include utility modules
7
+ require 'vtasks/utils/output'
8
+ include Vtasks::Utils::Output
9
+
10
+ def initialize(image, oldtag, newtag)
11
+ info "Tagging #{image}:#{newtag}"
12
+ system "docker image tag #{image}:#{oldtag} #{image}:#{newtag}"
13
+ end
14
+ end # class Tag
15
+ end # class Image
16
+ end # class Docker
17
+ end # module Vtasks
@@ -0,0 +1,83 @@
1
+ module Vtasks
2
+ class Docker
3
+ # Docker Image class
4
+ class Image
5
+ # Include utility modules
6
+ require 'vtasks/utils/git'
7
+ include Vtasks::Utils::Git
8
+ require 'vtasks/utils/semver'
9
+ include Vtasks::Utils::Semver
10
+
11
+ # Include utility classes
12
+ require 'vtasks/docker/image/build'
13
+ require 'vtasks/docker/image/push'
14
+ require 'vtasks/docker/image/tag'
15
+
16
+ attr_reader :image, :path, :has_build_args, :tags
17
+
18
+ def initialize(image, path, args = {})
19
+ @image ||= image
20
+ @path ||= path
21
+ @has_build_args ||= args.fetch(:has_build_args, false)
22
+ end
23
+
24
+ def tags
25
+ major, minor, patch = [
26
+ semver[:major],
27
+ semver[:minor],
28
+ semver[:patch]
29
+ ].freeze
30
+ @tags = [
31
+ "#{major}.#{minor}.#{patch}",
32
+ "#{major}.#{minor}",
33
+ "#{major}",
34
+ 'latest'
35
+ ]
36
+ end
37
+
38
+ # Compose build date
39
+ def build_date
40
+ @build_date ||= ::Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
41
+ end
42
+
43
+ # Compose build tag
44
+ def build_tag
45
+ @build_tag ||= gitver.to_s
46
+ end
47
+
48
+ # Build image
49
+ def build
50
+ args = {
51
+ build_date: build_date,
52
+ build_tag: build_tag
53
+ }
54
+ build = Vtasks::Docker::Image::Build.new(image, path, args)
55
+ if has_build_args
56
+ build.with_arguments
57
+ else
58
+ build.without_arguments
59
+ end
60
+ end
61
+
62
+ # Tag image
63
+ def tag
64
+ tags.each do |tag|
65
+ Vtasks::Docker::Image::Tag.new(image, build_tag, tag)
66
+ end
67
+ end
68
+
69
+ # Build image with tags
70
+ def build_with_tags
71
+ build
72
+ tag
73
+ end
74
+
75
+ # Push image
76
+ def push
77
+ tags.each do |tag|
78
+ Vtasks::Docker::Image::Push.new(image, tag)
79
+ end
80
+ end
81
+ end # class Image
82
+ end # class Docker
83
+ end # module Vtasks
@@ -0,0 +1,124 @@
1
+ module Vtasks
2
+ require 'rake/tasklib'
3
+
4
+ # Docker tasks
5
+ class Docker < ::Rake::TaskLib
6
+ # Include utility modules
7
+ require 'vtasks/utils/output'
8
+ include Vtasks::Utils::Output
9
+ require 'vtasks/utils/system'
10
+ include Vtasks::Utils::System
11
+
12
+ # Include utility classes
13
+ require 'vtasks/docker/image'
14
+
15
+ attr_reader :args, :repo
16
+
17
+ def initialize(args = {})
18
+ @args ||= args
19
+ @repo ||= args.fetch(:repo)
20
+
21
+ check_docker
22
+ define_tasks
23
+ end
24
+
25
+ def define_tasks
26
+ namespace :docker do
27
+ list_images
28
+ garbage_collect
29
+ tasks
30
+
31
+ dockerfiles.each do |dockerfile|
32
+ path = File.basename(dockerfile)
33
+ add_namespace("#{repo}/#{path}", path)
34
+ end # dockerfiles.each
35
+ end # namespace :docker
36
+ end # def define_tasks
37
+
38
+ # Image namespace
39
+ def add_namespace(image, path)
40
+ namespace path.to_sym do |_args|
41
+ require 'rspec/core/rake_task'
42
+ ::RSpec::Core::RakeTask.new(:spec) do |task|
43
+ task.pattern = "#{path}/spec/*_spec.rb"
44
+ end
45
+
46
+ docker_image = Vtasks::Docker::Image.new(image, path, args)
47
+
48
+ lint_image(path)
49
+
50
+ desc 'Build and tag docker image'
51
+ task :build do
52
+ docker_image.build_with_tags
53
+ end
54
+
55
+ desc 'Publish docker image'
56
+ task :push do
57
+ docker_image.push
58
+ end
59
+ end
60
+ end
61
+
62
+ # Tasks
63
+ def tasks
64
+ # Run tasks one by one for all images
65
+ [:spec, :lint].each { |task_name| run_task(task_name) }
66
+ # Run tasks in parallel for all images
67
+ [:build, :push].each { |task_name| run_task_parallel(task_name) }
68
+ end
69
+
70
+ # Run a task for all images
71
+ def run_task(name)
72
+ desc "Run #{name} for all images in repository"
73
+ task name => dockerfiles
74
+ .collect { |image| "docker:#{File.basename(image)}:#{name}" }
75
+ end
76
+
77
+ # Run a task for all images in parallel
78
+ def run_task_parallel(name)
79
+ desc "Run #{name} for all images in repository in parallel"
80
+ multitask name => dockerfiles
81
+ .collect { |image| "docker:#{File.basename(image)}:#{name}" }
82
+ end
83
+
84
+ # List all folders containing Dockerfiles
85
+ def dockerfiles
86
+ @dockerfiles = Dir.glob('*').select do |dir|
87
+ File.directory?(dir) && File.exist?("#{dir}/Dockerfile")
88
+ end
89
+ end
90
+
91
+ # Check Docker is installed
92
+ def check_docker
93
+ task :docker do
94
+ raise 'These tasks require docker to be installed' unless command? 'docker'
95
+ end
96
+ end
97
+
98
+ # List all images
99
+ def list_images
100
+ desc 'List all Docker images'
101
+ task :list do
102
+ info dockerfiles.map { |image| File.basename(image) }
103
+ end
104
+ end
105
+
106
+ # Lint image
107
+ def lint_image(path)
108
+ desc 'Run Hadolint against the Dockerfile'
109
+ task :lint do
110
+ dockerfile = "#{path}/Dockerfile"
111
+ info "Running Hadolint to check the style of #{dockerfile}"
112
+ system "docker container run --rm -i lukasmartinelli/hadolint hadolint --ignore DL3008 --ignore DL3013 - < #{dockerfile}"
113
+ end
114
+ end
115
+
116
+ # Garbage collect
117
+ def garbage_collect
118
+ desc 'Garbage collect unused docker data'
119
+ task :gc do
120
+ system 'docker system prune --all --force'
121
+ end
122
+ end
123
+ end # class Docker
124
+ end # module Vtasks
@@ -0,0 +1,65 @@
1
+ module Vtasks
2
+ require 'rake/tasklib'
3
+
4
+ # Lint tasks
5
+ class Lint < ::Rake::TaskLib
6
+ attr_reader :file_list
7
+
8
+ def initialize(options = {})
9
+ @file_list ||= options.fetch(:file_list, FileList['{lib,spec}/**/*.rb'])
10
+ define_tasks
11
+ end
12
+
13
+ # Define tasks
14
+ def define_tasks
15
+ desc 'Check for code smells with Reek and Rubocop'
16
+ task lint: ['lint:reek', 'lint:rubocop']
17
+
18
+ namespace :lint do
19
+ rubocop
20
+ reek
21
+ rubycritic
22
+ end
23
+ end
24
+
25
+ # RuboCop
26
+ def rubocop
27
+ begin
28
+ require 'rubocop/rake_task'
29
+ rescue LoadError
30
+ nil # Might be in a group that is not installed
31
+ end
32
+ desc 'Run RuboCop on the tasks and lib directory'
33
+ ::RuboCop::RakeTask.new(:rubocop) do |task|
34
+ task.patterns = file_list
35
+ task.options = ['--display-cop-names', '--extra-details']
36
+ end
37
+ end
38
+
39
+ # Reek
40
+ def reek
41
+ begin
42
+ require 'reek/rake/task'
43
+ rescue LoadError
44
+ nil # Might be in a group that is not installed
45
+ end
46
+ ::Reek::Rake::Task.new do |task|
47
+ task.source_files = file_list
48
+ task.fail_on_error = false
49
+ task.reek_opts = '--wiki-links --color'
50
+ end
51
+ end
52
+
53
+ # Ruby Critic
54
+ def rubycritic
55
+ begin
56
+ require 'rubycritic/rake_task'
57
+ rescue LoadError
58
+ nil # Might be in a group that is not installed
59
+ end
60
+ ::RubyCritic::RakeTask.new do |task|
61
+ task.paths = file_list
62
+ end
63
+ end
64
+ end # class Lint
65
+ end # module Vtasks
@@ -0,0 +1,212 @@
1
+ module Vtasks
2
+ require 'rake/tasklib'
3
+
4
+ # Puppet tasks
5
+ class Puppet < ::Rake::TaskLib
6
+ # Include utility modules
7
+ require 'vtasks/utils/git'
8
+ include Vtasks::Utils::Git
9
+ require 'vtasks/utils/output'
10
+ include Vtasks::Utils::Output
11
+
12
+ require 'json'
13
+ require 'open-uri'
14
+ require 'yaml'
15
+
16
+ attr_reader :exclude_paths
17
+
18
+ def initialize(options = {})
19
+ # Fix for namespaced :syntax task
20
+ task syntax: ['puppet:syntax']
21
+
22
+ namespace :puppet do
23
+ begin
24
+ require 'r10k/cli'
25
+ require 'r10k/puppetfile'
26
+ require 'puppet_forge'
27
+ require 'puppetlabs_spec_helper/rake_tasks' # ORDER IS VERY IMPORTANT BECAUSE IT OVERRIDES A LOT OF OTHER TASKS; AS OF NOW IT NEEDS TO BE AFTER `r10k` and `puppet_forge` (BECAUSE OF FAST_GETTEXT INITIALIZATION) BUT BEFORE puppet-strings (BECAUSE ERROR: `Don't know how to build task 'spec_prep'`)
28
+ require 'metadata-json-lint/rake_task'
29
+ require 'puppet-syntax/tasks/puppet-syntax'
30
+ require 'puppet-lint/tasks/puppet-lint'
31
+ require 'puppet-strings/tasks'
32
+ require 'puppet_blacksmith/rake_tasks'
33
+ rescue LoadError
34
+ nil # Might be in a group that is not installed
35
+ end
36
+
37
+ @exclude_paths ||= options.fetch(
38
+ :exclude_paths,
39
+ [
40
+ 'bundle/**/*',
41
+ 'modules/**/*',
42
+ 'pkg/**/*',
43
+ 'spec/**/*',
44
+ 'tmp/**/*',
45
+ 'vendor/**/*'
46
+ ]
47
+ )
48
+
49
+ define_tasks
50
+ end # namespace :puppet
51
+ end
52
+
53
+ def define_tasks
54
+ ::PuppetLint::RakeTask.new :lint do |config|
55
+ config.relative = true
56
+ config.with_context = true
57
+ config.fail_on_warnings = true
58
+ config.ignore_paths = exclude_paths
59
+ config.disable_checks = [
60
+ '140chars'
61
+ ]
62
+ end
63
+
64
+ # Puppet syntax tasks
65
+ ::PuppetSyntax.exclude_paths = exclude_paths
66
+
67
+ desc 'Run syntax, lint, and spec tests'
68
+ task test: [
69
+ :metadata_lint,
70
+ :syntax,
71
+ :lint,
72
+ :unit
73
+ ]
74
+
75
+ desc 'Run unit tests'
76
+ task unit: [
77
+ :r10k_install_modules,
78
+ :spec_prep,
79
+ :spec_standalone
80
+ ]
81
+
82
+ desc 'Run acceptance tests'
83
+ task integration: [
84
+ :spec_prep,
85
+ :beaker
86
+ ]
87
+
88
+ desc 'Clean all test files'
89
+ task clean: [:spec_clean]
90
+
91
+ desc 'Use R10K to download all modules'
92
+ task :r10k_install_modules do
93
+ r10k_install_modules
94
+ end
95
+
96
+ desc 'Generates a new .fixtures.yml from a Puppetfile'
97
+ task :generate_fixtures do
98
+ generate_fixtures
99
+ end
100
+
101
+ desc 'Print outdated Puppetfile modules'
102
+ task :puppetfile_inspect do
103
+ check_puppetfile_versions
104
+ end
105
+ end # def define_tasks
106
+
107
+ def puppetfile
108
+ @puppetfile ||= ::R10K::Puppetfile.new(pwd)
109
+ end
110
+
111
+ def check_puppetfile
112
+ puppetfile.load
113
+ error 'Puppetfile was not found or is empty!' if puppetfile.modules.empty?
114
+ end
115
+
116
+ def r10k_install_modules
117
+ info 'Updating modules with R10K'
118
+ ::R10K::CLI.command.run(%w(puppetfile install --force --verbose))
119
+ rescue SystemExit # because R10K::CLI.command.run calls `exit 0`
120
+ info 'Modules have been updated'
121
+ end
122
+
123
+ def generate_fixtures
124
+ info 'Generating fixtures file'
125
+
126
+ check_puppetfile
127
+
128
+ fixtures = {
129
+ 'fixtures' => {
130
+ 'symlinks' => {
131
+ 'role' => '#{source_dir}/dist/role',
132
+ 'profile' => '#{source_dir}/dist/profile'
133
+ },
134
+ 'repositories' => {}
135
+ }
136
+ }
137
+
138
+ puppetfile.modules.each do |mod|
139
+ module_name = mod.title.tr('/', '-')
140
+ remote = mod.instance_variable_get('@remote')
141
+ ref = mod.instance_variable_get('@desired_ref')
142
+
143
+ fixtures['fixtures']['repositories'][module_name] = {
144
+ 'repo' => remote,
145
+ 'ref' => ref
146
+ }
147
+ end
148
+
149
+ File.open('.fixtures.yml', 'w') { |file| file.write(fixtures.to_yaml) }
150
+ info 'Done'
151
+ end # def generate_fixtures
152
+
153
+ def check_puppetfile_versions
154
+ puppetfile.load
155
+ error 'Puppetfile was not found or is empty!' if puppetfile.modules.empty?
156
+
157
+ puppetfile.modules.each do |mod|
158
+ if mod.class == ::R10K::Module::Forge
159
+ module_name = mod.title.tr('/', '-')
160
+ forge_version = ::PuppetForge::Module.find(module_name)
161
+ .current_release.version
162
+ installed_version = mod.expected_version
163
+ if installed_version != forge_version
164
+ puts "#{module_name} is OUTDATED: " \
165
+ "#{installed_version} vs #{forge_version}"
166
+ .red
167
+ else
168
+ puts "#{module_name}: #{forge_version}".green
169
+ end
170
+ elsif mod.class == ::R10K::Module::Git
171
+ # Try to extract owner and repo name from remote string
172
+ remote = mod.instance_variable_get('@remote')
173
+ owner = remote.gsub(%r{(.*)\/(.*)\/(.*)}, '\\2')
174
+ repo = remote.gsub(%r{(.*)\/(.*)\/}, '\\3')
175
+
176
+ # It's better to query the API authenticated because of the rate
177
+ # limit. You can make up to 5,000 requests per hour. For unauthenticated
178
+ # requests, the rate limit is only up to 60 requests per hour.
179
+ # (https://developer.github.com/v3/#rate-limiting)
180
+ tags = if GITHUB_TOKEN
181
+ open("https://api.github.com/repos/#{owner}/#{repo}/tags?access_token=#{GITHUB_TOKEN}")
182
+ else
183
+ open("https://api.github.com/repos/#{owner}/#{repo}/tags")
184
+ end
185
+
186
+ # Get rid of non-semantic versions (for example
187
+ # https://github.com/puppetlabs/puppetlabs-ntp/releases/tag/push)
188
+ all_tags = JSON.parse(tags.read).select do |tag|
189
+ tag['name'] =~ /v?\d+\.\d+\.\d+/
190
+ end
191
+
192
+ # Use Gem::Version to sort tags
193
+ latest_tag = all_tags.map do |line|
194
+ ::Gem::Version.new line['name'].gsub(/[v]?(.*)/, '\\1')
195
+ end.max.to_s
196
+
197
+ # Print results
198
+ installed_version = mod.version.gsub(/[v]?(.*)/, '\\1')
199
+ if installed_version == 'master'
200
+ puts "#{mod.title}: 'master' branch (#{latest_tag})".blue
201
+ elsif installed_version != latest_tag
202
+ puts "#{mod.title} is OUTDATED: " \
203
+ "#{installed_version} vs #{latest_tag}"
204
+ .red
205
+ else
206
+ puts "#{mod.title}: #{latest_tag}".green
207
+ end
208
+ end
209
+ end
210
+ end # def check_puppetfile_versions
211
+ end # class Puppet
212
+ end # module Vtasks
@@ -0,0 +1,101 @@
1
+ module Vtasks
2
+ require 'rake/tasklib'
3
+
4
+ # Release tasks
5
+ class Release < ::Rake::TaskLib
6
+ # Include utility modules
7
+ require 'vtasks/utils/git'
8
+ include Vtasks::Utils::Git
9
+ require 'vtasks/utils/output'
10
+ include Vtasks::Utils::Output
11
+ require 'vtasks/utils/semver'
12
+ include Vtasks::Utils::Semver
13
+
14
+ attr_reader :write_changelog,
15
+ :wait_for_ci_success,
16
+ :bug_labels,
17
+ :enhancement_labels
18
+
19
+ def initialize(options = {})
20
+ @write_changelog = options.fetch(:write_changelog, false)
21
+ @wait_for_ci_success = options.fetch(:wait_for_ci_success, false)
22
+ @bug_labels = options.fetch(:bug_labels, 'bug')
23
+ @enhancement_labels = options.fetch(:enhancement_labels, 'enhancement')
24
+ define_tasks
25
+ end
26
+
27
+ # Configure the github_changelog_generator/task
28
+ def changelog(config, release: nil)
29
+ config.bug_labels = bug_labels #'Type: Bug'
30
+ config.enhancement_labels = enhancement_labels #'Type: Enhancement'
31
+ config.future_release = "v#{release}" if release
32
+ end
33
+
34
+ def define_tasks
35
+ desc "Release patch version"
36
+ task release: ['release:patch']
37
+
38
+ namespace :release do
39
+ begin
40
+ require 'github_changelog_generator/task'
41
+
42
+ # Create release:changes task
43
+ ::GitHubChangelogGenerator::RakeTask.new(:changes) do |config|
44
+ changelog(config)
45
+ end
46
+ rescue LoadError
47
+ nil # Might be in a group that is not installed
48
+ end
49
+
50
+ SEM_LEVELS.each do |level|
51
+ desc "Release #{level} version"
52
+ task level.to_sym do
53
+ new_version = bump(level)
54
+ release = "#{new_version[:major]}.#{new_version[:minor]}.#{new_version[:patch]}"
55
+ release_branch = "release_v#{release.gsub(/[^0-9A-Za-z]/, '_')}"
56
+ initial_branch = git_branch
57
+
58
+ info 'Check if the repository is clean'
59
+ git_clean_repo
60
+
61
+ # Write changelog
62
+ # Create a separate release branch (works with protected branches as well)
63
+ if write_changelog == true
64
+ info 'Generate new changelog'
65
+ ::GitHubChangelogGenerator::RakeTask.new(:latest_release) do |config|
66
+ changelog(config, release: release)
67
+ end
68
+ task('latest_release').invoke
69
+
70
+ if system 'git diff --quiet HEAD'
71
+ info 'CHANGELOG has not changed. Skipping...'
72
+ else
73
+ info 'Create a new release branch'
74
+ sh "git checkout -b #{release_branch}"
75
+
76
+ info 'Commit the new changes'
77
+ sh "git commit --gpg-sign --message 'Update change log for v#{release}' CHANGELOG.md"
78
+
79
+ info 'Push the new changes'
80
+ sh "git push --set-upstream origin #{release_branch}"
81
+
82
+ if wait_for_ci_success == true
83
+ info 'Waiting for CI to finish'
84
+ sleep 5 until git_ci_status(release_branch) == 'success'
85
+ end
86
+
87
+ info 'Merge release branch'
88
+ sh "git checkout #{initial_branch}"
89
+ sh "git merge --gpg-sign --no-ff --message 'Release v#{release}' #{release_branch}"
90
+ end
91
+ end
92
+
93
+ info "Tag #{release}"
94
+ sh "git tag --sign v#{release} --message 'Release v#{release}'"
95
+ sh 'git push --follow-tags'
96
+ end # task
97
+ end # LEVELS
98
+ end # namespace :release
99
+ end # def define_tasks
100
+ end # class Release
101
+ end # module Vtasks