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.
@@ -1,5 +1,5 @@
1
1
  module Bosh
2
2
  module Cli
3
- VERSION = '1.2671.0'
3
+ VERSION = '1.2682.0'
4
4
  end
5
5
  end
@@ -1,4 +1,4 @@
1
- module Bosh::Cli
1
+ module Bosh::Cli::Versions
2
2
  class LocalVersionStorage
3
3
 
4
4
  class Sha1MismatchError < StandardError; end
@@ -0,0 +1,24 @@
1
+ module Bosh::Cli::Versions
2
+ class MultiReleaseSupport
3
+
4
+ def initialize(work_dir, default_release_name, ui)
5
+ @work_dir = work_dir
6
+ @default_release_name = default_release_name
7
+ @ui = ui
8
+ end
9
+
10
+ def migrate
11
+ dev_releases_path = File.join(@work_dir, 'dev_releases')
12
+ migrator = ReleasesDirMigrator.new(dev_releases_path, @default_release_name, @ui, 'DEV')
13
+ dev_release_migrated = migrator.migrate
14
+
15
+ final_releases_path = File.join(@work_dir, 'releases')
16
+ migrator = ReleasesDirMigrator.new(final_releases_path, @default_release_name, @ui, 'FINAL')
17
+ final_releases_migrated = migrator.migrate
18
+
19
+ if final_releases_migrated || dev_release_migrated
20
+ Bosh::Cli::SourceControl::GitIgnore.new(@work_dir).update
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,4 +1,4 @@
1
- module Bosh::Cli
1
+ module Bosh::Cli::Versions
2
2
  class ReleaseVersionsIndex
3
3
 
4
4
  def initialize(versions_index)
@@ -0,0 +1,108 @@
1
+ # This class migrates the releases directory and its index.yml files from format v1 -> v2
2
+ module Bosh::Cli::Versions
3
+ class ReleasesDirMigrator
4
+
5
+ def initialize(releases_path, default_release_name, ui, release_type_name)
6
+ @releases_path = releases_path
7
+ @default_release_name = default_release_name
8
+ @ui = ui
9
+ @release_type_name = release_type_name
10
+ end
11
+
12
+ def needs_migration?
13
+ index_path = File.join(@releases_path, 'index.yml')
14
+ return false unless File.exists?(index_path)
15
+
16
+ index = VersionsIndex.load_index_yaml(index_path)
17
+ format_version_string = index['format-version']
18
+ return true if format_version_string.nil?
19
+
20
+ begin
21
+ SemiSemantic::Version.parse(format_version_string) < VersionsIndex::CURRENT_INDEX_VERSION
22
+ rescue ArgumentError, SemiSemantic::ParseError
23
+ raise InvalidIndex, "Invalid versions index version in `#{index_path}', " +
24
+ "`#{format_version_string}' given, SemiSemantic version expected"
25
+ end
26
+ end
27
+
28
+ def migrate
29
+ return false unless needs_migration?
30
+
31
+ unless Dir.exist?(@releases_path)
32
+ raise "Releases path `#{@releases_path}' does not exist"
33
+ end
34
+
35
+ @ui.header("Migrating #{@release_type_name} releases".make_green)
36
+
37
+ old_index = VersionsIndex.new(@releases_path)
38
+
39
+ migrated_releases = Set.new
40
+
41
+ release_versions_to_migrate.each do |data|
42
+ release_name = data[:name]
43
+ if migrated_releases.add?(release_name)
44
+ @ui.say("Migrating release: #{release_name}")
45
+ end
46
+
47
+ release_path = File.join(@releases_path, release_name)
48
+
49
+ FileUtils.mkdir_p(release_path) unless Dir.exist?(release_path)
50
+
51
+ # move version record to new index file in release_path
52
+ index_key = old_index.find_key_by_version(data[:version])
53
+ index_value = old_index[index_key]
54
+ old_index.remove_version(index_key)
55
+ new_index = VersionsIndex.new(release_path)
56
+ new_index.add_version(index_key, index_value)
57
+
58
+ # move tarball and manifest to release_path
59
+ FileUtils.mv(data[:manifest_path], release_path)
60
+ FileUtils.mv(data[:tarball_path], release_path) if File.exist?(data[:tarball_path])
61
+ end
62
+
63
+ @ui.say("Migrating default release: #{@default_release_name}")
64
+ create_release_symlink
65
+
66
+ # initialize release name & format-version in index.yml
67
+ old_index.save
68
+
69
+ true
70
+ end
71
+
72
+ private
73
+
74
+ def release_versions_to_migrate
75
+ release_versions_data.select { |v| v[:name] != @default_release_name }
76
+ end
77
+
78
+ def release_versions_data
79
+ release_manifest_paths.map do |file_path|
80
+ manifest_hash = load_yaml_file(file_path, nil)
81
+ return {} unless manifest_hash.kind_of?(Hash)
82
+ {
83
+ name: manifest_hash['name'],
84
+ version: manifest_hash['version'],
85
+ manifest_path: file_path,
86
+ tarball_path: file_path[0...-4] + '.tgz',
87
+ }
88
+ end.select do |version_record|
89
+ # ignore invalid manifests
90
+ version_record != {}
91
+ end
92
+ end
93
+
94
+ def release_manifest_paths
95
+ index_path = File.join(@releases_path, 'index.yml')
96
+ Dir.glob(File.join(@releases_path, '*.yml')).select do |file_path|
97
+ file_path != index_path
98
+ end
99
+ end
100
+
101
+ def create_release_symlink
102
+ # symlink must be relative in order to be portable (ex: git clone)
103
+ Dir.chdir(@releases_path) do
104
+ File.symlink('.', @default_release_name)
105
+ end
106
+ end
107
+ end
108
+ end
@@ -1,4 +1,4 @@
1
- module Bosh::Cli
1
+ module Bosh::Cli::Versions
2
2
  class VersionFileResolver
