bosh_cli 1.2671.0 → 1.2682.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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