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.
Files changed (135) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +13 -0
  5. data/Gemfile +3 -0
  6. data/README.md +198 -0
  7. data/Rakefile +45 -0
  8. data/bin/minimart +4 -0
  9. data/lib/minimart.rb +15 -0
  10. data/lib/minimart/cli.rb +93 -0
  11. data/lib/minimart/commands/mirror.rb +30 -0
  12. data/lib/minimart/commands/web.rb +91 -0
  13. data/lib/minimart/configuration.rb +46 -0
  14. data/lib/minimart/cookbook.rb +173 -0
  15. data/lib/minimart/download/cookbook.rb +70 -0
  16. data/lib/minimart/download/git_cache.rb +60 -0
  17. data/lib/minimart/download/git_repository.rb +41 -0
  18. data/lib/minimart/error.rb +32 -0
  19. data/lib/minimart/inventory_requirement/base_requirement.rb +81 -0
  20. data/lib/minimart/inventory_requirement/git_requirement.rb +87 -0
  21. data/lib/minimart/inventory_requirement/git_requirements_builder.rb +101 -0
  22. data/lib/minimart/inventory_requirement/local_path_requirement.rb +45 -0
  23. data/lib/minimart/inventory_requirement/local_requirements_builder.rb +31 -0
  24. data/lib/minimart/inventory_requirement/supermarket_requirements_builder.rb +35 -0
  25. data/lib/minimart/mirror.rb +12 -0
  26. data/lib/minimart/mirror/dependency_graph.rb +73 -0
  27. data/lib/minimart/mirror/download_metadata.rb +57 -0
  28. data/lib/minimart/mirror/inventory_builder.rb +143 -0
  29. data/lib/minimart/mirror/inventory_configuration.rb +74 -0
  30. data/lib/minimart/mirror/inventory_requirements.rb +104 -0
  31. data/lib/minimart/mirror/local_store.rb +107 -0
  32. data/lib/minimart/mirror/source.rb +57 -0
  33. data/lib/minimart/mirror/source_cookbook.rb +77 -0
  34. data/lib/minimart/mirror/sources.rb +37 -0
  35. data/lib/minimart/output.rb +34 -0
  36. data/lib/minimart/utils/archive.rb +39 -0
  37. data/lib/minimart/utils/file_helper.rb +34 -0
  38. data/lib/minimart/utils/http.rb +60 -0
  39. data/lib/minimart/version.rb +3 -0
  40. data/lib/minimart/web.rb +10 -0
  41. data/lib/minimart/web/cookbook_show_page_generator.rb +78 -0
  42. data/lib/minimart/web/cookbooks.rb +83 -0
  43. data/lib/minimart/web/dashboard_generator.rb +46 -0
  44. data/lib/minimart/web/html_generator.rb +52 -0
  45. data/lib/minimart/web/markdown_parser.rb +47 -0
  46. data/lib/minimart/web/template_helper.rb +132 -0
  47. data/lib/minimart/web/universe_generator.rb +109 -0
  48. data/minimart.gemspec +36 -0
  49. data/spec/fixtures/sample_cookbook.tar.gz +0 -0
  50. data/spec/fixtures/sample_cookbook/Berksfile +3 -0
  51. data/spec/fixtures/sample_cookbook/CHANGELOG.md +3 -0
  52. data/spec/fixtures/sample_cookbook/Gemfile +16 -0
  53. data/spec/fixtures/sample_cookbook/LICENSE +3 -0
  54. data/spec/fixtures/sample_cookbook/README.md +42 -0
  55. data/spec/fixtures/sample_cookbook/Thorfile +5 -0
  56. data/spec/fixtures/sample_cookbook/Vagrantfile +90 -0
  57. data/spec/fixtures/sample_cookbook/chefignore +94 -0
  58. data/spec/fixtures/sample_cookbook/metadata.rb +9 -0
  59. data/spec/fixtures/sample_cookbook/recipes/default.rb +8 -0
  60. data/spec/fixtures/sample_inventory.yml +16 -0
  61. data/spec/fixtures/simple_git_inventory.yml +8 -0
  62. data/spec/fixtures/simple_inventory.yml +6 -0
  63. data/spec/fixtures/simple_local_path_inventory.yml +5 -0
  64. data/spec/fixtures/universe.json +42 -0
  65. data/spec/fixtures/vcr_cassettes/local_path_cookbooks.yml +3316 -0
  66. data/spec/fixtures/vcr_cassettes/location_specific_cookbooks.yml +3316 -0
  67. data/spec/fixtures/vcr_cassettes/supermarket_cookbooks_graph.yml +905 -0
  68. data/spec/fixtures/vcr_cassettes/supermarket_cookbooks_installing_cookbooks.yml +4218 -0
  69. data/spec/lib/minimart/cli_spec.rb +104 -0
  70. data/spec/lib/minimart/commands/mirror_spec.rb +37 -0
  71. data/spec/lib/minimart/commands/web_spec.rb +75 -0
  72. data/spec/lib/minimart/configuration_spec.rb +54 -0
  73. data/spec/lib/minimart/cookbook_spec.rb +137 -0
  74. data/spec/lib/minimart/download/cookbook_spec.rb +135 -0
  75. data/spec/lib/minimart/download/git_cache_spec.rb +69 -0
  76. data/spec/lib/minimart/download/git_repository_spec.rb +39 -0
  77. data/spec/lib/minimart/error_spec.rb +18 -0
  78. data/spec/lib/minimart/inventory_requirement/base_requirement_spec.rb +38 -0
  79. data/spec/lib/minimart/inventory_requirement/git_requirement_spec.rb +92 -0
  80. data/spec/lib/minimart/inventory_requirement/git_requirements_builder_spec.rb +130 -0
  81. data/spec/lib/minimart/inventory_requirement/local_path_requirement_spec.rb +35 -0
  82. data/spec/lib/minimart/inventory_requirement/local_requirements_builder_spec.rb +33 -0
  83. data/spec/lib/minimart/inventory_requirement/supermarket_requirements_builder_spec.rb +69 -0
  84. data/spec/lib/minimart/mirror/dependency_graph_spec.rb +123 -0
  85. data/spec/lib/minimart/mirror/download_metadata_spec.rb +73 -0
  86. data/spec/lib/minimart/mirror/inventory_builder_spec.rb +195 -0
  87. data/spec/lib/minimart/mirror/inventory_configuration_spec.rb +86 -0
  88. data/spec/lib/minimart/mirror/inventory_requirements_spec.rb +78 -0
  89. data/spec/lib/minimart/mirror/local_store_spec.rb +64 -0
  90. data/spec/lib/minimart/mirror/source_spec.rb +54 -0
  91. data/spec/lib/minimart/mirror/sources_spec.rb +50 -0
  92. data/spec/lib/minimart/output_spec.rb +29 -0
  93. data/spec/lib/minimart/utils/archive_spec.rb +38 -0
  94. data/spec/lib/minimart/utils/file_helper_spec.rb +43 -0
  95. data/spec/lib/minimart/utils/http_spec.rb +37 -0
  96. data/spec/lib/minimart/web/cookbook_show_page_generator_spec.rb +101 -0
  97. data/spec/lib/minimart/web/cookbooks_spec.rb +70 -0
  98. data/spec/lib/minimart/web/dashboard_generator_spec.rb +33 -0
  99. data/spec/lib/minimart/web/html_generator_spec.rb +34 -0
  100. data/spec/lib/minimart/web/markdown_parser_spec.rb +54 -0
  101. data/spec/lib/minimart/web/template_helper_spec.rb +86 -0
  102. data/spec/lib/minimart/web/universe_generator_spec.rb +125 -0
  103. data/spec/spec_helper.rb +35 -0
  104. data/spec/support/file_system.rb +22 -0
  105. data/web/_assets/javascripts/app.js +164 -0
  106. data/web/_assets/javascripts/backbone.min.js +6 -0
  107. data/web/_assets/javascripts/jquery.min.js +4 -0
  108. data/web/_assets/javascripts/jquery.tabslet.min.js +9 -0
  109. data/web/_assets/javascripts/manifest.js +5 -0
  110. data/web/_assets/javascripts/underscore.min.js +5 -0
  111. data/web/_assets/stylesheets/font-awesome.min.css +4 -0
  112. data/web/_assets/stylesheets/font-mfizz.css +318 -0
  113. data/web/_assets/stylesheets/main.css +720 -0
  114. data/web/_assets/stylesheets/manifest.css +4 -0
  115. data/web/_assets/stylesheets/normalize.css +427 -0
  116. data/web/assets/fonts/FontAwesome.otf +0 -0
  117. data/web/assets/fonts/font-mfizz.eot +0 -0
  118. data/web/assets/fonts/font-mfizz.svg +1344 -0
  119. data/web/assets/fonts/font-mfizz.ttf +0 -0
  120. data/web/assets/fonts/font-mfizz.woff +0 -0
  121. data/web/assets/fonts/fontawesome-webfont.eot +0 -0
  122. data/web/assets/fonts/fontawesome-webfont.svg +520 -0
  123. data/web/assets/fonts/fontawesome-webfont.ttf +0 -0
  124. data/web/assets/fonts/fontawesome-webfont.woff +0 -0
  125. data/web/assets/images/header-slim.jpg +0 -0
  126. data/web/assets/images/icon-search.png +0 -0
  127. data/web/assets/images/mad-glory-logo.png +0 -0
  128. data/web/assets/images/main-gradient.png +0 -0
  129. data/web/assets/images/top-bar-logo.png +0 -0
  130. data/web/assets/javascripts/application.min.js +5 -0
  131. data/web/assets/stylesheets/application.min.css +4 -0
  132. data/web/templates/cookbook_show.erb +96 -0
  133. data/web/templates/dashboard.erb +81 -0
  134. data/web/templates/layout.erb +38 -0
  135. 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