3
3
 
4
4
  def initialize(storage, blobstore, tmpdir=Dir.tmpdir)
@@ -28,14 +28,7 @@ module Bosh::Cli
28
28
  tmp_file_path = File.join(@tmpdir, "bosh-tmp-file-#{SecureRandom.uuid}")
29
29
  begin
30
30
  File.open(tmp_file_path, 'w') do |tmp_file|
31
- @blobstore.get(blobstore_id, tmp_file)
32
- end
33
-
34
- file_sha1 = Digest::SHA1.file(tmp_file_path).hexdigest
35
- if file_sha1 != sha1
36
- err("SHA1 `#{file_sha1}' of #{desc} from blobstore (id=#{blobstore_id}) " +
37
- "does not match expected SHA1 `#{sha1}'" +
38
- 'please remove it manually and re-create the release')
31
+ @blobstore.get(blobstore_id, tmp_file, sha1: sha1)
39
32
  end
40
33
 
41
34
  @storage.put_file(version, tmp_file_path)
@@ -43,7 +36,5 @@ module Bosh::Cli
43
36
  FileUtils.rm(tmp_file_path, :force => true)
44
37
  end
45
38
  end
46
-
47
-
48
39
  end
49
40
  end
@@ -1,5 +1,27 @@
1
- module Bosh::Cli
1
+ module Bosh::Cli::Versions
2
2
  class VersionsIndex
3
+ include Enumerable
4
+
5
+ CURRENT_INDEX_VERSION = SemiSemantic::Version.parse('2')
6
+
7
+ def self.load_index_yaml(index_path)
8
+ contents = load_yaml_file(index_path, nil)
9
+ # Psych.load returns false if file is empty
10
+ return nil if contents === false
11
+ unless contents.kind_of?(Hash)
12
+ raise Bosh::Cli::InvalidIndex, "Invalid versions index data type, #{contents.class} given, Hash expected"
13
+ end
14
+ contents
15
+ end
16
+
17
+ def self.write_index_yaml(index_path, contents)
18
+ unless contents.kind_of?(Hash)
19
+ raise Bosh::Cli::InvalidIndex, "Invalid versions index data type, #{contents.class} given, Hash expected"
20
+ end
21
+ File.open(index_path, 'w') do |f|
22
+ f.write(Psych.dump(contents))
23
+ end
24
+ end
3
25
 
4
26
  attr_reader :index_file
5
27
  attr_reader :storage_dir
@@ -9,7 +31,7 @@ module Bosh::Cli
9
31
  @index_file = File.join(@storage_dir, 'index.yml')
10
32
 
11
33
  if File.file?(@index_file)
12
- init_index(load_yaml_file(@index_file, nil))
34
+ init_index(VersionsIndex.load_index_yaml(@index_file))
13
35
  else
14
36
  init_index({})
15
37
  end
@@ -19,17 +41,8 @@ module Bosh::Cli
19
41
  @data['builds'][key]
20
42
  end
21
43
 
22
- def each_pair(&block)
23
- @data['builds'].each_pair(&block)
24
- end
25
-
26
- def latest_version
27
- builds = @data['builds'].values
28
-
29
- return nil if builds.empty?
30
-
31
- version_strings = builds.map { |b| b['version'] }
32
- Bosh::Common::Version::ReleaseVersionList.parse(version_strings).latest.to_s
44
+ def each(&block)
45
+ @data['builds'].each(&block)
33
46
  end
34
47
 
35
48
  def select(&block)
@@ -41,15 +54,15 @@ module Bosh::Cli
41
54
  version = new_build['version']
42
55
 
43
56
  if version.blank?
