vara 0.17.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ require 'yaml'
2
+ require 'vara/log'
3
+
4
+ module Vara
5
+ class ContentMigrationsProcessor
6
+ include Vara::Loggable
7
+
8
+ # @param product_dir [String] path to product directory root
9
+ # @param versioner [Vara::PrereleaseVersioner,Vara::StaticVersioner]
10
+ def initialize(product_dir, versioner)
11
+ @product_dir = product_dir
12
+ @versioner = versioner
13
+ end
14
+
15
+ # Composes content_migrations_parts/base.yml and content_migrations_parts/migrations/*.yml
16
+ # into content_migrations/migrations.yml, expanding placeholders as necessary.
17
+ # @return [String] path to the generated content migration file
18
+ def process
19
+ content_migrations = load_content_migrations
20
+ processed = versioner.update_content_migrations(content_migrations)
21
+ save_content_migrations(processed)
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :product_dir, :versioner
27
+
28
+ def load_content_migrations
29
+ parts_dir = File.join(product_dir, 'content_migrations_parts')
30
+ base_path = File.join(parts_dir, 'base.yml')
31
+ content_migrations = YAML.load_file(base_path) || {}
32
+ log.info("Composing content migrations: loaded #{base_path}")
33
+ Dir.glob(File.join(parts_dir, 'migrations', '*.yml')).each do |migration_path|
34
+ migration = YAML.load_file(migration_path)
35
+ log.info("Composing content migrations: loaded #{migration_path}")
36
+ content_migrations['migrations'] ||= []
37
+ content_migrations['migrations'] << migration
38
+ end
39
+
40
+ content_migrations
41
+ end
42
+
43
+ def save_content_migrations(processed_hash)
44
+ out_path = File.join(product_dir, 'content_migrations', 'migrations.yml')
45
+
46
+ File.open(out_path, 'w') do |out|
47
+ YAML.dump(processed_hash, out)
48
+ end
49
+
50
+ out_path
51
+ end
52
+ end
53
+ end
54
+
55
+ # Copyright (c) 2014-2015 Pivotal Software, Inc.
56
+ # All rights reserved.
57
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
58
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
59
+ # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
60
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
61
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
62
+ # USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,61 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'aws-sdk'
4
+
5
+ module Vara
6
+ class Downloader
7
+ # @param source [String] The URL from which to download
8
+ # @param target [String] The path on disk for where to save the downloaded file
9
+ def self.download(metadata, target)
10
+ fail 'URL or AWS Config required to download!' unless metadata.url || metadata.aws
11
+ if metadata.aws
12
+ download_from_aws(metadata.aws, target)
13
+ elsif metadata.url
14
+ download_from_url(metadata.url, target)
15
+ end
16
+ end
17
+
18
+ def self.download_from_aws(aws_config, target)
19
+ s3 = Aws::S3::Client.new(
20
+ access_key_id: aws_config.access_key_id,
21
+ secret_access_key: aws_config.secret_access_key,
22
+ force_path_style: true,
23
+ region: aws_config.region
24
+ )
25
+
26
+ s3.get_object(
27
+ response_target: target,
28
+ bucket: aws_config.bucket_name,
29
+ key: aws_config.filename
30
+ )
31
+ end
32
+
33
+ def self.download_from_url(url, target)
34
+ uri = URI(url)
35
+ http = Net::HTTP.new(uri.host, uri.port)
36
+ http.use_ssl = uri.scheme == 'https'
37
+ http.start { |h| stream_target(h, target, uri) }
38
+ end
39
+
40
+ def self.stream_target(http, target, uri)
41
+ request = Net::HTTP::Get.new uri.request_uri
42
+
43
+ http.request request do |response|
44
+ return download_from_url(response['location'], target) if response.is_a?(Net::HTTPRedirection)
45
+
46
+ open target, 'w' do |io|
47
+ response.read_body { |chunk| io.write chunk }
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ # Copyright (c) 2014-2015 Pivotal Software, Inc.
55
+ # All rights reserved.
56
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
57
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
58
+ # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
59
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
60
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
61
+ # USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,66 @@
1
+ module Vara
2
+ class GitInspector
3
+ # @param repo_path [String] Path to the root of the repository to inspect
4
+ # @return [String] The SHA of the current git revision
5
+ def self.current_revision(repo_path)
6
+ new(repo_path).current_revision
7
+ end
8
+
9
+ # @param repo_path [String] Path to the root of the repository to inspect
10
+ # @return [Boolean]
11
+ def self.git_repo?(repo_path)
12
+ system("cd #{repo_path} && git rev-parse --is-inside-work-tree")
13
+ end
14
+
15
+ # @param repo_path [String] Path to the root of the repository to inspect
16
+ def initialize(repo_path)
17
+ fail "The directory #{repo_path} must be a git repo" unless self.class.git_repo?(repo_path)
18
+ @repo_path = repo_path
19
+ end
20
+
21
+ # @return [String] The SHA of the current git revision
22
+ def current_revision
23
+ revision = `cd #{repo_path} && git rev-list --max-count=1 HEAD`.chomp
24
+ [revision, dirty_suffix].join('')
25
+ end
26
+
27
+ # @return [Integer] The number of commits on this git branch
28
+ def commit_count
29
+ count = `cd #{repo_path} && git rev-list HEAD | wc -l`
30
+ Integer(count)
31
+ end
32
+
33
+ # @return [String] The abbreviated SHA of the current git revision
34
+ def short_sha
35
+ sha = `cd #{repo_path} && git rev-parse --short HEAD`.chomp
36
+ [sha, dirty_suffix].join('')
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :repo_path
42
+
43
+ def dirty?
44
+ # inspired by http://stackoverflow.com/a/5737794
45
+ `cd #{repo_path} && test -n "$(git status --porcelain)"`
46
+ $?.success?
47
+ end
48
+
49
+ def dirty_suffix
50
+ if dirty?
51
+ '.dirty'
52
+ else
53
+ ''
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ # Copyright (c) 2014-2015 Pivotal Software, Inc.
60
+ # All rights reserved.
61
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
62
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
63
+ # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
64
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
65
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
66
+ # USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,40 @@
1
+ require 'yaml'
2
+
3
+ module Vara
4
+ class Linter
5
+ def self.build(metadata_path)
6
+ new(YAML.load_file(metadata_path))
7
+ end
8
+
9
+ def initialize(metadata)
10
+ @metadata = metadata
11
+ @errors = []
12
+ end
13
+
14
+ def lint!
15
+ validate_metadata_version
16
+ fail LintError, errors.join("\n") unless errors.empty?
17
+ end
18
+
19
+ private
20
+
21
+ def validate_metadata_version
22
+ metadata_version = metadata.fetch('metadata_version')
23
+ errors << 'metadata_version must be a string' unless metadata_version.is_a?(String)
24
+ end
25
+
26
+ attr_reader :metadata, :errors
27
+ end
28
+
29
+ class LintError < RuntimeError
30
+ end
31
+ end
32
+
33
+ # Copyright (c) 2014-2015 Pivotal Software, Inc.
34
+ # All rights reserved.
35
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
36
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
37
+ # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
38
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
39
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
40
+ # USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,71 @@
1
+ require 'logger'
2
+
3
+ module Vara
4
+ class << self
5
+ def log
6
+ Log.instance
7
+ end
8
+
9
+ def logger_for(progname)
10
+ LoggerWithProgName.new(progname)
11
+ end
12
+ end
13
+
14
+ module Loggable
15
+ def log
16
+ @_releng_instance_log ||= LoggerWithProgName.new(self.class.name)
17
+ end
18
+
19
+ def self.included(other_module)
20
+ def other_module.log
21
+ @_releng_class_log ||= LoggerWithProgName.new(name)
22
+ end
23
+ end
24
+ end
25
+
26
+ class Log
27
+ class << self
28
+ def instance
29
+ @instance || fail('Logging attempted without being configured first!')
30
+ end
31
+
32
+ def test_mode!
33
+ log_path = '/tmp/vara_test.log'
34
+ File.open(log_path, 'w') {} # empty out the file in a cross-platform-safe way
35
+ @instance = ::Logger.new(log_path, File::WRONLY | File::APPEND)
36
+ instance.sev_threshold = ::Logger::DEBUG
37
+ end
38
+
39
+ def stdout_mode!
40
+ STDOUT.sync = true
41
+ @instance = ::Logger.new(STDOUT)
42
+ instance.sev_threshold = ::Logger.const_get(ENV.fetch('LOG_LEVEL', 'INFO'))
43
+ end
44
+ end
45
+ end
46
+
47
+ class LoggerWithProgName
48
+ def initialize(progname)
49
+ @progname = progname
50
+ end
51
+
52
+ %w(debug info warn error fatal).map(&:to_sym).each do |level|
53
+ define_method(level) do |message|
54
+ Log.instance.public_send(level, progname) { message }
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ attr_reader :progname
61
+ end
62
+ end
63
+
64
+ # Copyright (c) 2014-2015 Pivotal Software, Inc.
65
+ # All rights reserved.
66
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
67
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
68
+ # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
69
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
70
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
71
+ # USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,139 @@
1
+ require 'yaml'
2
+ require 'digest/md5'
3
+ require 'vara/product_metadata'
4
+ require 'vara/git_inspector'
5
+ require 'vara/log'
6
+ require 'vara/version'
7
+
8
+ module Vara
9
+ class Materials
10
+ include Vara::Loggable
11
+
12
+ # @param materials_path [String] Path to a materials YAML file
13
+ # @return [Vara::Materials] A Materials object based on the content of the file at materials_path
14
+ def self.from_file(materials_path)
15
+ new(YAML.load_file(materials_path))
16
+ end
17
+
18
+ # @param product_metadata_path [String] Path to a product's metadata file.
19
+ # Expected to be in metadata/ folder of a product repo.
20
+ # @param product_path [String] Path to a built .pivotal file on disk
21
+ # @return [Vara::Materials]
22
+ def self.build(product_metadata_path, product_path)
23
+ product_metadata_dir = File.dirname(File.expand_path(product_metadata_path))
24
+ repo_dir = File.expand_path(File.join(product_metadata_dir, '..'))
25
+ repo_name = File.basename(repo_dir)
26
+
27
+ product_metadata = ProductMetadata.from_file(product_metadata_path)
28
+
29
+ repo_revision = if GitInspector.git_repo?(repo_dir)
30
+ GitInspector.new(repo_dir).current_revision
31
+ else
32
+ log.warn('Creating Materials from a non-git repository')
33
+ 'not_a_git_repository'
34
+ end
35
+
36
+ materials_hash = {
37
+ 'filename' => File.basename(product_path),
38
+ 'md5' => Digest::MD5.file(product_path).hexdigest,
39
+ 'contents' => {
40
+ 'releases' => product_metadata.releases_metadata.map(&:basename),
41
+ 'compiled_packages' => []
42
+ },
43
+ 'build_info' => {
44
+ 'product_repository' => repo_name,
45
+ 'product_revision' => repo_revision,
46
+ 'vara_version' => Vara::VERSION
47
+ }
48
+ }
49
+
50
+ if product_metadata.explicit_stemcell?
51
+ materials_hash['contents']['stemcells'] = [product_metadata.stemcell_metadata.basename]
52
+
53
+ if product_metadata.has_compiled_packages?
54
+ materials_hash['contents']['compiled_packages'] << product_metadata.compiled_packages_metadata.basename
55
+ end
56
+ else
57
+ materials_hash['contents']['stemcell_criteria'] = product_metadata.stemcell_criteria
58
+ end
59
+
60
+ new(materials_hash)
61
+ end
62
+
63
+ # @param materials_hash [Hash]
64
+ def initialize(materials_hash)
65
+ @hash = materials_hash
66
+ end
67
+
68
+ def save_to(file_path)
69
+ File.open(file_path, 'w') { |file| YAML.dump(hash, file) }
70
+ end
71
+
72
+ # @return [String] The filename of the .pivotal file
73
+ def filename
74
+ hash.fetch('filename')
75
+ end
76
+
77
+ # @return [String] The listed md5 of the .pivotal file
78
+ def md5
79
+ hash.fetch('md5')
80
+ end
81
+
82
+ # @return [<String>] The listed releases contained in the .pivotal file
83
+ def releases
84
+ contents.fetch('releases')
85
+ end
86
+
87
+ # @return [<String>] The listed stemcells contained in the .pivotal file
88
+ def stemcells
89
+ contents['stemcells']
90
+ end
91
+
92
+ def stemcell_criteria
93
+ contents['stemcell_criteria']
94
+ end
95
+
96
+ # @return [<String>] The listed compiled packages contained in the .pivotal file
97
+ def compiled_packages
98
+ contents.fetch('compiled_packages')
99
+ end
100
+
101
+ # @return [String] The name of the product repository
102
+ def product_repository
103
+ build_info.fetch('product_repository')
104
+ end
105
+
106
+ # @return [String] The git revision of the product repository used to build the .pivotal
107
+ def product_revision
108
+ build_info.fetch('product_revision')
109
+ end
110
+
111
+ # @return [String] the version of vara used to build the described .pivotal (will be >= 0.7.1 or 'unknown')
112
+ def vara_version
113
+ build_info.fetch('vara_version', 'unknown')
114
+ end
115
+
116
+ private
117
+
118
+ attr_reader :hash
119
+
120
+ # @return [Hash]
121
+ def contents
122
+ hash.fetch('contents')
123
+ end
124
+
125
+ # @return [Hash]
126
+ def build_info
127
+ hash.fetch('build_info')
128
+ end
129
+ end
130
+ end
131
+
132
+ # Copyright (c) 2014-2015 Pivotal Software, Inc.
133
+ # All rights reserved.
134
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
135
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
136
+ # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
137
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
138
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
139
+ # USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,31 @@
1
+ require 'vara/log'
2
+
3
+ module Vara
4
+ class MD5Creator
5
+ include Loggable
6
+
7
+ def self.md5(path)
8
+ expanded_path = File.expand_path(path)
9
+ md5_file = "#{expanded_path}.md5"
10
+
11
+ md5_string = Digest::MD5.file(expanded_path).hexdigest
12
+
13
+ log.warn("File #{md5_file} already exists, overwriting!") if File.exist?(md5_file)
14
+ File.open(md5_file, 'w') do |file|
15
+ file.write(md5_string)
16
+ log.info("Created md5 file: #{md5_file} with contents #{md5_string}")
17
+ end
18
+
19
+ md5_file
20
+ end
21
+ end
22
+ end
23
+
24
+ # Copyright (c) 2014-2015 Pivotal Software, Inc.
25
+ # All rights reserved.
26
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
27
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
28
+ # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
30
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
31
+ # USE OR OTHER DEALINGS IN THE SOFTWARE.