bosh_cli 1.2831.0 → 1.2839.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,32 @@
1
+ module Bosh::Cli
2
+ class GlobMatch
3
+ # Helper class encapsulating the data we know about the glob. We need
4
+ # both directory and file path, as we match the same path in several
5
+ # directories (src, src_alt, blobs)
6
+ attr_reader :dir
7
+ attr_reader :path
8
+
9
+ def initialize(dir, path)
10
+ @dir = dir
11
+ @path = path
12
+ end
13
+
14
+ def full_path
15
+ File.join(dir, path)
16
+ end
17
+
18
+ def <=>(other)
19
+ @path <=> other.path
20
+ end
21
+
22
+ # GlobMatch will be used as Hash key (as implied by using Set),
23
+ # hence we need to define both eql? and hash
24
+ def eql?(other)
25
+ @path == other.path
26
+ end
27
+
28
+ def hash
29
+ @path.hash
30
+ end
31
+ end
32
+ end
@@ -8,12 +8,12 @@ module Bosh::Cli
8
8
  include Enumerable
9
9
  include Bosh::Template::PropertyHelper
10
10
 
11
- # @param [JobBuilder] job_builder Which job this property collection is for
11
+ # @param [Bosh::Cli::Resources::Job] job Which job this property collection is for
12
12
  # @param [Hash] global_properties Globally defined properties
13
13
  # @param [Hash] job_properties Properties defined for this job only
14
14
  # @param [Hash] mappings Property mappings for this job
15
- def initialize(job_builder, global_properties, job_properties, mappings)
16
- @job_builder = job_builder
15
+ def initialize(job, global_properties, job_properties, mappings)
16
+ @job = job
17
17
 
18
18
  @job_properties = Bosh::Common::DeepCopy.copy(job_properties || {})
19
19
  merge(@job_properties, Bosh::Common::DeepCopy.copy(global_properties))
@@ -52,7 +52,7 @@ module Bosh::Cli
52
52
 
53
53
  # @return [void] Modifies @properties
54
54
  def filter_properties
55
- if @job_builder.properties.empty?
55
+ if @job.properties.empty?
56
56
  # If at least one template doesn't have properties defined, we
57
57
  # need all properties to be available to job (backward-compatibility)
58
58
  @properties = @job_properties
@@ -61,7 +61,7 @@ module Bosh::Cli
61
61
 
62
62
  @properties = {}
63
63
 
64
- @job_builder.properties.each_pair do |name, definition|
64
+ @job.properties.each_pair do |name, definition|
65
65
  copy_property(
66
66
  @properties, @job_properties, name, definition["default"])
67
67
  end
@@ -7,7 +7,7 @@ module Bosh::Cli
7
7
  attr_reader :template_errors
8
8
  attr_reader :jobs_without_properties
9
9
 
10
- # @param [Array<JobBuilder>] built_jobs Built job templates
10
+ # @param [Array<Bosh::Cli::Resources::Job>] built_jobs Built job templates
11
11
  # @param [Hash] manifest Deployment manifest
12
12
  def initialize(built_jobs, manifest)
13
13
  @built_jobs = {}
@@ -44,7 +44,7 @@ module Bosh::Cli
44
44
  end
45
45
 
46
46
  if job["template"].nil?
47
- bad_manifest("Job `#{job_name}' doesn't have a template")
47
+ bad_manifest("Job '#{job_name}' doesn't have a template")
48
48
  end
49
49
  end
50
50
 
@@ -68,7 +68,7 @@ module Bosh::Cli
68
68
  built_job = @built_jobs[job_spec["template"]]
69
69
 
70
70
  if built_job.nil?
71
- raise CliError, "Job `#{job_spec["template"]}' has not been built"
71
+ raise CliError, "Job '#{job_spec["template"]}' has not been built"
72
72
  end
73
73
 
74
74
  collection = JobPropertyCollection.new(
@@ -86,8 +86,8 @@ module Bosh::Cli
86
86
  'properties' => collection.to_hash
87
87
  }
88
88
 
89
- built_job.all_templates.each do |template_path|
90
- evaluate_template(built_job, template_path, spec)
89
+ built_job.files.each do |file_tuple|
90
+ evaluate_template(built_job, file_tuple.first, spec)
91
91
  end
92
92
  end
93
93
 
