minimart 0.0.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.
- checksums.yaml +15 -0
- data/.gitignore +21 -0
- data/.rspec +3 -0
- data/.travis.yml +13 -0
- data/Gemfile +3 -0
- data/README.md +198 -0
- data/Rakefile +45 -0
- data/bin/minimart +4 -0
- data/lib/minimart.rb +15 -0
- data/lib/minimart/cli.rb +93 -0
- data/lib/minimart/commands/mirror.rb +30 -0
- data/lib/minimart/commands/web.rb +91 -0
- data/lib/minimart/configuration.rb +46 -0
- data/lib/minimart/cookbook.rb +173 -0
- data/lib/minimart/download/cookbook.rb +70 -0
- data/lib/minimart/download/git_cache.rb +60 -0
- data/lib/minimart/download/git_repository.rb +41 -0
- data/lib/minimart/error.rb +32 -0
- data/lib/minimart/inventory_requirement/base_requirement.rb +81 -0
- data/lib/minimart/inventory_requirement/git_requirement.rb +87 -0
- data/lib/minimart/inventory_requirement/git_requirements_builder.rb +101 -0
- data/lib/minimart/inventory_requirement/local_path_requirement.rb +45 -0
- data/lib/minimart/inventory_requirement/local_requirements_builder.rb +31 -0
- data/lib/minimart/inventory_requirement/supermarket_requirements_builder.rb +35 -0
- data/lib/minimart/mirror.rb +12 -0
- data/lib/minimart/mirror/dependency_graph.rb +73 -0
- data/lib/minimart/mirror/download_metadata.rb +57 -0
- data/lib/minimart/mirror/inventory_builder.rb +143 -0
- data/lib/minimart/mirror/inventory_configuration.rb +74 -0
- data/lib/minimart/mirror/inventory_requirements.rb +104 -0
- data/lib/minimart/mirror/local_store.rb +107 -0
- data/lib/minimart/mirror/source.rb +57 -0
- data/lib/minimart/mirror/source_cookbook.rb +77 -0
- data/lib/minimart/mirror/sources.rb +37 -0
- data/lib/minimart/output.rb +34 -0
- data/lib/minimart/utils/archive.rb +39 -0
- data/lib/minimart/utils/file_helper.rb +34 -0
- data/lib/minimart/utils/http.rb +60 -0
- data/lib/minimart/version.rb +3 -0
- data/lib/minimart/web.rb +10 -0
- data/lib/minimart/web/cookbook_show_page_generator.rb +78 -0
- data/lib/minimart/web/cookbooks.rb +83 -0
- data/lib/minimart/web/dashboard_generator.rb +46 -0
- data/lib/minimart/web/html_generator.rb +52 -0
- data/lib/minimart/web/markdown_parser.rb +47 -0
- data/lib/minimart/web/template_helper.rb +132 -0
- data/lib/minimart/web/universe_generator.rb +109 -0
- data/minimart.gemspec +36 -0
- data/spec/fixtures/sample_cookbook.tar.gz +0 -0
- data/spec/fixtures/sample_cookbook/Berksfile +3 -0
- data/spec/fixtures/sample_cookbook/CHANGELOG.md +3 -0
- data/spec/fixtures/sample_cookbook/Gemfile +16 -0
- data/spec/fixtures/sample_cookbook/LICENSE +3 -0
- data/spec/fixtures/sample_cookbook/README.md +42 -0
- data/spec/fixtures/sample_cookbook/Thorfile +5 -0
- data/spec/fixtures/sample_cookbook/Vagrantfile +90 -0
- data/spec/fixtures/sample_cookbook/chefignore +94 -0
- data/spec/fixtures/sample_cookbook/metadata.rb +9 -0
- data/spec/fixtures/sample_cookbook/recipes/default.rb +8 -0
- data/spec/fixtures/sample_inventory.yml +16 -0
- data/spec/fixtures/simple_git_inventory.yml +8 -0
- data/spec/fixtures/simple_inventory.yml +6 -0
- data/spec/fixtures/simple_local_path_inventory.yml +5 -0
- data/spec/fixtures/universe.json +42 -0
- data/spec/fixtures/vcr_cassettes/local_path_cookbooks.yml +3316 -0
- data/spec/fixtures/vcr_cassettes/location_specific_cookbooks.yml +3316 -0
- data/spec/fixtures/vcr_cassettes/supermarket_cookbooks_graph.yml +905 -0
- data/spec/fixtures/vcr_cassettes/supermarket_cookbooks_installing_cookbooks.yml +4218 -0
- data/spec/lib/minimart/cli_spec.rb +104 -0
- data/spec/lib/minimart/commands/mirror_spec.rb +37 -0
- data/spec/lib/minimart/commands/web_spec.rb +75 -0
- data/spec/lib/minimart/configuration_spec.rb +54 -0
- data/spec/lib/minimart/cookbook_spec.rb +137 -0
- data/spec/lib/minimart/download/cookbook_spec.rb +135 -0
- data/spec/lib/minimart/download/git_cache_spec.rb +69 -0
- data/spec/lib/minimart/download/git_repository_spec.rb +39 -0
- data/spec/lib/minimart/error_spec.rb +18 -0
- data/spec/lib/minimart/inventory_requirement/base_requirement_spec.rb +38 -0
- data/spec/lib/minimart/inventory_requirement/git_requirement_spec.rb +92 -0
- data/spec/lib/minimart/inventory_requirement/git_requirements_builder_spec.rb +130 -0
- data/spec/lib/minimart/inventory_requirement/local_path_requirement_spec.rb +35 -0
- data/spec/lib/minimart/inventory_requirement/local_requirements_builder_spec.rb +33 -0
- data/spec/lib/minimart/inventory_requirement/supermarket_requirements_builder_spec.rb +69 -0
- data/spec/lib/minimart/mirror/dependency_graph_spec.rb +123 -0
- data/spec/lib/minimart/mirror/download_metadata_spec.rb +73 -0
- data/spec/lib/minimart/mirror/inventory_builder_spec.rb +195 -0
- data/spec/lib/minimart/mirror/inventory_configuration_spec.rb +86 -0
- data/spec/lib/minimart/mirror/inventory_requirements_spec.rb +78 -0
- data/spec/lib/minimart/mirror/local_store_spec.rb +64 -0
- data/spec/lib/minimart/mirror/source_spec.rb +54 -0
- data/spec/lib/minimart/mirror/sources_spec.rb +50 -0
- data/spec/lib/minimart/output_spec.rb +29 -0
- data/spec/lib/minimart/utils/archive_spec.rb +38 -0
- data/spec/lib/minimart/utils/file_helper_spec.rb +43 -0
- data/spec/lib/minimart/utils/http_spec.rb +37 -0
- data/spec/lib/minimart/web/cookbook_show_page_generator_spec.rb +101 -0
- data/spec/lib/minimart/web/cookbooks_spec.rb +70 -0
- data/spec/lib/minimart/web/dashboard_generator_spec.rb +33 -0
- data/spec/lib/minimart/web/html_generator_spec.rb +34 -0
- data/spec/lib/minimart/web/markdown_parser_spec.rb +54 -0
- data/spec/lib/minimart/web/template_helper_spec.rb +86 -0
- data/spec/lib/minimart/web/universe_generator_spec.rb +125 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/support/file_system.rb +22 -0
- data/web/_assets/javascripts/app.js +164 -0
- data/web/_assets/javascripts/backbone.min.js +6 -0
- data/web/_assets/javascripts/jquery.min.js +4 -0
- data/web/_assets/javascripts/jquery.tabslet.min.js +9 -0
- data/web/_assets/javascripts/manifest.js +5 -0
- data/web/_assets/javascripts/underscore.min.js +5 -0
- data/web/_assets/stylesheets/font-awesome.min.css +4 -0
- data/web/_assets/stylesheets/font-mfizz.css +318 -0
- data/web/_assets/stylesheets/main.css +720 -0
- data/web/_assets/stylesheets/manifest.css +4 -0
- data/web/_assets/stylesheets/normalize.css +427 -0
- data/web/assets/fonts/FontAwesome.otf +0 -0
- data/web/assets/fonts/font-mfizz.eot +0 -0
- data/web/assets/fonts/font-mfizz.svg +1344 -0
- data/web/assets/fonts/font-mfizz.ttf +0 -0
- data/web/assets/fonts/font-mfizz.woff +0 -0
- data/web/assets/fonts/fontawesome-webfont.eot +0 -0
- data/web/assets/fonts/fontawesome-webfont.svg +520 -0
- data/web/assets/fonts/fontawesome-webfont.ttf +0 -0
- data/web/assets/fonts/fontawesome-webfont.woff +0 -0
- data/web/assets/images/header-slim.jpg +0 -0
- data/web/assets/images/icon-search.png +0 -0
- data/web/assets/images/mad-glory-logo.png +0 -0
- data/web/assets/images/main-gradient.png +0 -0
- data/web/assets/images/top-bar-logo.png +0 -0
- data/web/assets/javascripts/application.min.js +5 -0
- data/web/assets/stylesheets/application.min.css +4 -0
- data/web/templates/cookbook_show.erb +96 -0
- data/web/templates/dashboard.erb +81 -0
- data/web/templates/layout.erb +38 -0
- metadata +433 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Minimart
|
|
2
|
+
module Mirror
|
|
3
|
+
# This class can be used to parse, and create `.minimart.json` files to
|
|
4
|
+
# store information about when, and how Minimart downloaded a given cookbook.
|
|
5
|
+
class DownloadMetadata
|
|
6
|
+
|
|
7
|
+
FILE_NAME = '.minimart.json'
|
|
8
|
+
|
|
9
|
+
# @return [String] the path to the directory containing the cookbook.
|
|
10
|
+
attr_reader :path_to_cookbook
|
|
11
|
+
|
|
12
|
+
# @return [Hash] the contents of the metadata file.
|
|
13
|
+
attr_reader :metadata
|
|
14
|
+
|
|
15
|
+
# @param [String] path_to_cookbook The path to the directory containing the cookbook.
|
|
16
|
+
def initialize(path_to_cookbook)
|
|
17
|
+
@path_to_cookbook = path_to_cookbook
|
|
18
|
+
parse_file
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Write the given contents to the metadata file. This will overwrite any
|
|
22
|
+
# existing contents.
|
|
23
|
+
# @param [Hash] contents The hash of data to write to the file.
|
|
24
|
+
def write(contents = {})
|
|
25
|
+
File.open(file_path, 'w+') do |file|
|
|
26
|
+
@metadata = contents
|
|
27
|
+
@metadata.merge!('downloaded_at' => Time.now.utc.iso8601)
|
|
28
|
+
|
|
29
|
+
file.write(metadata.to_json)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Time] The downloaded_at time found in the metadata file.
|
|
34
|
+
def downloaded_at
|
|
35
|
+
return unless self['downloaded_at']
|
|
36
|
+
Time.iso8601(metadata['downloaded_at']).utc
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def [](key)
|
|
40
|
+
metadata[key] if metadata
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def parse_file
|
|
46
|
+
return unless File.exists?(file_path)
|
|
47
|
+
file_contents = File.open(file_path).read
|
|
48
|
+
@metadata = JSON.parse(file_contents)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def file_path
|
|
52
|
+
File.join(path_to_cookbook, FILE_NAME)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
module Minimart
|
|
2
|
+
module Mirror
|
|
3
|
+
|
|
4
|
+
# InventoryBuilder coordinates downloading any cookbooks, and their dependencies.
|
|
5
|
+
class InventoryBuilder
|
|
6
|
+
|
|
7
|
+
# @return [Minimart::Mirror::InventoryConfiguration] The user specified inventory config.
|
|
8
|
+
attr_reader :inventory_configuration
|
|
9
|
+
|
|
10
|
+
# @return [Minimart::Mirror::DependencyGraph]
|
|
11
|
+
attr_reader :graph
|
|
12
|
+
|
|
13
|
+
# @return [Minimart::Mirror::LocalStore] the local store manages the inventory directory contents
|
|
14
|
+
attr_reader :local_store
|
|
15
|
+
|
|
16
|
+
# @param [String] inventory_directory The directory to store the inventory.
|
|
17
|
+
# @param [Minimart::Mirror::InventoryConfiguration] inventory_configuration The inventory as defined by a user of Minimart.
|
|
18
|
+
def initialize(inventory_directory, inventory_configuration)
|
|
19
|
+
@graph = DependencyGraph.new
|
|
20
|
+
@local_store = LocalStore.new(inventory_directory)
|
|
21
|
+
@inventory_configuration = inventory_configuration
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Build the inventory!
|
|
25
|
+
def build!
|
|
26
|
+
install_cookbooks_with_explicit_location
|
|
27
|
+
add_source_cookbooks_to_graph
|
|
28
|
+
add_requirements_to_graph
|
|
29
|
+
fetch_inventory
|
|
30
|
+
display_success_message
|
|
31
|
+
|
|
32
|
+
ensure
|
|
33
|
+
clear_cache
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
# First we must install any cookbooks with a location specification (git, local path, etc..).
|
|
39
|
+
# These cookbooks and their associated metadata (any dependencies they have) take
|
|
40
|
+
# precedence over information found elsewhere.
|
|
41
|
+
def install_cookbooks_with_explicit_location
|
|
42
|
+
inventory_requirements.each_with_explicit_location do |requirement|
|
|
43
|
+
requirement.fetch_cookbook do |cookbook|
|
|
44
|
+
validate_cookbook_against_local_store(cookbook, requirement)
|
|
45
|
+
add_artifact_to_graph(cookbook)
|
|
46
|
+
add_cookbook_to_local_store(cookbook.path, requirement.to_hash)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Fetch the universe from any of the defined sources, and add them as artifacts
|
|
52
|
+
# to the dependency resolution graph.
|
|
53
|
+
def add_source_cookbooks_to_graph
|
|
54
|
+
sources.each_cookbook { |cookbook| add_artifact_to_graph(cookbook) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Add any cookbooks defined in the inventory file as a requirement to the graph
|
|
58
|
+
def add_requirements_to_graph
|
|
59
|
+
inventory_requirements.each do |requirement|
|
|
60
|
+
graph.add_requirement(requirement.requirements)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def fetch_inventory
|
|
65
|
+
resolved_requirements.each do |resolved_requirement|
|
|
66
|
+
install_cookbook(*resolved_requirement)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def resolved_requirements
|
|
71
|
+
graph.resolved_requirements
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def install_cookbook(name, version)
|
|
75
|
+
if cookbook_already_installed?(name, version)
|
|
76
|
+
Configuration.output.puts_yellow("cookbook already installed: #{name}-#{version}.")
|
|
77
|
+
return
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
verify_dependency_can_be_installed(name, version)
|
|
81
|
+
|
|
82
|
+
source_cookbook = cookbook_from_source(name, version)
|
|
83
|
+
source_cookbook.fetch do |cookbook|
|
|
84
|
+
add_cookbook_to_local_store(cookbook.path, source_cookbook.to_hash)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def cookbook_already_installed?(name, version)
|
|
89
|
+
local_store.installed?(name, version)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def verify_dependency_can_be_installed(name, version)
|
|
93
|
+
return unless non_required_version?(name, version)
|
|
94
|
+
|
|
95
|
+
msg = "The dependency #{name}-#{version} could not be installed."
|
|
96
|
+
msg << " This is because a cookbook listed in the inventory depends on a version of '#{name}'"
|
|
97
|
+
msg << " that does not match the explicit requirements for the '#{name}' cookbook."
|
|
98
|
+
raise Error::BrokenDependency, msg
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def non_required_version?(name, version)
|
|
102
|
+
!inventory_requirements.version_required?(name, version)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# We need to validate that if this cookbook is in the store
|
|
106
|
+
# it came from the same source, otherwise we could
|
|
107
|
+
# possibly override a cookbook
|
|
108
|
+
def validate_cookbook_against_local_store(cookbook, requirement)
|
|
109
|
+
local_store.validate_resolved_requirement(cookbook, requirement)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def add_cookbook_to_local_store(cookbook_path, data = {})
|
|
113
|
+
local_store.add_cookbook_from_path(cookbook_path, data)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def cookbook_from_source(name, version)
|
|
117
|
+
sources.find_cookbook(name, version)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def add_artifact_to_graph(cookbook)
|
|
121
|
+
graph.add_artifact(cookbook)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def inventory_requirements
|
|
125
|
+
inventory_configuration.requirements
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def sources
|
|
129
|
+
inventory_configuration.sources
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def display_success_message
|
|
133
|
+
Configuration.output.puts_green %{Minimart is done building your inventory!}
|
|
134
|
+
Configuration.output.puts_green %{The inventory can be found in #{local_store.directory_path}}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def clear_cache
|
|
138
|
+
Minimart::Download::GitCache.instance.clear
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'erb'
|
|
3
|
+
|
|
4
|
+
require 'minimart/mirror/inventory_requirements'
|
|
5
|
+
require 'minimart/utils/file_helper'
|
|
6
|
+
|
|
7
|
+
module Minimart
|
|
8
|
+
module Mirror
|
|
9
|
+
|
|
10
|
+
# This class is responsible for parsing a user defined Minimart configuration file.
|
|
11
|
+
class InventoryConfiguration
|
|
12
|
+
|
|
13
|
+
# The path to the inventory configuration file
|
|
14
|
+
attr_reader :inventory_config_path
|
|
15
|
+
|
|
16
|
+
# @param [String] inventory_config_path The path to the inventory configuration file
|
|
17
|
+
def initialize(inventory_config_path)
|
|
18
|
+
@inventory_config_path = inventory_config_path
|
|
19
|
+
@configuration = parse_config_file
|
|
20
|
+
parse_global_configuration
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The collection of files defined in the inventory file
|
|
24
|
+
# @return [Minimart::Mirror::Sources]
|
|
25
|
+
def sources
|
|
26
|
+
@sources ||= Sources.new(raw_sources)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The collection of cookbook requirements defined in the inventory file
|
|
30
|
+
# @return [Array]
|
|
31
|
+
def requirements
|
|
32
|
+
@cookbooks ||= InventoryRequirements.new(raw_cookbooks)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# The raw parsed configuration file
|
|
38
|
+
attr_reader :configuration
|
|
39
|
+
|
|
40
|
+
def parse_config_file
|
|
41
|
+
unless Utils::FileHelper.file_exists?(inventory_config_path)
|
|
42
|
+
raise Error::InvalidInventoryError, 'The inventory configuration file could not be found'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
file = File.open(inventory_config_path).read
|
|
46
|
+
erb = ERB.new(file).result(binding)
|
|
47
|
+
YAML.load(erb)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def parse_global_configuration
|
|
51
|
+
return unless (conf = configuration['configuration']) && conf.is_a?(Hash)
|
|
52
|
+
|
|
53
|
+
Minimart::Configuration.tap do |c|
|
|
54
|
+
c.chef_server_config = conf.fetch('chef', {})
|
|
55
|
+
c.github_config = conf.fetch('github', [])
|
|
56
|
+
c.verify_ssl = conf['verify_ssl']
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def raw_sources
|
|
61
|
+
configuration['sources'] || []
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def raw_cookbooks
|
|
65
|
+
configuration['cookbooks'].tap do |cookbooks|
|
|
66
|
+
if cookbooks.nil? || cookbooks.empty?
|
|
67
|
+
raise Error::InvalidInventoryError, 'Minimart could not find any cookbooks defined in the inventory'
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require 'minimart/inventory_requirement/supermarket_requirements_builder'
|
|
2
|
+
require 'minimart/inventory_requirement/git_requirements_builder'
|
|
3
|
+
require 'minimart/inventory_requirement/local_requirements_builder'
|
|
4
|
+
|
|
5
|
+
module Minimart
|
|
6
|
+
module Mirror
|
|
7
|
+
|
|
8
|
+
# The collection of requirements as defined in the inventory file.
|
|
9
|
+
class InventoryRequirements
|
|
10
|
+
include Enumerable
|
|
11
|
+
|
|
12
|
+
# @return [Hash<String, Hash>] The cookbooks listed in the inventory file
|
|
13
|
+
attr_reader :raw_cookbooks
|
|
14
|
+
|
|
15
|
+
# @param [Hash<String, Hash>] raw_cookbooks The cookbooks listed in the inventory file
|
|
16
|
+
def initialize(raw_cookbooks)
|
|
17
|
+
@raw_cookbooks = raw_cookbooks
|
|
18
|
+
@requirements = {}
|
|
19
|
+
|
|
20
|
+
parse_cookbooks
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Iterate over the requirements
|
|
24
|
+
# @yield [Minimart::Inventory::BaseRequirement]
|
|
25
|
+
def each(&block)
|
|
26
|
+
requirements.values.flatten.each &block
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def each_with_explicit_location(&block)
|
|
30
|
+
each do |req|
|
|
31
|
+
block.call(req) if req.explicit_location?
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# This method will determine whether or not a version of a cookbook
|
|
36
|
+
# resolves a constraint defined in the inventory file. This method
|
|
37
|
+
# can be used to verify that we aren't downloading a non specified version
|
|
38
|
+
# of a cookbook (e.g. possibly downloading cookbooks that a user wants,
|
|
39
|
+
# but in versions that they do not)
|
|
40
|
+
# @param [String] name The name of the cookbook to verify
|
|
41
|
+
# @param [String] version The cookbook version to check
|
|
42
|
+
# @return [Boolean] Return true if this version solves a requirement
|
|
43
|
+
# as specified in the inventory. This method will also return true for
|
|
44
|
+
# any cookbooks not specified in the inventory.
|
|
45
|
+
def version_required?(name, version)
|
|
46
|
+
(!has_cookbook?(name)) ||
|
|
47
|
+
solves_explicit_requirement?(name, version)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
attr_reader :requirements
|
|
53
|
+
|
|
54
|
+
def has_cookbook?(name)
|
|
55
|
+
requirements.has_key?(name)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def solves_explicit_requirement?(name, version)
|
|
59
|
+
requirements[name].each do |req|
|
|
60
|
+
next unless req.version_requirement?
|
|
61
|
+
return true if Semverse::Constraint.new(req.version_requirement).satisfies?(version)
|
|
62
|
+
end
|
|
63
|
+
return false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Build Minimart::Inventory requirements from the inventory.
|
|
67
|
+
def parse_cookbooks
|
|
68
|
+
raw_cookbooks.each do |name, reqs|
|
|
69
|
+
@requirements[name] = build_requirements_for(name, (reqs || {}))
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def build_requirements_for(name, reqs)
|
|
74
|
+
get_requirements_for(name, reqs).tap do |parsed_requirements|
|
|
75
|
+
validate_requirements(name, parsed_requirements)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def get_requirements_for(name, reqs)
|
|
80
|
+
(market_requirements(name, reqs) +
|
|
81
|
+
git_requirements(name, reqs) +
|
|
82
|
+
local_path_requirements(name, reqs)).flatten.compact
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def validate_requirements(name, reqs)
|
|
86
|
+
return unless reqs.nil? || reqs.empty?
|
|
87
|
+
raise Minimart::Error::InvalidInventoryError,
|
|
88
|
+
"Minimart could not find any requirements for '#{name}'"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def market_requirements(name, reqs)
|
|
92
|
+
InventoryRequirement::SupermarketRequirementsBuilder.new(name, reqs).build
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def git_requirements(name, reqs)
|
|
96
|
+
InventoryRequirement::GitRequirementsBuilder.new(name, reqs).build
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def local_path_requirements(name, reqs)
|
|
100
|
+
InventoryRequirement::LocalRequirementsBuilder.new(name, reqs).build
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
require 'minimart/mirror/download_metadata'
|
|
2
|
+
require 'minimart/utils/file_helper'
|
|
3
|
+
|
|
4
|
+
module Minimart
|
|
5
|
+
module Mirror
|
|
6
|
+
|
|
7
|
+
# The LocalStore manages what is stored in the local inventory, and adding
|
|
8
|
+
# cookbook directories to the local inventory. The LocalStore will automatically
|
|
9
|
+
# load anything that has been stored in the inventory upon initialization.
|
|
10
|
+
class LocalStore
|
|
11
|
+
|
|
12
|
+
# @return [String] the path to the local inventory
|
|
13
|
+
attr_reader :directory_path
|
|
14
|
+
|
|
15
|
+
# @param [String] directory_path The path to the local inventory
|
|
16
|
+
def initialize(directory_path)
|
|
17
|
+
@directory_path = directory_path
|
|
18
|
+
@cookbooks = {}
|
|
19
|
+
|
|
20
|
+
load_local_inventory
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# :nodoc:
|
|
24
|
+
def add_cookbook_to_store(name, version)
|
|
25
|
+
cookbooks[name] ||= []
|
|
26
|
+
cookbooks[name] << version
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Copy a given cookbook to the local store, and record any metadata
|
|
30
|
+
# about how the cookbook was downloaded.
|
|
31
|
+
# @param [String] path The path to the cookbook to add to the store
|
|
32
|
+
# @param [Hash] download_data Any data to record about the cookbook in a Minimart metadata file.
|
|
33
|
+
def add_cookbook_from_path(path, download_data = {})
|
|
34
|
+
cookbook_from_path(path).tap do |cookbook|
|
|
35
|
+
add_cookbook_to_store(cookbook.name, cookbook.version)
|
|
36
|
+
copy_cookbook(cookbook.path, local_path_for(cookbook))
|
|
37
|
+
write_download_data(cookbook, download_data)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Determine whether or not a cookbook has been added to the LocalStore
|
|
42
|
+
# @return [Boolean]
|
|
43
|
+
def installed?(cookbook_name, cookbook_version)
|
|
44
|
+
!!(cookbooks[cookbook_name] &&
|
|
45
|
+
cookbooks[cookbook_name].include?(cookbook_version))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Validate that a new resolved requirement is not in the local store
|
|
49
|
+
# with different requirements. If we download two different branches
|
|
50
|
+
# of the same cookbook and they both resolve to the same version, then we
|
|
51
|
+
# raise an exception.
|
|
52
|
+
# @param [Minimart::Cookbook] new_cookbook
|
|
53
|
+
# @param [Minimart::InventoryRequirement::BaseRequirement] requirement
|
|
54
|
+
# @raise [Minimart::Error::BrokenDependency]
|
|
55
|
+
def validate_resolved_requirement(new_cookbook, requirement)
|
|
56
|
+
return unless installed?(new_cookbook.name, new_cookbook.version)
|
|
57
|
+
|
|
58
|
+
existing_cookbook = load_cookbook(new_cookbook)
|
|
59
|
+
unless requirement.matching_source?(existing_cookbook.download_metadata)
|
|
60
|
+
raise Minimart::Error::BrokenDependency, "A version of #{new_cookbook} already exists in the inventory from a different source."
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
attr_reader :cookbooks
|
|
67
|
+
|
|
68
|
+
def copy_cookbook(source, destination)
|
|
69
|
+
FileUtils.rm_rf(destination) if Dir.exists?(destination)
|
|
70
|
+
FileUtils.cp_r(source, destination)
|
|
71
|
+
|
|
72
|
+
# clean destination directory
|
|
73
|
+
git_dir = File.join(destination, '/.git')
|
|
74
|
+
FileUtils.rm_rf(git_dir) if Dir.exists?(git_dir)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def load_local_inventory
|
|
78
|
+
Utils::FileHelper.find_cookbooks_in_directory(directory).each do |path|
|
|
79
|
+
cookbook = cookbook_from_path(path)
|
|
80
|
+
add_cookbook_to_store(cookbook.name, cookbook.version)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def load_cookbook(cookbook)
|
|
85
|
+
cookbook_from_path(local_path_for(cookbook))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def cookbook_from_path(path)
|
|
89
|
+
Minimart::Cookbook.from_path(Utils::FileHelper.cookbook_path_in_directory(path))
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def directory
|
|
93
|
+
# lazily create the directory to hold the store
|
|
94
|
+
@directory ||= FileUtils.mkdir_p(directory_path).first
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def local_path_for(cookbook)
|
|
98
|
+
File.join(directory, "/#{cookbook}")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def write_download_data(cookbook, download_data = {})
|
|
102
|
+
Minimart::Mirror::DownloadMetadata.new(local_path_for(cookbook)).write(download_data)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|