summaryrefslogtreecommitdiff
path: root/lib/chef/knife/supermarket_install.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef/knife/supermarket_install.rb')
-rw-r--r--lib/chef/knife/supermarket_install.rb175
1 files changed, 169 insertions, 6 deletions
diff --git a/lib/chef/knife/supermarket_install.rb b/lib/chef/knife/supermarket_install.rb
index 7642e68181..97a761e69b 100644
--- a/lib/chef/knife/supermarket_install.rb
+++ b/lib/chef/knife/supermarket_install.rb
@@ -1,6 +1,6 @@
#
# Author:: Christopher Webber (<cwebber@chef.io>)
-# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# Copyright:: Copyright (c) 2014-2018 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,17 +17,180 @@
#
require "chef/knife"
-require "chef/knife/cookbook_site_install"
+require "chef/exceptions"
+require "shellwords"
+require "mixlib/archive"
class Chef
class Knife
- class SupermarketInstall < Knife::CookbookSiteInstall
- # Handle the subclassing (knife doesn't do this :()
- dependency_loaders.concat(superclass.dependency_loaders)
- options.merge!(superclass.options)
+ class SupermarketInstall < Knife
+
+ deps do
+ require "chef/mixin/shell_out"
+ require "chef/knife/core/cookbook_scm_repo"
+ require "chef/cookbook/metadata"
+ end
banner "knife supermarket install COOKBOOK [VERSION] (options)"
category "supermarket"
+
+ option :no_deps,
+ short: "-D",
+ long: "--skip-dependencies",
+ boolean: true,
+ default: false,
+ description: "Skips automatic dependency installation."
+
+ option :cookbook_path,
+ short: "-o PATH:PATH",
+ long: "--cookbook-path PATH:PATH",
+ description: "A colon-separated path to look for cookbooks in",
+ proc: lambda { |o| o.split(":") }
+
+ option :default_branch,
+ short: "-B BRANCH",
+ long: "--branch BRANCH",
+ description: "Default branch to work with",
+ default: "master"
+
+ option :use_current_branch,
+ short: "-b",
+ long: "--use-current-branch",
+ description: "Use the current branch",
+ boolean: true,
+ default: false
+
+ option :supermarket_site,
+ short: "-m SUPERMARKET_SITE",
+ long: "--supermarket-site SUPERMARKET_SITE",
+ description: "Supermarket Site",
+ default: "https://supermarket.chef.io",
+ proc: Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket }
+
+ attr_reader :cookbook_name
+ attr_reader :vendor_path
+
+ def run
+ extend Chef::Mixin::ShellOut
+
+ if config[:cookbook_path]
+ Chef::Config[:cookbook_path] = config[:cookbook_path]
+ else
+ config[:cookbook_path] = Chef::Config[:cookbook_path]
+ end
+
+ @cookbook_name = parse_name_args!
+ # Check to ensure we have a valid source of cookbooks before continuing
+ #
+ @install_path = File.expand_path(Array(config[:cookbook_path]).first)
+ ui.info "Installing #{@cookbook_name} to #{@install_path}"
+
+ @repo = CookbookSCMRepo.new(@install_path, ui, config)
+ # cookbook_path = File.join(vendor_path, name_args[0])
+ upstream_file = File.join(@install_path, "#{@cookbook_name}.tar.gz")
+
+ @repo.sanity_check
+ unless config[:use_current_branch]
+ @repo.reset_to_default_state
+ @repo.prepare_to_import(@cookbook_name)
+ end
+
+ downloader = download_cookbook_to(upstream_file)
+ clear_existing_files(File.join(@install_path, @cookbook_name))
+ extract_cookbook(upstream_file, downloader.version)
+
+ # TODO: it'd be better to store these outside the cookbook repo and
+ # keep them around, e.g., in ~/Library/Caches on OS X.
+ ui.info("Removing downloaded tarball")
+ File.unlink(upstream_file)
+
+ if @repo.finalize_updates_to(@cookbook_name, downloader.version)
+ unless config[:use_current_branch]
+ @repo.reset_to_default_state
+ end
+ @repo.merge_updates_from(@cookbook_name, downloader.version)
+ else
+ unless config[:use_current_branch]
+ @repo.reset_to_default_state
+ end
+ end
+
+ unless config[:no_deps]
+ preferred_metadata.dependencies.each_key do |cookbook|
+ # Doesn't do versions.. yet
+ nv = self.class.new
+ nv.config = config
+ nv.name_args = [ cookbook ]
+ nv.run
+ end
+ end
+ end
+
+ def parse_name_args!
+ if name_args.empty?
+ ui.error("Please specify a cookbook to download and install.")
+ exit 1
+ elsif name_args.size >= 2
+ unless name_args.last.match(/^(\d+)(\.\d+){1,2}$/) && name_args.size == 2
+ ui.error("Installing multiple cookbooks at once is not supported.")
+ exit 1
+ end
+ end
+ name_args.first
+ end
+
+ def download_cookbook_to(download_path)
+ downloader = Chef::Knife::CookbookSiteDownload.new
+ downloader.config[:file] = download_path
+ downloader.config[:supermarket_site] = config[:supermarket_site]
+ downloader.name_args = name_args
+ downloader.run
+ downloader
+ end
+
+ def extract_cookbook(upstream_file, version)
+ ui.info("Uncompressing #{@cookbook_name} version #{version}.")
+ Mixlib::Archive.new(convert_path(upstream_file)).extract(@install_path, perms: false)
+ end
+
+ def clear_existing_files(cookbook_path)
+ ui.info("Removing pre-existing version.")
+ FileUtils.rmtree(cookbook_path) if File.directory?(cookbook_path)
+ end
+
+ def convert_path(upstream_file)
+ # converts a Windows path (C:\foo) to a mingw path (/c/foo)
+ if ENV["MSYSTEM"] == "MINGW32"
+ upstream_file.sub(/^([[:alpha:]]):/, '/\1')
+ else
+ Shellwords.escape upstream_file
+ end
+ end
+
+ # Get the preferred metadata path on disk. Chef prefers the metadata.rb
+ # over the metadata.json.
+ #
+ # @raise if there is no metadata in the cookbook
+ #
+ # @return [Chef::Cookbook::Metadata]
+ def preferred_metadata
+ md = Chef::Cookbook::Metadata.new
+
+ rb = File.join(@install_path, @cookbook_name, "metadata.rb")
+ if File.exist?(rb)
+ md.from_file(rb)
+ return md
+ end
+
+ json = File.join(@install_path, @cookbook_name, "metadata.json")
+ if File.exist?(json)
+ json = IO.read(json)
+ md.from_json(json)
+ return md
+ end
+
+ raise Chef::Exceptions::MetadataNotFound.new(@install_path, @cookbook_name)
+ end
end
end
end