@@ -104,7 +104,7 @@ module Bosh::Cli
104
104
 
105
105
  private
106
106
 
107
- # @param [JobBuilder] job Job builder
107
+ # @param [Bosh::Cli::Resources::Job] job Job builder
108
108
  # @param [String] template_path Template path
109
109
  # @param [Hash] spec Fake instance spec
110
110
  def evaluate_template(job, template_path, spec)
@@ -127,7 +127,7 @@ module Bosh::Cli
127
127
  attr_reader :exception
128
128
  attr_reader :line
129
129
 
130
- # @param [JobBuilder] job
130
+ # @param [Bosh::Cli::Resources::Job] job
131
131
  # @param [String] template_path
132
132
  # @param [Exception] exception
133
133
  def initialize(job, template_path, exception)
@@ -138,4 +138,4 @@ module Bosh::Cli
138
138
  end
139
139
  end
140
140
  end
141
- end
141
+ end
@@ -5,16 +5,16 @@ module Bosh::Cli
5
5
  attr_reader :release, :packages, :jobs, :name, :version, :build_dir, :commit_hash, :uncommitted_changes
6
6
 
7
7
  # @param [Bosh::Cli::Release] release Current release
8
- # @param [Array<Bosh::Cli::PackageBuilder>] packages Built packages
9
- # @param [Array<Bosh::Cli::JobBuilder>] jobs Built jobs
8
+ # @param [Array<Bosh::Cli::BuildArtifact>] package_artifacts Built packages
9
+ # @param [Array<Bosh::Cli::BuildArtifact>] job_artifacts Built jobs
10
10
  # @param [Hash] options Release build options
11
- def initialize(release, packages, jobs, name, options = { })
11
+ def initialize(release, package_artifacts, job_artifacts, name, options = { })
12
12
  @release = release
13
13
  @final = options.has_key?(:final) ? !!options[:final] : false
14
14
  @commit_hash = options.fetch(:commit_hash, '00000000')
15
15
  @uncommitted_changes = options.fetch(:uncommitted_changes, true)
16
- @packages = packages
17
- @jobs = jobs
16
+ @packages = package_artifacts # todo
17
+ @jobs = job_artifacts # todo
18
18
  @name = name
19
19
  raise 'Release name is blank' if name.blank?
20
20
 
@@ -49,19 +49,18 @@ module Bosh::Cli
49
49
  @final
50
50
  end
51
51
 
52
- # @return [Array] List of jobs affected by this release compared
53
- # to the previous one.
52
+ # @return [Array<Bosh::Cli::BuildArtifact>] List of job artifacts
53
+ # affected by this release compared to the previous one.
54
54
  def affected_jobs
55
- result = Set.new(@jobs.select { |job| job.new_version? })
56
- return result if @packages.empty?
55
+ result = Set.new(@jobs.select { |job_artifact| job_artifact.new_version? })
56
+ return result.to_a if @packages.empty?
57
57
 
58
- new_package_names = @packages.inject([]) do |list, package|
59
- list << package.name if package.new_version?
60
- list
61
- end
58
+ new_package_names = @packages.map do |package_artifact|
59
+ package_artifact.name if package_artifact.new_version?
60
+ end.compact
62
61
 
63
62
  @jobs.each do |job|
64
- result << job if (new_package_names & job.packages).size > 0
63
+ result << job if (new_package_names & job.dependencies).size > 0
65
64
  end
66
65
 
67
66
  result.to_a
@@ -83,22 +82,25 @@ module Bosh::Cli
83
82
 
84
83
  # Copies packages into release
85
84
  def copy_packages
