bosh_cli 1.2671.0 → 1.2682.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d4c1e1cbf0d51f7f8c6474986297c23727e40717
4
- data.tar.gz: 9cba9a5e26641af58639cfb67c686e5827ce04f7
3
+ metadata.gz: 6c465feaf450a9d1578bdcb091559697af1ca32d
4
+ data.tar.gz: 32eaf038e07206d75d5b13f604908df2d37eeac5
5
5
  SHA512:
6
- metadata.gz: ba53edc2f056dc17be341c9252cba7ae3d9cb6f42b7b08ed3726ec8e6b435aaddd298ba0ba2f8fb9a3b553e93cb7cf0ae832dd2d597257b9b13b9d16ace7bb70
7
- data.tar.gz: ca3d07521f5537aa40cbc7379e134f69b5bada1565de8ff09dd6636f324c3671bbba796783dbe4a0ca2400d7685c705c101194c4175ae45120e6dd61d545ac0c
6
+ metadata.gz: 59097f2f31a97d3103137d366fd750e413bbbd19d98aa000afcc54f0e75b08a0fb4c4674157214afde9a21a4707b9948385769ad24ac3ff90b316a173f704d2c
7
+ data.tar.gz: f1aa9306cdae45a81eefc22b6222f9e4651be01c7238a14dc4743985bc55f98f123b8f1336d103c3f015c3f11c07b0b836abb7d6fe4e85ad53cbf3be45d54696
data/lib/cli.rb CHANGED
@@ -31,6 +31,8 @@ require 'zlib'
31
31
  require 'archive/tar/minitar'
32
32
  include Archive::Tar
33
33
 
34
+ require 'semi_semantic/version'
35
+
34
36
  require 'bosh/template/evaluation_context'
35
37
 
36
38
  unless defined?(Bosh::Cli::VERSION)
@@ -61,10 +63,15 @@ require 'cli/director_task'
61
63
  require 'cli/line_wrap'
62
64
  require 'cli/backup_destination_path'
63
65
 
66
+ require 'cli/source_control/git_ignore'
67
+
64
68
  require 'cli/versions/versions_index'
65
69
  require 'cli/versions/local_version_storage'
66
70
  require 'cli/versions/release_versions_index'
71
+ require 'cli/versions/releases_dir_migrator'
67
72
  require 'cli/versions/version_file_resolver'
73
+ require 'cli/versions/multi_release_support'
74
+
68
75
  require 'cli/packaging_helper'
69
76
  require 'cli/package_builder'
70
77
  require 'cli/job_builder'
@@ -93,6 +100,6 @@ tmpdir = Dir.mktmpdir
93
100
  at_exit { FileUtils.rm_rf(tmpdir) }
94
101
  ENV['TMPDIR'] = tmpdir
95
102
 
96
- Dir[File.dirname(__FILE__) + '/cli/commands/*.rb'].each do |file|
103
+ Dir[File.dirname(__FILE__) + '/cli/commands/**/*.rb'].each do |file|
97
104
  require file
98
105
  end
