vara 0.17.1

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.
@@ -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.