44
- raise InvalidIndex, "Cannot save index entry without a version: `#{new_build}'"
57
+ raise Bosh::Cli::InvalidIndex, "Cannot save index entry without a version: `#{new_build}'"
45
58
  end
46
59
 
47
60
  if @data['builds'][new_key]
48
61
  raise "Trying to add duplicate entry `#{new_key}' into index `#{@index_file}'"
49
62
  end
50
63
 
51
- self.each_pair do |key, build|
52
- if build['version'] == version && key != new_key
64
+ each do |key, build|
65
+ if key != new_key && build['version'] == version
53
66
  raise "Trying to add duplicate version `#{version}' into index `#{@index_file}'"
54
67
  end
55
68
  end
@@ -58,9 +71,7 @@ module Bosh::Cli
58
71
 
59
72
  @data['builds'][new_key] = new_build
60
73
 
61
- File.open(@index_file, 'w') do |f|
62
- f.write(Psych.dump(@data))
63
- end
74
+ save
64
75
 
65
76
  version
66
77
  end
@@ -77,9 +88,24 @@ module Bosh::Cli
77
88
 
78
89
  @data['builds'][key] = new_build
79
90
 
80
- File.open(@index_file, 'w') do |f|
81
- f.write(Psych.dump(@data))
91
+ save
92
+ end
93
+
94
+ def remove_version(key)
95
+ build = @data['builds'][key]
96
+ unless build
97
+ raise "Cannot remove non-existent entry with key `#{key}'"
82
98
  end
99
+
100
+ @data['builds'].delete(key)
101
+
102
+ save
103
+ end
104
+
105
+ def find_key_by_version(version)
106
+ key_and_build = find { |_, build| build['version'] == version }
107
+
108
+ key_and_build.first unless key_and_build.nil?
83
109
  end
84
110
 
85
111
  def version_strings
@@ -90,6 +116,18 @@ module Bosh::Cli
90
116
  @data['builds'].to_s
91
117
  end
92
118
 
119
+ def format_version
120
+ format_version_string = @data['format-version']
121
+ SemiSemantic::Version.parse(format_version_string)
122
+ rescue ArgumentError, SemiSemantic::ParseError
123
+ raise InvalidIndex, "Invalid versions index version in `#{@index_file}', " +
124
+ "`#{format_version_string}' given, SemiSemantic version expected"
125
+ end
126
+
127
+ def save
128
+ VersionsIndex.write_index_yaml(@index_file, @data)
129
+ end
130
+
93
131
  private
94
132
 
95
133
  def create_directories
@@ -107,13 +145,9 @@ module Bosh::Cli
107
145
  end
108
146
 
109
147
  def init_index(data)
110
- data ||= {}
111
-
112
- unless data.kind_of?(Hash)
113
- raise InvalidIndex, "Invalid versions index data type, #{data.class} given, Hash expected"
114
- end
115
- @data = data
148
+ @data = data || {}
116
149
  @data['builds'] ||= {}
150
+ @data['format-version'] ||= CURRENT_INDEX_VERSION.to_s
117
151
  end
118
152
  end
119
153
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bosh_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2671.0
4
+ version: 1.2682.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - VMware
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-31 00:00:00.000000000 Z
11
+ date: 2014-08-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bosh_common
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.2671.0
19
+ version: 1.2682.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 1.2671.0
26
+ version: 1.2682.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bosh-template
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.2671.0
33
+ version: 1.2682.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.2671.0
40
+ version: 1.2682.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: json_pure
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: 1.2671.0
117
+ version: 1.2682.0
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 1.2671.0
124
+ version: 1.2682.0
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: net-ssh
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -194,7 +194,7 @@ dependencies:
194
194
  version: 0.5.4
195
195
  description: |-
196
196
  BOSH CLI
197
- 03191d
197
+ 4f6636
198
198
  email: support@cloudfoundry.com
199
199
  executables:
200
200
  - bosh
@@ -232,7 +232,13 @@ files:
232
232
  - lib/cli/commands/misc.rb
233
233
  - lib/cli/commands/package.rb
234
234
  - lib/cli/commands/property_management.rb
235
- - lib/cli/commands/release.rb
235
+ - lib/cli/commands/release/create_release.rb
236
+ - lib/cli/commands/release/delete_release.rb
237
+ - lib/cli/commands/release/init_release.rb
238
+ - lib/cli/commands/release/list_releases.rb
239
+ - lib/cli/commands/release/reset_release.rb
240
+ - lib/cli/commands/release/upload_release.rb
241
+ - lib/cli/commands/release/verify_release.rb
236
242
  - lib/cli/commands/snapshot.rb
237
243
  - lib/cli/commands/ssh.rb
238
244
  - lib/cli/commands/stemcell.rb
@@ -270,6 +276,7 @@ files:
270
276
  - lib/cli/release_tarball.rb
271
277
  - lib/cli/resurrection.rb