@@ -0,0 +1,279 @@
1
+ module Bosh::Cli::Command
2
+ module Release
3
+ class CreateRelease < Base
4
+ include Bosh::Cli::DependencyHelper
5
+
6
+ DEFAULT_RELEASE_NAME = 'bosh-release'
7
+
8
+ # bosh create release
9
+ usage 'create release'
10
+ desc 'Create release (assumes current directory to be a release repository)'
11
+ option '--force', 'bypass git dirty state check'
12
+ option '--final', 'create final release'
13
+ option '--with-tarball', 'create release tarball'
14
+ option '--dry-run', 'stop before writing release manifest'
15
+ option '--name NAME', 'specify a custom release name'
16
+ option '--version VERSION', 'specify a custom version number (ex: 1.0.0 or 1.0-beta.2+dev.10)'
17
+
18
+ def create(manifest_file = nil)
19
+ check_if_release_dir
20
+
21
+ migrate_to_support_multiple_releases
22
+
23
+ if manifest_file && File.file?(manifest_file)
24
+ if options[:version]
25
+ err('Cannot specify a custom version number when creating from a manifest. The manifest already specifies a version.'.make_red)
26
+ end
27
+
28
+ say('Recreating release from the manifest')
29
+ Bosh::Cli::ReleaseCompiler.compile(manifest_file, release.blobstore)
30
+ release_filename = manifest_file
31
+ else
32
+ version = options[:version]
33
+ version = Bosh::Common::Version::ReleaseVersion.parse(version).to_s unless version.nil?
34
+
35
+ release_filename = create_from_spec(version)
36
+ end
37
+
38
+ if release_filename
39
+ release.latest_release_filename = release_filename
40
+ release.save_config
41
+ end
42
+ rescue SemiSemantic::ParseError
43
+ err("Invalid version: `#{version}'. Please specify a valid version (ex: 1.0.0 or 1.0-beta.2+dev.10).".make_red)
44
+ rescue Bosh::Cli::ReleaseVersionError => e
45
+ err(e.message.make_red)
46
+ end
47
+
48
+ private
49
+
50
+ def migrate_to_support_multiple_releases
51
+ default_release_name = release.final_name
52
+
53
+ # can't migrate without a default release name
54
+ return if default_release_name.blank?
55
+
56
+ Bosh::Cli::Versions::MultiReleaseSupport.new(@work_dir, default_release_name, self).migrate
57
+ end
58
+
59
+ def create_from_spec(version)
60
+ final = options[:final]
61
+ force = options[:force]
62
+ name = options[:name]
63
+ manifest_only = !options[:with_tarball]
64
+ dry_run = options[:dry_run]
65
+
66
+ err("Can't create final release without blobstore secret") if final && !release.has_blobstore_secret?
67
+
68
+ dirty_blob_check(force)
69
+
70
+ raise_dirty_state_error if dirty_state? && !force
71
+
72
+ if final
73
+ confirm_final_release(dry_run)
74
+ unless name
75
+ save_final_release_name if release.final_name.blank?
76
+ name = release.final_name
77
+ end
78
+ header('Building FINAL release'.make_green)
79
+ else
80
+ unless name
81
+ save_dev_release_name if release.dev_name.blank?
82
+ name = release.dev_name
83
+ end
84
+ header('Building DEV release'.make_green)
85
+ end
86
+
87
+ header('Building packages')
88
+ packages = build_packages(dry_run, final)
89
+
90
+ header('Building jobs')
91
+ jobs = build_jobs(packages.map(&:name), dry_run, final)
92
+
93
+ header('Building release')
94
+ release_builder = build_release(dry_run, final, jobs, manifest_only, packages, name, version)
95
+
96
+ header('Release summary')
97
+ show_summary(release_builder)
98
+ nl
99
+
100
+ return nil if dry_run
101
+
102
+ say("Release name: #{name.make_green}")
103
+ say("Release version: #{release_builder.version.to_s.make_green}")
104
+ say("Release manifest: #{release_builder.manifest_path.make_green}")
105
+
106
+ unless manifest_only
107
+ say("Release tarball (#{pretty_size(release_builder.tarball_path)}): " +
108
+ release_builder.tarball_path.make_green)
109
+ end
110
+
111
+ release.save_config
112
+
113
+ release_builder.manifest_path
114
+ end
115
+
116
+ def confirm_final_release(dry_run)
117
+ confirmed = non_interactive? || agree("Are you sure you want to generate #{'final'.make_red} version? ")
118
+ if !dry_run && !confirmed
119
+ say('Canceled release generation'.make_green)
120
+ exit(1)
121
+ end
122
+ end
123
+
124
+ def dirty_blob_check(force)
125
+ blob_manager.sync
126
+ if blob_manager.dirty?
127
+ blob_manager.print_status
128
+ if force
129
+ say("Proceeding with dirty blobs as '--force' is given".make_red)
130
+ else
131
+ err("Please use '--force' or upload new blobs")
132
+ end
133
+ end
134
+ end
135
+
136
+ def build_packages(dry_run, final)
137
+ packages = Bosh::Cli::PackageBuilder.discover(
138
+ work_dir,
139
+ :final => final,
140
+ :blobstore => release.blobstore,
141
+ :dry_run => dry_run
142
+ )
143
+
144
+ packages.each do |package|
145
+ say("Building #{package.name.make_green}...")
146
+ package.build
147
+ nl
148
+ end
149
+
150
+ if packages.size > 0
151
+ package_index = packages.inject({}) do |index, package|
152
+ index[package.name] = package.dependencies
153
+ index
154
+ end
155
+ sorted_packages = tsort_packages(package_index)
156
+ header('Resolving dependencies')
157
+ say('Dependencies resolved, correct build order is:')
158
+ sorted_packages.each do |package_name|
159
+ say('- %s' % [package_name])
160
+ end
161
+ nl
162
+ end
163
+
164
+ packages
165
+ end
166
+
167
+ def build_release(dry_run, final, jobs, manifest_only, packages, name, version)
168
+ release_builder = Bosh::Cli::ReleaseBuilder.new(release, packages, jobs, name,
169
+ final: final,
170
+ commit_hash: commit_hash,
171
+ version: version,
172
+ uncommitted_changes: dirty_state?
173
+ )
174
+
175
+ unless dry_run
176
+ if manifest_only
177
+ release_builder.build(:generate_tarball => false)
178
+ else
179
+ release_builder.build(:generate_tarball => true)
180
+ end
181
+ end
182
+
183
+ release_builder
184
+ end
185
+
186
+ def build_jobs(built_package_names, dry_run, final)
187
+ jobs = Bosh::Cli::JobBuilder.discover(
188
+ work_dir,
189
+ :final => final,
190
+ :blobstore => release.blobstore,
191
+ :dry_run => dry_run,
192
+ :package_names => built_package_names
193
+ )
194
+
195
+ jobs.each do |job|
196
+ say("Building #{job.name.make_green}...")
197
+ job.build
198
+ nl
199
+ end
200
+
201
+ jobs
202
+ end
203
+
204
+ def save_final_release_name
205
+ release.final_name = DEFAULT_RELEASE_NAME
206
+ if interactive?
207
+ release.final_name = ask('Please enter final release name: ').to_s
208
+ err('Canceled release creation, no name given') if release.final_name.blank?
209
+ end
210
+ release.save_config
211
+ end
212
+
213
+ def save_dev_release_name
214
+ if interactive?
215
+ release.dev_name = ask('Please enter development release name: ') do |q|
216
+ q.default = release.final_name if release.final_name
217
+ end.to_s
218
+ err('Canceled release creation, no name given') if release.dev_name.blank?
219
+ else
220
+ release.dev_name = release.final_name ? release.final_name : DEFAULT_RELEASE_NAME
221
+ end
222
+ release.save_config
223
+ end
224
+
225
+ def show_summary(builder)
226
+ packages_table = table do |t|
227
+ t.headings = %w(Name Version Notes)
228
+ builder.packages.each do |package|
229
+ t << artifact_summary(package)
230
+ end
231
+ end
232
+
233
+ jobs_table = table do |t|
234
+ t.headings = %w(Name Version Notes)
235
+ builder.jobs.each do |job|
236
+ t << artifact_summary(job)
237
+ end
238
+ end
239
+
240
+ say('Packages')
241
+ say(packages_table)
242
+ nl
243
+ say('Jobs')
244
+ say(jobs_table)
245
+
246
+ affected_jobs = builder.affected_jobs
247
+
248
+ if affected_jobs.size > 0
249
+ nl
250
+ say('Jobs affected by changes in this release')
251
+
252
+ affected_jobs_table = table do |t|
253
+ t.headings = %w(Name Version)
254
+ affected_jobs.each do |job|
255
+ t << [job.name, job.version]
256
+ end
257
+ end
258
+
259
+ say(affected_jobs_table)
260
+ end
261
+ end
262
+
263
+ def artifact_summary(artefact)
264
+ result = []
265
+ result << artefact.name
266
+ result << artefact.version
267
+ result << artefact.notes.join(', ')
268
+ result
269
+ end
270
+
271
+ def commit_hash
272
+ status = Bosh::Exec.sh('git show-ref --head --hash=8 2> /dev/null')
273
+ status.output.split.first
274
+ rescue Bosh::Exec::Error
275
+ '00000000'
276
+ end
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,32 @@
1
+ module Bosh::Cli::Command
2
+ module Release
3
+ class DeleteRelease < Base
4
+
5
+ usage 'delete release'
6
+ desc 'Delete release (or a particular release version)'
7
+ option '--force', 'ignore errors during deletion'
8
+ def delete(name, version = nil)
9
+ auth_required
10
+ force = !!options[:force]
11
+
12
+ desc = "#{name}"
13
+ desc << "/#{version}" if version
14
+
15
+ if force
16
+ say("Deleting `#{desc}' (FORCED DELETE, WILL IGNORE ERRORS)".make_red)
17
+ else
18
+ say("Deleting `#{desc}'".make_red)
19
+ end
20
+
21
+ if confirmed?
22
+ status, task_id = director.delete_release(name, force: force, version: version)
23
+ task_report(status, task_id, "Deleted `#{desc}'")
24
+ else
25
+ say('Canceled deleting release'.make_green)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+
@@ -0,0 +1,45 @@
1
+ module Bosh::Cli::Command
2
+ module Release
3
+ class InitRelease < Base
4
+
5
+ # bosh init release
6
+ usage 'init release'
7
+ desc 'Initialize release directory'
8
+ option '--git', 'initialize git repository'
9
+ def init(base = nil)
10
+ if base
11
+ FileUtils.mkdir_p(base)
12
+ Dir.chdir(base)
13
+ end
14
+
15
+ err('Release already initialized') if in_release_dir?
16
+ git_init if options[:git]
17
+
18
+ %w[config jobs packages src blobs].each do |dir|
19
+ FileUtils.mkdir(dir)
20
+ end
21
+
22
+ # Initialize an empty blobs index
23
+ File.open(File.join('config', 'blobs.yml'), 'w') do |f|
24
+ Psych.dump({}, f)
25
+ end
26
+
27
+ say('Release directory initialized'.make_green)
28
+ end
29
+
30
+ private
31
+
32
+ def git_init
33
+ out = %x{git init 2>&1}
34
+ if $? != 0
35
+ say("error running 'git init':\n#{out}")
36
+ else
37
+ Bosh::Cli::SourceControl::GitIgnore.new(@work_dir).update
38
+ end
39
+ rescue Errno::ENOENT
40
+ say("Unable to run 'git init'".make_red)
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,123 @@
1
+ module Bosh::Cli::Command
2
+ module Release
3
+ class ListReleases < Base
4
+
5
+ usage 'releases'
6
+ desc 'Show the list of available releases'
7
+ option '--jobs', 'include job templates'
8
+ def list
9
+ auth_required
10
+ releases = director.list_releases.sort do |r1, r2|
11
+ r1['name'] <=> r2['name']
12
+ end
13
+
14
+ err('No releases') if releases.empty?
15
+
16
+ currently_deployed = false
17
+ uncommited_changes = false
18
+ if releases.first.has_key? 'release_versions'
19
+ releases_table = build_releases_table(releases, options)
20
+ currently_deployed, uncommited_changes = release_version_details(releases)
21
+ elsif releases.first.has_key? 'versions'
22
+ releases_table = build_releases_table_for_old_director(releases)
23
+ currently_deployed, uncommited_changes = release_version_details_for_old_director(releases)
24
+ end
25
+
26
+ nl
27
+ say(releases_table.render)
28
+
29
+ say('(*) Currently deployed') if currently_deployed
30
+ say('(+) Uncommitted changes') if uncommited_changes
31
+ nl
32
+ say('Releases total: %d' % releases.size)
33
+ end
34
+
35
+ private
36
+ def build_releases_table_for_old_director(releases)
37
+ table do |t|
38
+ t.headings = 'Name', 'Versions'
39
+ releases.each do |release|
40
+ versions = release['versions'].sort { |v1, v2|
41
+ Bosh::Common::Version::ReleaseVersion.parse_and_compare(v1, v2)
42
+ }.map { |v| ((release['in_use'] || []).include?(v)) ? "#{v}*" : v }
43
+
44
+ t << [release['name'], versions.join(', ')]
45
+ end
46
+ end
47
+ end
48
+
49
+ # Builds table of release information
50
+ # Default headings: "Name", "Versions", "Commit Hash"
51
+ # Extra headings: options[:job] => "Jobs"
52
+ def build_releases_table(releases, options = {})
53
+ show_jobs = options[:jobs]
54
+ table do |t|
55
+ t.headings = 'Name', 'Versions', 'Commit Hash'
56
+ t.headings << 'Jobs' if show_jobs
57
+ releases.each do |release|
58
+ versions, commit_hashes = formatted_versions(release).transpose
59
+ row = [release['name'], versions.join("\n"), commit_hashes.join("\n")]
60
+ if show_jobs
61
+ jobs = formatted_jobs(release).transpose
62
+ row << jobs.join("\n")
63
+ end
64
+ t << row
65
+ end
66
+ end
67
+ end
68
+
69
+ def formatted_versions(release)
70
+ sort_versions(release['release_versions']).map { |v| formatted_version_and_commit_hash(v) }
71
+ end
72
+
73
+ def sort_versions(versions)
74
+ versions.sort { |v1, v2| Bosh::Common::Version::ReleaseVersion.parse_and_compare(v1['version'], v2['version']) }
75
+ end
76
+
77
+ def formatted_version_and_commit_hash(version)
78
+ version_number = version['version'] + (version['currently_deployed'] ? '*' : '')
79
+ commit_hash = version['commit_hash'] + (version['uncommitted_changes'] ? '+' : '')
80
+ [version_number, commit_hash]
81
+ end
82
+
83
+ def formatted_jobs(release)
84
+ sort_versions(release['release_versions']).map do |v|
85
+ if job_names = v['job_names']
86
+ [job_names.join(', ')]
87
+ else
88
+ ['n/a '] # with enough whitespace to match "Jobs" header
89
+ end
90
+ end
91
+ end
92
+
93
+
94
+ def release_version_details(releases)
95
+ currently_deployed = false
96
+ uncommitted_changes = false
97
+ releases.each do |release|
98
+ release['release_versions'].each do |version|
99
+ currently_deployed ||= version['currently_deployed']
100
+ uncommitted_changes ||= version['uncommitted_changes']
101
+ if currently_deployed && uncommitted_changes
102
+ return true, true
103
+ end
104
+ end
105
+ end
106
+ return currently_deployed, uncommitted_changes
107
+ end
108
+
109
+ def release_version_details_for_old_director(releases)
110
+ currently_deployed = false
111
+ # old director did not support uncommitted changes
112
+ uncommitted_changes = false
113
+ releases.each do |release|
114
+ currently_deployed ||= release['in_use'].any?
115
+ if currently_deployed
116
+ return true, uncommitted_changes
117
+ end
118
+ end
119
+ return currently_deployed, uncommitted_changes
120
+ end
121
+ end
122
+ end
123
+ end