86
- packages.each do |package|
87
- say("%-40s %s" % [package.name.make_green,
88
- pretty_size(package.tarball_path)])
89
- FileUtils.cp(package.tarball_path,
90
- File.join(build_dir, "packages", "#{package.name}.tgz"),
85
+ packages.each do |package_artifact|
86
+ name = package_artifact.name
87
+ tarball_path = package_artifact.tarball_path
88
+ say("%-40s %s" % [name.make_green, pretty_size(tarball_path)])
89
+ FileUtils.cp(tarball_path,
90
+ File.join(build_dir, "packages", "#{name}.tgz"),
91
91
  :preserve => true)
92
92
  end
93
93
  @packages_copied = true
94
94
  end
95
95
 
96
- # Copies jobs into release
96
+ # Copies jobs into release todo DRY vs copy_packages
97
97
  def copy_jobs
98
- jobs.each do |job|
99
- say("%-40s %s" % [job.name.make_green, pretty_size(job.tarball_path)])
100
- FileUtils.cp(job.tarball_path,
101
- File.join(build_dir, "jobs", "#{job.name}.tgz"),
98
+ jobs.each do |job_artifact|
99
+ name = job_artifact.name
100
+ tarball_path = job_artifact.tarball_path
101
+ say("%-40s %s" % [name.make_green, pretty_size(tarball_path)])
102
+ FileUtils.cp(tarball_path,
103
+ File.join(build_dir, "jobs", "#{name}.tgz"),
102
104
  :preserve => true)
103
105
  end
104
106
  @jobs_copied = true
@@ -107,40 +109,37 @@ module Bosh::Cli
107
109
  # Generates release manifest
108
110
  def generate_manifest
109
111
  manifest = {}
110
- manifest["packages"] = []
111
-
112
- manifest["packages"] = packages.map do |package|
112
+ manifest['packages'] = packages.map do |build_artifact|
113
113
  {
114
- "name" => package.name,
115
- "version" => package.version,
116
- "sha1" => package.checksum,
117
- "fingerprint" => package.fingerprint,
118
- "dependencies" => package.dependencies
114
+ 'name' => build_artifact.name,
115
+ 'version' => build_artifact.version,
116
+ 'fingerprint' => build_artifact.fingerprint,
117
+ 'sha1' => build_artifact.sha1,
118
+ 'dependencies' => build_artifact.dependencies,
119
119
  }
120
120
  end
121
-
122
- manifest["jobs"] = jobs.map do |job|
121
+ manifest['jobs'] = jobs.map do |build_artifact|
123
122
  {
124
- "name" => job.name,
125
- "version" => job.version,
126
- "fingerprint" => job.fingerprint,
127
- "sha1" => job.checksum,
123
+ 'name' => build_artifact.name,
124
+ 'version' => build_artifact.version,
125
+ 'fingerprint' => build_artifact.fingerprint,
126
+ 'sha1' => build_artifact.sha1,
128
127
  }
129
128
  end
130
129
 
131
- manifest["commit_hash"] = commit_hash
132
- manifest["uncommitted_changes"] = uncommitted_changes
130
+ manifest['commit_hash'] = commit_hash
131
+ manifest['uncommitted_changes'] = uncommitted_changes
133
132
 
134
133
  unless @name.bosh_valid_id?
135
- raise InvalidRelease, "Release name `#{@name}' is not a valid BOSH identifier"
134
+ raise InvalidRelease, "Release name '#{@name}' is not a valid BOSH identifier"
136
135
  end
137
- manifest["name"] = @name
136
+ manifest['name'] = @name
138
137
 
139
138
  # New release versions are allowed to have the same fingerprint as old versions.
140
139
  # For reverse compatibility, random uuids are stored instead.
141
- @index.add_version(SecureRandom.uuid, { "version" => version })
140
+ @index.add_version(SecureRandom.uuid, { 'version' => version })
142
141
 
143
- manifest["version"] = version
142
+ manifest['version'] = version
144
143
  manifest_yaml = Psych.dump(manifest)
145
144
 
146
145
  say("Writing manifest...")
@@ -14,13 +14,13 @@ module Bosh::Cli
14
14
  # @param [Bosh::Blobstore::Client] blobstore Blobstore client
15
15
  # @param [Array] package_matches List of package checksums that director
16
16
  # can match
17
- # @param [String] release_dir Release directory
17
+ # @param [String] release_source Release directory
18
18
  def initialize(manifest_file, blobstore,
19
- package_matches = [], release_dir = nil)
19
+ package_matches = [], release_source = nil)
20
20
 
21
21
  @blobstore = blobstore
22
- @release_dir = release_dir || Dir.pwd
23
- @manifest_file = File.expand_path(manifest_file, @release_dir)
22
+ @release_source = release_source || Dir.pwd
23
+ @manifest_file = File.expand_path(manifest_file, @release_source)
24
24
  @tarball_path = nil
25
25
 
26
26
  @build_dir = Dir.mktmpdir
@@ -95,18 +95,18 @@ module Bosh::Cli
95
95
 
96
96
  def find_package(package)
97
97
  name = package.name
98
- final_package_dir = File.join(@release_dir, '.final_builds', 'packages', name)
98
+ final_package_dir = File.join(@release_source, '.final_builds', 'packages', name)
99
99
  final_index = Versions::VersionsIndex.new(final_package_dir)
100
- dev_package_dir = File.join(@release_dir, '.dev_builds', 'packages', name)
100
+ dev_package_dir = File.join(@release_source, '.dev_builds', 'packages', name)
101
101
  dev_index = Versions::VersionsIndex.new(dev_package_dir)
102
102
  find_in_indices(final_index, dev_index, package, 'package')
103
103
  end
104
104
 
105
105
  def find_job(job)
106
106
  name = job.name
107
- final_jobs_dir = File.join(@release_dir, '.final_builds', 'jobs', name)
107
+ final_jobs_dir = File.join(@release_source, '.final_builds', 'jobs', name)
108
108
  final_index = Versions::VersionsIndex.new(final_jobs_dir)
109
- dev_jobs_dir = File.join(@release_dir, '.dev_builds', 'jobs', name)
109
+ dev_jobs_dir = File.join(@release_source, '.dev_builds', 'jobs', name)
110
110
  dev_index = Versions::VersionsIndex.new(dev_jobs_dir)
111
111
  find_in_indices(final_index, dev_index, job, 'job')
112
112
  end
@@ -148,9 +148,9 @@ module Bosh::Cli
148
148
  # @return [Boolean]
149
149
  def remote_package_exists?(local_package)
150
150
  # If checksum is known to director we can always match it
151
- @package_matches.include?(local_package.sha1) ||
152
- (local_package.fingerprint &&
153
- @package_matches.include?(local_package.fingerprint))
151
+ @package_matches.include?(local_package.sha1) || # !!! Needs test coverage
152
+ (local_package.fingerprint && # !!! Needs test coverage
153
+ @package_matches.include?(local_package.fingerprint)) # !!! Needs test coverage
154
154
  end
155
155
 
156
156
  # Checks if local job is already known remotely
@@ -0,0 +1,190 @@
1
+ module Bosh::Cli::Resources
2
+ class Job
3
+ BUILD_HOOK_FILES = ['prepare']
4
+
5
+ # @param [String] directory base Release directory
6
+ def self.discover(release_base, packages)
7
+ Dir[File.join(release_base, 'jobs', '*')].inject([]) do |jobs, job_base|
8
+ next unless File.directory?(job_base)
9
+ jobs << new(job_base, release_base, packages)
10
+ end
11
+ end
12
+
13
+ attr_reader :job_base, :release_base, :package_dependencies
14
+
15
+ def initialize(job_base, release_base, packages)
16
+ @release_base = Pathname.new(release_base)
17
+ @job_base = Pathname.new(job_base)
18
+ @package_dependencies = packages
19
+ end
20
+
21
+ def spec
22
+ @spec ||= load_yaml_file(job_base.join('spec'))
23
+ rescue
24
+ raise Bosh::Cli::InvalidJob, 'Job spec is missing'
25
+ end
26
+
27
+ def name
28
+ spec['name']
29
+ end
30
+
31
+ def dependencies
32
+ package_dependencies #TODO: should this be packages or package_dependencies?
33
+ end
34
+
35
+ def singular_type
36
+ 'job'
37
+ end
38
+
39
+ def plural_type
40
+ 'jobs'
41
+ end
42
+
43
+ def files
44
+ validate!
45
+
46
+ files = (templates_files + monit_files).map { |absolute_path| [absolute_path, relative_path(absolute_path)] }
47
+ files << [File.join(job_base, 'spec'), 'job.MF']
48
+ files
49
+ end
50
+
51
+ # TODO: check dependency packages
52
+ def validate!
53
+ if name.blank?
54
+ raise Bosh::Cli::InvalidJob, 'Job name is missing'
55
+ end
56
+
57
+ unless name.bosh_valid_id?
58
+ raise Bosh::Cli::InvalidJob, "'#{name}' is not a valid BOSH identifier"
59
+ end
60
+
61
+ unless spec['templates'].is_a?(Hash)
62
+ raise Bosh::Cli::InvalidJob, "Incorrect templates section in '#{name}' job spec (Hash expected, #{spec['templates'].class} given)"
63
+ end
64
+
65
+ if extra_templates.size > 0
66
+ raise Bosh::Cli::InvalidJob, "There are unused template files for job '#{name}': #{extra_templates.join(", ")}"
67
+ end
68
+
69
+ if missing_templates.size > 0
70
+ raise Bosh::Cli::InvalidJob, "Some template files required by '#{name}' job are missing: #{missing_templates.join(", ")}"
71
+ end
72
+
73
+ if missing_packages.size > 0
74
+ raise Bosh::Cli::InvalidJob, "Some packages required by '#{name}' job are missing: #{missing_packages.join(", ")}"
75
+ end
76
+
77
+ if spec.has_key?('properties')
78
+ unless spec['properties'].is_a?(Hash)
79
+ raise Bosh::Cli::InvalidJob, "Incorrect properties section in '#{name}' job spec (Hash expected, #{spec['properties'].class} given)"
80
+ end
81
+ end
82
+
83
+ unless monit_files.size > 0
84
+ raise Bosh::Cli::InvalidJob, "Cannot find monit file for '#{name}'"
85
+ end
86
+ end
87
+
88
+ def additional_fingerprints
89
+ []
90
+ end
91
+
92
+ def format_fingerprint(digest, filename, name, file_mode)
93
+ "%s%s%s" % [File.basename(filename), digest, file_mode]
94
+ end
95
+
96
+ def run_script(script_name, *args)
97
+ if BUILD_HOOK_FILES.include?(script_name.to_s)
98
+ send(:"run_script_#{script_name}", *args)
99
+ end
100
+ end
101
+
102
+ def properties
103
+ spec['properties'] || {}
104
+ end
105
+
106
+ private
107
+
108
+ def extra_templates
109
+ return [] if !File.directory?(templates_dir)
110
+
111
+ Dir.chdir(templates_dir) do
112
+ Dir["**/*"].reject do |file|
113
+ File.directory?(file) || templates.include?(file)
114
+ end
115
+ end
116
+ end
117
+
118
+ def missing_packages
119
+ @missing_packages ||= (packages - package_dependencies)
120
+ end
121
+
122
+ def missing_templates
123
+ templates.select do |template|
124
+ !File.exists?(File.join(templates_dir, template))
125
+ end
126
+ end
127
+
128
+ def monit_files
129
+ monit = File.join(job_base, 'monit')
130
+ files = Dir.glob(File.join(job_base, '*.monit'))
131
+ files << monit if File.exist?(monit)
132
+ files
133
+ end
134
+
135
+ def packages
136
+ spec['packages'] || []
137
+ end
138
+
139
+ def relative_path(path)
140
+ Pathname.new(path).relative_path_from(job_base).to_s
141
+ end
142
+
143
+ def templates
144
+ spec['templates'].keys
145
+ end
146
+
147
+ def templates_dir
148
+ @templates_dir ||= job_base.join('templates')
149
+ end
150
+
151
+ def templates_files
152
+ templates.map { |file| File.join(templates_dir, file) }
153
+ end
154
+
155
+ def run_script_prepare
156
+ script_path = File.join(job_base, 'prepare')
157
+
158
+ return nil unless File.exists?(script_path)
159
+
160
+ unless File.executable?(script_path)
161
+ raise Bosh::Cli::InvalidJob, "Prepare script at '#{script_path}' is not executable"
162
+ end
163
+
164
+ old_env = ENV
165
+ script_dir = File.dirname(script_path)
166
+ script_name = File.basename(script_path)
167
+
168
+ begin
169
+ # We need to temporarily delete some rubygems related artefacts
170
+ # because preparation scripts shouldn't share any assumptions
171
+ # with CLI itself
172
+ %w{ BUNDLE_GEMFILE RUBYOPT }.each { |key| ENV.delete(key) }
173
+
174
+ output = nil
175
+ Dir.chdir(script_dir) do
176
+ cmd = "./#{script_name} 2>&1"
177
+ output = `#{cmd}`
178
+ end
179
+
180
+ unless $?.exitstatus == 0
181
+ raise Bosh::Cli::InvalidJob, "'#{script_path}' script failed: #{output}"
182
+ end
183
+
184
+ output
185
+ ensure
186
+ ENV.each_pair { |k, v| ENV[k] = old_env[k] }
187
+ end
188
+ end
189
+ end
190
+ end