272
278
  - lib/cli/runner.rb
279
+ - lib/cli/source_control/git_ignore.rb
273
280
  - lib/cli/stemcell.rb
274
281
  - lib/cli/task_tracking.rb
275
282
  - lib/cli/task_tracking/event_log_renderer.rb
@@ -283,7 +290,9 @@ files:
283
290
  - lib/cli/validation.rb
284
291
  - lib/cli/version.rb
285
292
  - lib/cli/versions/local_version_storage.rb
293
+ - lib/cli/versions/multi_release_support.rb
286
294
  - lib/cli/versions/release_versions_index.rb
295
+ - lib/cli/versions/releases_dir_migrator.rb
287
296
  - lib/cli/versions/version_file_resolver.rb
288
297
  - lib/cli/versions/versions_index.rb
289
298
  - lib/cli/vm_state.rb
@@ -1,684 +0,0 @@
1
- module Bosh::Cli::Command
2
- class Release < Base
3
- DEFAULT_RELEASE_NAME = 'bosh-release'
4
-
5
- include Bosh::Cli::DependencyHelper
6
-
7
- # bosh init release
8
- usage 'init release'
9
- desc 'Initialize release directory'
10
- option '--git', 'initialize git repository'
11
- def init(base = nil)
12
- if base
13
- FileUtils.mkdir_p(base)
14
- Dir.chdir(base)
15
- end
16
-
17
- err('Release already initialized') if in_release_dir?
18
- git_init if options[:git]
19
-
20
- %w[config jobs packages src blobs].each do |dir|
21
- FileUtils.mkdir(dir)
22
- end
23
-
24
- # Initialize an empty blobs index
25
- File.open(File.join('config', 'blobs.yml'), 'w') do |f|
26
- Psych.dump({}, f)
27
- end
28
-
29
- say('Release directory initialized'.make_green)
30
- end
31
-
32
- # bosh create release
33
- usage 'create release'
34
- desc 'Create release (assumes current directory to be a release repository)'
35
- option '--force', 'bypass git dirty state check'
36
- option '--final', 'create final release'
37
- option '--with-tarball', 'create release tarball'
38
- option '--dry-run', 'stop before writing release manifest'
39
- option '--version VERSION', 'specify a custom version number (ex: 1.0.0 or 1.0-beta.2+dev.10)'
40
- def create(manifest_file = nil)
41
- check_if_release_dir
42
-
43
- if manifest_file && File.file?(manifest_file)
44
- if options[:version]
45
- err('Cannot specify a custom version number when creating from a manifest. The manifest already specifies a version.'.make_red)
46
- end
47
-
48
- say('Recreating release from the manifest')
49
- Bosh::Cli::ReleaseCompiler.compile(manifest_file, release.blobstore)
50
- release_filename = manifest_file
51
- else
52
- version = options[:version]
53
- version = Bosh::Common::Version::ReleaseVersion.parse(version).to_s unless version.nil?
54
-
55
- release_filename = create_from_spec(version)
56
- end
57
-
58
- if release_filename
59
- release.latest_release_filename = release_filename
60
- release.save_config
61
- end
62
- rescue SemiSemantic::ParseError
63
- err("Invalid version: `#{version}'. Please specify a valid version (ex: 1.0.0 or 1.0-beta.2+dev.10).".make_red)
64
- rescue Bosh::Cli::ReleaseVersionError => e
65
- err(e.message.make_red)
66
- end
67
-
68
- # bosh verify release
69
- usage 'verify release'
70
- desc 'Verify release'
71
- def verify(tarball_path)
72
- tarball = Bosh::Cli::ReleaseTarball.new(tarball_path)
73
-
74
- nl
75
- say('Verifying release...')
76
- tarball.validate
77
- nl
78
-
79
- if tarball.valid?
80
- say("`#{tarball_path}' is a valid release".make_green)
81
- else
82
- say('Validation errors:'.make_red)
83
- tarball.errors.each do |error|
84
- say("- #{error}")
85
- end
86
- err("`#{tarball_path}' is not a valid release".make_red)
87
- end
88
- end
89
-
90
- usage 'upload release'
91
- desc 'Upload release (release_file can be a local file or a remote URI)'
92
- option '--rebase',
93
- 'Rebases this release onto the latest version',
94
- 'known by director (discards local job/package',
95
- 'versions in favor of versions assigned by director)'
96
- option '--skip-if-exists', 'skips upload if release already exists'
97
- def upload(release_file = nil)
98
- auth_required
99
-
100
- upload_options = {
101
- :rebase => options[:rebase],
102
- :repack => true,
103
- :skip_if_exists => options[:skip_if_exists],
104
- }
105
-
106
- if release_file.nil?
107
- check_if_release_dir
108
- release_file = release.latest_release_filename
109
- if release_file.nil?
110
- err('The information about latest generated release is missing, please provide release filename')
111
- end
112
- unless confirmed?("Upload release `#{File.basename(release_file).make_green}' to `#{target_name.make_green}'")
113
- err('Canceled upload')
114
- end
115
- end
116
-
117
- if release_file =~ /^#{URI::regexp}$/
118
- upload_remote_release(release_file, upload_options)
119
- else
120
- unless File.exist?(release_file)
121
- err("Release file doesn't exist")
122
- end
123
-
124
- file_type = `file --mime-type -b '#{release_file}'`
125
-
126
- if file_type =~ /text\/(plain|yaml)/
127
- upload_manifest(release_file, upload_options)
128
- else
129
- upload_tarball(release_file, upload_options)
130
- end
131
- end
132
- end
133
-
134
- usage 'reset release'
135
- desc 'Reset dev release'
136
- def reset
137
- check_if_release_dir
138
-
139
- say('Your dev release environment will be completely reset'.make_red)
140
- if confirmed?
141
- say('Removing dev_builds index...')
142
- FileUtils.rm_rf('.dev_builds')
143
- say('Clearing dev name...')
144
- release.dev_name = nil
145
- release.save_config
146
- say('Removing dev tarballs...')
147
- FileUtils.rm_rf('dev_releases')
148
-
149
- say('Release has been reset'.make_green)
150
- else
151
- say('Canceled')
152
- end
153
- end
154
-
155
- usage 'releases'
156
- desc 'Show the list of available releases'
157
- option '--jobs', 'include job templates'
158
- def list
159
- auth_required
160
- releases = director.list_releases.sort do |r1, r2|
161
- r1['name'] <=> r2['name']
162
- end
163
-
164
- err('No releases') if releases.empty?
165
-
166
- currently_deployed = false
167
- uncommited_changes = false
168
- if releases.first.has_key? 'release_versions'
169
- releases_table = build_releases_table(releases, options)
170
- currently_deployed, uncommited_changes = release_version_details(releases)
171
- elsif releases.first.has_key? 'versions'
172
- releases_table = build_releases_table_for_old_director(releases)
173
- currently_deployed, uncommited_changes = release_version_details_for_old_director(releases)
174
- end
175
-
176
- nl
177
- say(releases_table.render)
178
-
179
- say('(*) Currently deployed') if currently_deployed
180
- say('(+) Uncommitted changes') if uncommited_changes
181
- nl
182
- say('Releases total: %d' % releases.size)
183
- end
184
-
185
- usage 'delete release'
186
- desc 'Delete release (or a particular release version)'
187
- option '--force', 'ignore errors during deletion'
188
- def delete(name, version = nil)
189
- auth_required
190
- force = !!options[:force]
191
-
192
- desc = "#{name}"
193
- desc << "/#{version}" if version
194
-
195
- if force
196
- say("Deleting `#{desc}' (FORCED DELETE, WILL IGNORE ERRORS)".make_red)
197
- else
198
- say("Deleting `#{desc}'".make_red)
199
- end
200
-
201
- if confirmed?
202
- status, task_id = director.delete_release(name, force: force, version: version)
203
- task_report(status, task_id, "Deleted `#{desc}'")
204
- else
205
- say('Canceled deleting release'.make_green)
206
- end
207
- end
208
-
209
- private
210
-
211
- def upload_manifest(manifest_path, upload_options = {})
212
- package_matches = match_remote_packages(File.read(manifest_path))
213
-
214
- find_release_dir(manifest_path)
215
-
216
- blobstore = release.blobstore
217
- tmpdir = Dir.mktmpdir
218
-
219
- compiler = Bosh::Cli::ReleaseCompiler.new(manifest_path, blobstore, package_matches)
220
- need_repack = true
221
-
222
- unless compiler.exists?
223
- compiler.tarball_path = File.join(tmpdir, 'release.tgz')
224
- compiler.compile
225
- need_repack = false
226
- end
227
-
228
- upload_options[:repack] = need_repack
229
- upload_tarball(compiler.tarball_path, upload_options)
230
- end
231
-
232
- def upload_tarball(tarball_path, upload_options = {})
233
- tarball = Bosh::Cli::ReleaseTarball.new(tarball_path)
234
- # Trying to repack release by default
235
- repack = upload_options[:repack]
236
- rebase = upload_options[:rebase]
237
-
238
- say("\nVerifying release...")
239
- tarball.validate(:allow_sparse => true)
240
- nl
241
-
242
- unless tarball.valid?
243
- err('Release is invalid, please fix, verify and upload again')
244
- end
245
-
246
- if should_convert_to_old_format?(tarball.version)
247
- msg = "You are using CLI > 1.2579.0 with a director that doesn't support " +
248
- 'the new version format you are using. Upgrade your ' +
249
- 'director to match the version of your CLI or downgrade your ' +
250
- 'CLI to 1.2579.0 to avoid versioning mismatch issues.'
251
-
252
- say(msg.make_yellow)
253
- tarball_path = tarball.convert_to_old_format
254
- end
255
-
256
- remote_release = get_remote_release(tarball.release_name) rescue nil
257
- if remote_release && !rebase
258
- if remote_release['versions'].include?(tarball.version)
259
- if upload_options[:skip_if_exists]
260
- say("Release `#{tarball.release_name}/#{tarball.version}' already exists. Skipping upload.")
261
- return
262
- else
263
- err('This release version has already been uploaded')
264
- end
265
- end
266
- end
267
-
268
- begin
269
- if repack
270
- package_matches = match_remote_packages(tarball.manifest)
271
-
272
- say('Checking if can repack release for faster upload...')
273
- repacked_path = tarball.repack(package_matches)
274
-
275
- if repacked_path.nil?
276
- say('Uploading the whole release'.make_green)
277
- else
278
- say("Release repacked (new size is #{pretty_size(repacked_path)})".make_green)
279
- tarball_path = repacked_path
280
- end
281
- end
282
- rescue Bosh::Cli::DirectorError
283
- # It's OK for director to choke on getting
284
- # a release info (think new releases)
285
- end
286
-
287
- if rebase
288
- say("Uploading release (#{'will be rebased'.make_yellow})")
289
- status, task_id = director.rebase_release(tarball_path)
290
- task_report(status, task_id, 'Release rebased')
291
- else
292
- say("\nUploading release\n")
293
- status, task_id = director.upload_release(tarball_path)
294
- task_report(status, task_id, 'Release uploaded')
295
- end
296
- end
297
-
298
- def upload_remote_release(release_location, upload_options = {})
299
- nl
300
- if upload_options[:rebase]
301
- say("Using remote release `#{release_location}' (#{'will be rebased'.make_yellow})")
302
- status, task_id = director.rebase_remote_release(release_location)
303
- task_report(status, task_id, 'Release rebased')
304
- else
305
- say("Using remote release `#{release_location}'")
306
- status, task_id = director.upload_remote_release(release_location)
307
- task_report(status, task_id, 'Release uploaded')
308
- end
309
- end
310
-
311
- def create_from_spec(version)
312
- final = options[:final]
313
- force = options[:force]
314
- manifest_only = !options[:with_tarball]
315
- dry_run = options[:dry_run]
316
-
317
- err("Can't create final release without blobstore secret") if final && !release.has_blobstore_secret?
318
-
319
- dirty_blob_check(force)
320
-
321
- raise_dirty_state_error if dirty_state? && !force
322
-
323
- if final
324
- confirm_final_release(dry_run)
325
- save_final_release_name if release.final_name.blank?
326
- header('Building FINAL release'.make_green)
327
- else
328
- save_dev_release_name if release.dev_name.blank?
329
- header('Building DEV release'.make_green)
330
- end
331
-
332
- header('Building packages')
333
- packages = build_packages(dry_run, final)
334
-
335
- header('Building jobs')
336
- jobs = build_jobs(packages.map(&:name), dry_run, final)
337
-
338
- header('Building release')
339
- release_builder = build_release(dry_run, final, jobs, manifest_only, packages, version)
340
-
341
- header('Release summary')
342
- show_summary(release_builder)
343
- nl
344
-
345
- return nil if dry_run
346
-
347
- say("Release version: #{release_builder.version.to_s.make_green}")
348
- say("Release manifest: #{release_builder.manifest_path.make_green}")
349
-
350
- unless manifest_only
351
- say("Release tarball (#{pretty_size(release_builder.tarball_path)}): " +
352
- release_builder.tarball_path.make_green)
353
- end
354
-
355
- release.save_config
356
-
357
- release_builder.manifest_path
358
- end
359
-
360
- def confirm_final_release(dry_run)
361
- confirmed = non_interactive? || agree("Are you sure you want to generate #{'final'.make_red} version? ")
362
- if !dry_run && !confirmed
363
- say('Canceled release generation'.make_green)
364
- exit(1)
365
- end
366
- end
367
-
368
- def dirty_blob_check(force)
369
- blob_manager.sync
370
- if blob_manager.dirty?
371
- blob_manager.print_status
372
- if force
373
- say("Proceeding with dirty blobs as '--force' is given".make_red)
374
- else
375
- err("Please use '--force' or upload new blobs")
376
- end
377
- end
378
- end
379
-
380
- def build_packages(dry_run, final)
381
- packages = Bosh::Cli::PackageBuilder.discover(
382
- work_dir,
383
- :final => final,
384
- :blobstore => release.blobstore,
385
- :dry_run => dry_run
386
- )
387
-
388
- packages.each do |package|
389
- say("Building #{package.name.make_green}...")
390
- package.build
391
- nl
392
- end
393
-
394
- if packages.size > 0
395
- package_index = packages.inject({}) do |index, package|
396
- index[package.name] = package.dependencies
397
- index
398
- end
399
- sorted_packages = tsort_packages(package_index)
400
- header('Resolving dependencies')
401
- say('Dependencies resolved, correct build order is:')
402
- sorted_packages.each do |package_name|
403
- say('- %s' % [package_name])
404
- end
405
- nl
406
- end
407
-
408
- packages
409
- end
410
-
411
- def build_release(dry_run, final, jobs, manifest_only, packages, version)
412
- release_builder = Bosh::Cli::ReleaseBuilder.new(release, packages, jobs, final: final,
413
- commit_hash: commit_hash, version: version,
414
- uncommitted_changes: dirty_state?)
415
-
416
- unless dry_run
417
- if manifest_only
418
- release_builder.build(:generate_tarball => false)
419
- else
420
- release_builder.build(:generate_tarball => true)
421
- end
422
- end
423
-
424
- release_builder
425
- end
426
-
427
- def build_jobs(built_package_names, dry_run, final)
428
- jobs = Bosh::Cli::JobBuilder.discover(
429
- work_dir,
430
- :final => final,
431
- :blobstore => release.blobstore,
432
- :dry_run => dry_run,
433
- :package_names => built_package_names
434
- )
435
-
436
- jobs.each do |job|
437
- say("Building #{job.name.make_green}...")
438
- job.build
439
- nl
440
- end
441
-
442
- jobs
443
- end
444
-
445
- def save_final_release_name
446
- release.final_name = DEFAULT_RELEASE_NAME
447
- if interactive?
448
- release.final_name = ask('Please enter final release name: ').to_s
449
- err('Canceled release creation, no name given') if release.final_name.blank?
450
- end
451
- release.save_config
452
- end
453
-
454
- def save_dev_release_name
455
- if interactive?
456
- release.dev_name = ask('Please enter development release name: ') do |q|
457
- q.default = release.final_name if release.final_name
458
- end.to_s
459
- err('Canceled release creation, no name given') if release.dev_name.blank?
460
- else
461
- release.dev_name = release.final_name ? release.final_name : DEFAULT_RELEASE_NAME
462
- end
463
- release.save_config
464
- end
465
-
466
- def git_init
467
- out = %x{git init 2>&1}
468
- if $? != 0
469
- say("error running 'git init':\n#{out}")
470
- else
471
- File.open('.gitignore', 'w') do |f|
472
- f << <<-EOS.gsub(/^\s{10}/, '')
473
- config/dev.yml
474
- config/private.yml
475
- releases/*.tgz
476
- dev_releases
477
- .blobs
478
- blobs
479
- .dev_builds
480
- .idea
481
- .DS_Store
482
- .final_builds/jobs/**/*.tgz
483
- .final_builds/packages/**/*.tgz
484
- *.swp
485
- *~
486
- *#
487
- #*
488
- EOS
489
- end
490
- end
491
- rescue Errno::ENOENT
492
- say("Unable to run 'git init'".make_red)
493
- end
494
-
495
- # if we aren't already in a release directory, try going up two levels
496
- # to see if that is a release directory, and then use that as the base
497
- def find_release_dir(manifest_path)
498
- unless in_release_dir?
499
- dir = File.expand_path('../..', manifest_path)
500
- Dir.chdir(dir)
501
- if in_release_dir?
502
- @release = Bosh::Cli::Release.new(dir)
503
- end
504
- end
505
-
506
- end
507
-
508
- def show_summary(builder)
509
- packages_table = table do |t|
510
- t.headings = %w(Name Version Notes)
511
- builder.packages.each do |package|
512
- t << artefact_summary(package)
513
- end
514
- end
515
-
516
- jobs_table = table do |t|
517
- t.headings = %w(Name Version Notes)
518
- builder.jobs.each do |job|
519
- t << artefact_summary(job)
520
- end
521
- end
522
-
523
- say('Packages')
524
- say(packages_table)
525
- nl
526
- say('Jobs')
527
- say(jobs_table)
528
-
529
- affected_jobs = builder.affected_jobs
530
-
531
- if affected_jobs.size > 0
532
- nl
533
- say('Jobs affected by changes in this release')
534
-
535
- affected_jobs_table = table do |t|
536
- t.headings = %w(Name Version)
537
- affected_jobs.each do |job|
538
- t << [job.name, job.version]
539
- end
540
- end
541
-
542
- say(affected_jobs_table)
543
- end
544
- end
545
-
546
- def artefact_summary(artefact)
547
- result = []
548
- result << artefact.name
549
- result << artefact.version
550
- result << artefact.notes.join(', ')
551
- result
552
- end
553
-
554
- def get_remote_release(name)
555
- release = director.get_release(name)
556
-
557
- unless release.is_a?(Hash) &&
558
- release.has_key?('jobs') &&
559
- release.has_key?('packages')
560
- raise Bosh::Cli::DirectorError,
561
- 'Cannot find version, jobs and packages info in the director response, maybe old director?'
562
- end
563
-
564
- release
565
- end
566
-
567
- def match_remote_packages(manifest_yaml)
568
- director.match_packages(manifest_yaml)
569
- rescue Bosh::Cli::DirectorError
570
- msg = "You are using CLI >= 0.20 with director that doesn't support " +
571
- "package matches.\nThis will result in uploading all packages " +
572
- "and jobs to your director.\nIt is recommended to update your " +
573
- 'director or downgrade your CLI to 0.19.6'
574
-
575
- say(msg.make_yellow)
576
- exit(1) unless confirmed?
577
- end
578
-
579
- def build_releases_table_for_old_director(releases)
580
- table do |t|
581
- t.headings = 'Name', 'Versions'
582
- releases.each do |release|
583
- versions = release['versions'].sort { |v1, v2|
584
- Bosh::Common::Version::ReleaseVersion.parse_and_compare(v1, v2)
585
- }.map { |v| ((release['in_use'] || []).include?(v)) ? "#{v}*" : v }
586
-
587
- t << [release['name'], versions.join(', ')]
588
- end
589
- end
590
- end
591
-
592
- # Builds table of release information
593
- # Default headings: "Name", "Versions", "Commit Hash"
594
- # Extra headings: options[:job] => "Jobs"
595
- def build_releases_table(releases, options = {})
596
- show_jobs = options[:jobs]
597
- table do |t|
598
- t.headings = 'Name', 'Versions', 'Commit Hash'
599
- t.headings << 'Jobs' if show_jobs
600
- releases.each do |release|
601
- versions, commit_hashes = formatted_versions(release).transpose
602
- row = [release['name'], versions.join("\n"), commit_hashes.join("\n")]
603
- if show_jobs
604
- jobs = formatted_jobs(release).transpose
605
- row << jobs.join("\n")
606
- end
607
- t << row
608
- end
609
- end
610
- end
611
-
612
- def formatted_versions(release)
613
- sort_versions(release['release_versions']).map { |v| formatted_version_and_commit_hash(v) }
614
- end
615
-
616
- def sort_versions(versions)
617
- versions.sort { |v1, v2| Bosh::Common::Version::ReleaseVersion.parse_and_compare(v1['version'], v2['version']) }
618
- end
619
-
620
- def formatted_version_and_commit_hash(version)
621
- version_number = version['version'] + (version['currently_deployed'] ? '*' : '')
622
- commit_hash = version['commit_hash'] + (version['uncommitted_changes'] ? '+' : '')
623
- [version_number, commit_hash]
624
- end
625
-
626
- def formatted_jobs(release)
627
- sort_versions(release['release_versions']).map do |v|
628
- if job_names = v['job_names']
629
- [job_names.join(', ')]
630
- else
631
- ['n/a '] # with enough whitespace to match "Jobs" header
632
- end
633
- end
634
- end
635
-
636
- def commit_hash
637
- status = Bosh::Exec.sh('git show-ref --head --hash=8 2> /dev/null')
638
- status.output.split.first
639
- rescue Bosh::Exec::Error => e
640
- '00000000'
641
- end
642
-
643
- def release_version_details(releases)
644
- currently_deployed = false
645
- uncommitted_changes = false
646
- releases.each do |release|
647
- release['release_versions'].each do |version|
648
- currently_deployed ||= version['currently_deployed']
649
- uncommitted_changes ||= version['uncommitted_changes']
650
- if currently_deployed && uncommitted_changes
651
- return true, true
652
- end
653
- end
654
- end
655
- return currently_deployed, uncommitted_changes
656
- end
657
-
658
- def release_version_details_for_old_director(releases)
659
- currently_deployed = false
660
- # old director did not support uncommitted changes
661
- uncommitted_changes = false
662
- releases.each do |release|
663
- currently_deployed ||= release['in_use'].any?
664
- if currently_deployed
665
- return true, uncommitted_changes
666
- end
667
- end
668
- return currently_deployed, uncommitted_changes
669
- end
670
-
671
- def should_convert_to_old_format?(version)
672
- director_version = director.get_status['version']
673
- new_format_director_version = '1.2580.0'
674
- if Bosh::Common::Version::BoshVersion.parse(director_version) >=
675
- Bosh::Common::Version::BoshVersion.parse(new_format_director_version)
676
- return false
677
- end
678
-
679
- old_format = Bosh::Common::Version::ReleaseVersion.parse(version).to_old_format
680
- old_format && version != old_format
681
- end
682
-
683
- end
684
- end