[DRE-commits] [ruby-mixlib-install] 01/02: Imported Upstream version 1.0.12
Hleb Valoshka
tsfgnu-guest at moszumanska.debian.org
Mon May 30 13:35:15 UTC 2016
This is an automated email from the git hooks/post-receive script.
tsfgnu-guest pushed a commit to branch master
in repository ruby-mixlib-install.
commit 9b0b0ae009b0fe75e06b70776a77180dfb324524
Author: Hleb Valoshka <375gnu at gmail.com>
Date: Mon May 30 16:34:28 2016 +0300
Imported Upstream version 1.0.12
---
.gitignore | 3 +
.travis.yml | 3 +-
CHANGELOG.md | 50 +++
CONTRIBUTING.md | 14 +
Gemfile | 3 +-
PRODUCT_MATRIX.md | 25 ++
README.md | 77 ++++-
Rakefile | 39 ++-
acceptance/.gitignore | 2 +
acceptance/Gemfile | 12 +
.../.acceptance/acceptance-cookbook/.gitignore | 2 +
.../.acceptance/acceptance-cookbook/metadata.rb | 1 +
.../acceptance-cookbook/recipes/destroy.rb | 3 +
.../acceptance-cookbook/recipes/provision.rb | 3 +
.../acceptance-cookbook/recipes/verify.rb | 3 +
acceptance/current/.kitchen.yml | 41 +++
.../.acceptance/acceptance-cookbook/.gitignore | 2 +
.../.acceptance/acceptance-cookbook/metadata.rb | 1 +
.../acceptance-cookbook/recipes/destroy.rb | 3 +
.../acceptance-cookbook/recipes/provision.rb | 3 +
.../acceptance-cookbook/recipes/verify.rb | 3 +
acceptance/unstable/.kitchen.yml | 41 +++
config.ru | 7 +
lib/mixlib/install.rb | 291 ++++++++----------
lib/mixlib/install/artifact_info.rb | 83 +++++
lib/mixlib/install/backend.rb | 36 +++
lib/mixlib/install/backend/artifactory.rb | 205 +++++++++++++
lib/mixlib/install/backend/base.rb | 112 +++++++
lib/mixlib/install/backend/bintray.rb | 338 +++++++++++++++++++++
lib/mixlib/install/backend/omnitruck.rb | 75 +++++
lib/mixlib/install/generator.rb | 33 ++
lib/mixlib/install/generator/base.rb | 65 ++++
lib/mixlib/install/generator/bourne.rb | 75 +++++
.../bourne/scripts/artifactory_urls.sh.erb | 28 ++
.../generator/bourne/scripts/fetch_metadata.sh.erb | 46 +++
.../generator/bourne/scripts/fetch_package.sh | 53 ++++
.../install/generator/bourne/scripts/helpers.sh | 331 ++++++++++++++++++++
.../generator/bourne/scripts/install_package.sh | 33 ++
.../generator/bourne/scripts/platform_detection.sh | 182 +++++++++++
.../bourne/scripts/script_cli_parameters.sh | 36 +++
lib/mixlib/install/generator/powershell.rb | 101 ++++++
.../scripts/get_project_metadata.ps1.erb | 85 ++++++
.../get_project_metadata_for_artifactory.ps1.erb | 75 +++++
.../generator/powershell/scripts/helpers.ps1 | 80 +++++
.../powershell/scripts/install_project.ps1 | 95 ++++++
.../powershell/scripts/platform_detection.ps1 | 4 +
lib/mixlib/install/options.rb | 144 +++++++++
lib/mixlib/install/product.rb | 293 ++++++++++++++++++
lib/mixlib/install/script_generator.rb | 217 +++++++++++++
lib/mixlib/install/version.rb | 2 +-
metadata.yml | 101 ------
mixlib-install.gemspec | 11 +-
support/install_command.ps1 | 40 ++-
53 files changed, 3304 insertions(+), 307 deletions(-)
diff --git a/.gitignore b/.gitignore
index 8b1df34..cbef319 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,6 @@
/spec/reports/
/tmp/
tags
+.ruby-version
+.kitchen/
+.kitchen.local.yml
diff --git a/.travis.yml b/.travis.yml
index c89cdb4..4921b7d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,4 +4,5 @@ rvm:
- 2.2
- 2.1
- 1.9.3
- - ruby-head
+
+script: bundle exec rake ci
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..063c11e
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,50 @@
+# Change Log
+
+## [1.0.12]
+- Normalize the architecture detection to return either x86_64, i386 or sparc.
+- Remove the powershell product_name validation to support delivery-cli and push-jobs-client in install.ps1.
+- Retry MSI installation when it fails with 1618 (another installation in progress).
+
+## [1.0.11]
+- Add `platform_version_compatibility_mode` option which makes mixlib-install select an artifact built for an earlier version of a platform when set.
+
+## [1.0.10]
+- Correctly parse architecture for ppc64el.
+- Return chef.bintray.com based urls for solaris9 and solaris10.
+- Handle historical artifacts published for solaris.
+
+## [1.0.9]
+- Update platform detection scripts to recognize debian 8 correctly.
+
+## [1.0.8]
+- Resolving artifacts from unstable channel properly map the product name to the relative package name when querying Artifactory.
+
+## [1.0.7]
+- Relax all gemspec dependency versions
+
+## [1.0.6]
+- Exclude metadata.json files from Artifactory package queries
+
+## [1.0.5]
+- Return chef.bintray.com based urls for el5 artifacts.
+
+## [1.0.4]
+- use `SHA256Managed` instead of `SHA256CryptoServiceProvider` to be compatible with .Net 2.0 which is the default runtime on Windows 2008 R2
+
+## [1.0.3]
+- Artifactory credentials are no longer required. A designated account has been hard-coded as default.
+- Exception is raised if Bintray can not find the version for channel/product.
+- freebsd 9 artifacts return chef.bintray.com based urls
+
+## [1.0.2]
+- Use 32 bit windows artifacts for 64-bit, when there is no 64-bit native artifact.
+
+## [1.0.1]
+- detect_platform method for Windows
+- added stopaction to kick in the catch statement if manifest is missing
+- wait for msiexec to exit
+- Replace md5sum checks with sha256 checks in install_command.ps1
+
+## [1.0.0]
+- Ability to query product artifacts from multiple channels
+- Ability to generate installation scripts for `sh` and `ps1`
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..55a65ed
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,14 @@
+This project is maintained by the contribution guidelines identified for
+ [chef](https://github.com/chef/chef) project. You can find the guidelines here:
+
+https://github.com/chef/chef/blob/master/CONTRIBUTING.md
+
+Pull requests in this project are merged when they have two :+1:s from maintainers.
+
+## Maintainers
+
+
+- [Thom May](https://github.com/thommay)
+- [Patrick Wright](https://github.com/patrick-wright)
+- [Serdar Sutay](https://github.com/sersut)
+- [Seth Chisamore](https://github.com/schisamo)
diff --git a/Gemfile b/Gemfile
index 0d8bff4..bee57bd 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,4 +1,5 @@
source "https://rubygems.org"
-gem "finstyle"
+gem "chefstyle", github: "chef/chefstyle"
+
gemspec
diff --git a/PRODUCT_MATRIX.md b/PRODUCT_MATRIX.md
new file mode 100644
index 0000000..bfa2b07
--- /dev/null
+++ b/PRODUCT_MATRIX.md
@@ -0,0 +1,25 @@
+| Product | Product Key |
+| ------- | ------------ |
+| Analytics Platform | analytics |
+| Angry Omnibus Toolchain | angry-omnibus-toolchain |
+| Angry Chef Client | angrychef |
+| Chef Client | chef |
+| Chef Backend | chef-backend |
+| Chef Server | chef-server |
+| Chef Server HA Provisioning for AWS | chef-server-ha-provisioning |
+| Chef Development Kit | chefdk |
+| Chef Compliance | compliance |
+| Delivery | delivery |
+| Delivery CLI | delivery-cli |
+| Chef Server High Availability addon | ha |
+| Management Console | manage |
+| Chef Cloud Marketplace addon | marketplace |
+| Omnibus Toolchain | omnibus-toolchain |
+| Enterprise Chef (legacy) | private-chef |
+| Chef Push Server | push-jobs-client |
+| Chef Push Server | push-jobs-server |
+| Chef Server Reporting addon | reporting |
+| Supermarket | supermarket |
+| Chef Server Replication addon | sync |
+
+Do not modify this file manually. It is automatically rendered via a rake task.
diff --git a/README.md b/README.md
index 4e95d40..e31e91f 100644
--- a/README.md
+++ b/README.md
@@ -1,34 +1,79 @@
# Mixlib::Install
-Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/mixlib/install`. To experiment with that code, run `bin/console` for an interactive prompt.
+## Usage
-TODO: Delete this and the text above, and describe your gem
+### Get URL for specific platform and package version
+```ruby
+options = {
+ channel: :current,
+ product_name: 'chef',
+ product_version: :latest,
+ platform: 'mac_os_x',
+ platform_version: '10.9',
+ architecture: 'x86_64'
+}
+
+artifact = Mixlib::Install.new(options).artifact_info
+# => ArtifactInfo
+
+artifact.url
+# => "http://opscode-omnibus-packages-current.s3.amazonaws.com/mac_os_x/10.9/x86_64/chef-12.5.1%2B20151009083009-1.dmg"
+```
-## Installation
+### Get list of artifacts for all platforms given a package version
+```ruby
+options = {
+ channel: :current,
+ product_name: 'chef',
+ product_version: :latest
+}
-Add this line to your application's Gemfile:
+artifacts = Mixlib::Install.new(options).artifact_info
+# => Array<ArtifactInfo>
-```ruby
-gem 'mixlib-install'
+artifacts.first.url
+# => "http://opscode-omnibus-packages-current.s3.amazonaws.com/mac_os_x/10.9/x86_64/chef-12.5.1%2B20151009083009-1.dmg"
```
-And then execute:
-
- $ bundle
+### Detect platform information
+```ruby
+options = {
+ channel: :current,
+ product_name: 'chef',
+ product_version: :latest
+}
-Or install it yourself as:
+artifact = Mixlib::Install.new(options).detect_platform
- $ gem install mixlib-install
+artifact.platform # => "mac_os_x"
+artifact.platform_version # => "10.10"
+```
-## Usage
+### Use an artifact released for an earlier version of the platform
+```ruby
+options = {
+ channel: :current,
+ product_name: 'chef',
+ product_version: :latest,
+ platform: 'ubuntu',
+ platform_version: '15.04',
+ architecture: 'x86_64',
+ platform_version_compatibility_mode: true
+}
+
+artifact = Mixlib::Install.new(options).artifact_info
+
+artifact.platform # => "ubuntu"
+artifact.platform_version # => "14.04"
+```
-TODO: Write usage instructions here
+## Unstable channel
+The `:unstable` channel is currently only available when connected to Chef's internal network.
## Development
+Since mixlib-install needs to interact with Bintray and Artifactory and since Artifactory instances are only available in Chef's network, this project uses [vcr](https://github.com/vcr/vcr).
-After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
-
-To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
+VCR is a tool that helps cache and replay http responses. When these responses change or when you add more tests you might need to update cached responses. Check out [spec_helper.rb](https://github.com/chef/mixlib-install/blob/master/spec/spec_helper.rb) for instructions on how to do this.
## Contributing
diff --git a/Rakefile b/Rakefile
index b938783..8f02455 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,15 +1,44 @@
require "bundler/gem_tasks"
-require "finstyle"
-require "rubocop/rake_task"
require "rspec/core/rake_task"
-task default: :spec
+task default: :test
desc "Run specs"
RSpec::Core::RakeTask.new(:spec) do |spec|
spec.pattern = "spec/**/*_spec.rb"
end
-RuboCop::RakeTask.new do |task|
- task.options << "--display-cop-names"
+begin
+ require "chefstyle"
+ require "rubocop/rake_task"
+ RuboCop::RakeTask.new(:style) do |task|
+ task.options += ["--display-cop-names", "--no-color"]
+ end
+rescue LoadError
+ puts "chefstyle/rubocop is not available. gem install chefstyle to do style checking."
+end
+
+desc "Run all tests"
+task test: [:style, :spec]
+
+desc "Run tests for Travis CI"
+task ci: [:style, :spec]
+
+desc "Render product matrix documentation"
+task "matrix" do
+ require "mixlib/install/product"
+
+ doc_file = File.join(File.dirname(__FILE__), "PRODUCT_MATRIX.md")
+ puts "Updating doc file at: #{doc_file}"
+
+ File.open(doc_file, "w+") do |f|
+ f.puts("| Product | Product Key |")
+ f.puts("| ------- | ------------ |")
+ PRODUCT_MATRIX.products.sort.each do |p_key|
+ product = PRODUCT_MATRIX.lookup(p_key)
+ f.puts("| #{product.product_name} | #{p_key} |")
+ end
+ f.puts("")
+ f.puts("Do not modify this file manually. It is automatically rendered via a rake task.")
+ end
end
diff --git a/acceptance/.gitignore b/acceptance/.gitignore
new file mode 100644
index 0000000..0f5b194
--- /dev/null
+++ b/acceptance/.gitignore
@@ -0,0 +1,2 @@
+/Gemfile.lock
+/.acceptance_logs/
diff --git a/acceptance/Gemfile b/acceptance/Gemfile
new file mode 100644
index 0000000..069c31d
--- /dev/null
+++ b/acceptance/Gemfile
@@ -0,0 +1,12 @@
+source "https://rubygems.org"
+
+gem "mixlib-install", path: "../"
+gem "test-kitchen", github: "sersut/test-kitchen", branch: "sersut/mixlib-install-update"
+gem "chef-acceptance", github: "chef/chef-acceptance"
+gem "pry"
+
+group(:development) do
+ gem "kitchen-vagrant"
+ gem "windows_chef_zero"
+ gem "winrm-transport"
+end
diff --git a/acceptance/current/.acceptance/acceptance-cookbook/.gitignore b/acceptance/current/.acceptance/acceptance-cookbook/.gitignore
new file mode 100644
index 0000000..640d03d
--- /dev/null
+++ b/acceptance/current/.acceptance/acceptance-cookbook/.gitignore
@@ -0,0 +1,2 @@
+tmp/
+nodes/
diff --git a/acceptance/current/.acceptance/acceptance-cookbook/metadata.rb b/acceptance/current/.acceptance/acceptance-cookbook/metadata.rb
new file mode 100644
index 0000000..4c7c42d
--- /dev/null
+++ b/acceptance/current/.acceptance/acceptance-cookbook/metadata.rb
@@ -0,0 +1 @@
+name 'acceptance-cookbook'
diff --git a/acceptance/current/.acceptance/acceptance-cookbook/recipes/destroy.rb b/acceptance/current/.acceptance/acceptance-cookbook/recipes/destroy.rb
new file mode 100644
index 0000000..50204d6
--- /dev/null
+++ b/acceptance/current/.acceptance/acceptance-cookbook/recipes/destroy.rb
@@ -0,0 +1,3 @@
+execute "bundle exec kitchen destroy #{ENV["KITCHEN_INSTANCES"]}" do
+ cwd node['chef-acceptance']['suite-dir']
+end
diff --git a/acceptance/current/.acceptance/acceptance-cookbook/recipes/provision.rb b/acceptance/current/.acceptance/acceptance-cookbook/recipes/provision.rb
new file mode 100644
index 0000000..50105ca
--- /dev/null
+++ b/acceptance/current/.acceptance/acceptance-cookbook/recipes/provision.rb
@@ -0,0 +1,3 @@
+execute "bundle exec kitchen converge #{ENV["KITCHEN_INSTANCES"]}" do
+ cwd node['chef-acceptance']['suite-dir']
+end
diff --git a/acceptance/current/.acceptance/acceptance-cookbook/recipes/verify.rb b/acceptance/current/.acceptance/acceptance-cookbook/recipes/verify.rb
new file mode 100644
index 0000000..eacd843
--- /dev/null
+++ b/acceptance/current/.acceptance/acceptance-cookbook/recipes/verify.rb
@@ -0,0 +1,3 @@
+execute "bundle exec kitchen verify #{ENV["KITCHEN_INSTANCES"]}" do
+ cwd node['chef-acceptance']['suite-dir']
+end
diff --git a/acceptance/current/.kitchen.yml b/acceptance/current/.kitchen.yml
new file mode 100644
index 0000000..776cdbf
--- /dev/null
+++ b/acceptance/current/.kitchen.yml
@@ -0,0 +1,41 @@
+driver:
+ name: vagrant
+ provider: vmware_fusion
+ forward_agent: yes
+ customize:
+ numvcpus: 2
+ memsize: 1048
+
+provisioner:
+ name: chef_zero
+
+platforms:
+ - name: centos-6.5
+ run_list:
+ - name: debian-7.4
+ run_list:
+ - name: freebsd-10.0
+ run_list:
+ - name: ubuntu-14.04
+ run_list:
+ - name: macosx-10.10
+ driver:
+ box: chef/macosx-10.10 # private
+ - name: windows-server-2012r2-standard
+ driver:
+ box: chef/windows-server-2012r2-standard # private
+
+suites:
+ - name: chef-current-install
+ provisioner:
+ product_name: chef
+ product_version: latest
+ channel: current
+ run_list:
+ - name: chefdk-current-install
+ excludes: [ 'freebsd-10.0' ]
+ provisioner:
+ product_name: chefdk
+ product_version: latest
+ channel: current
+ run_list:
diff --git a/acceptance/unstable/.acceptance/acceptance-cookbook/.gitignore b/acceptance/unstable/.acceptance/acceptance-cookbook/.gitignore
new file mode 100644
index 0000000..640d03d
--- /dev/null
+++ b/acceptance/unstable/.acceptance/acceptance-cookbook/.gitignore
@@ -0,0 +1,2 @@
+tmp/
+nodes/
diff --git a/acceptance/unstable/.acceptance/acceptance-cookbook/metadata.rb b/acceptance/unstable/.acceptance/acceptance-cookbook/metadata.rb
new file mode 100644
index 0000000..4c7c42d
--- /dev/null
+++ b/acceptance/unstable/.acceptance/acceptance-cookbook/metadata.rb
@@ -0,0 +1 @@
+name 'acceptance-cookbook'
diff --git a/acceptance/unstable/.acceptance/acceptance-cookbook/recipes/destroy.rb b/acceptance/unstable/.acceptance/acceptance-cookbook/recipes/destroy.rb
new file mode 100644
index 0000000..50204d6
--- /dev/null
+++ b/acceptance/unstable/.acceptance/acceptance-cookbook/recipes/destroy.rb
@@ -0,0 +1,3 @@
+execute "bundle exec kitchen destroy #{ENV["KITCHEN_INSTANCES"]}" do
+ cwd node['chef-acceptance']['suite-dir']
+end
diff --git a/acceptance/unstable/.acceptance/acceptance-cookbook/recipes/provision.rb b/acceptance/unstable/.acceptance/acceptance-cookbook/recipes/provision.rb
new file mode 100644
index 0000000..50105ca
--- /dev/null
+++ b/acceptance/unstable/.acceptance/acceptance-cookbook/recipes/provision.rb
@@ -0,0 +1,3 @@
+execute "bundle exec kitchen converge #{ENV["KITCHEN_INSTANCES"]}" do
+ cwd node['chef-acceptance']['suite-dir']
+end
diff --git a/acceptance/unstable/.acceptance/acceptance-cookbook/recipes/verify.rb b/acceptance/unstable/.acceptance/acceptance-cookbook/recipes/verify.rb
new file mode 100644
index 0000000..eacd843
--- /dev/null
+++ b/acceptance/unstable/.acceptance/acceptance-cookbook/recipes/verify.rb
@@ -0,0 +1,3 @@
+execute "bundle exec kitchen verify #{ENV["KITCHEN_INSTANCES"]}" do
+ cwd node['chef-acceptance']['suite-dir']
+end
diff --git a/acceptance/unstable/.kitchen.yml b/acceptance/unstable/.kitchen.yml
new file mode 100644
index 0000000..5aac65e
--- /dev/null
+++ b/acceptance/unstable/.kitchen.yml
@@ -0,0 +1,41 @@
+driver:
+ name: vagrant
+ provider: vmware_fusion
+ forward_agent: yes
+ customize:
+ numvcpus: 2
+ memsize: 1048
+
+provisioner:
+ name: chef_zero
+
+platforms:
+ - name: centos-6.5
+ run_list:
+ - name: debian-7.4
+ run_list:
+ - name: freebsd-10.0
+ run_list:
+ - name: ubuntu-14.04
+ run_list:
+ - name: macosx-10.10
+ driver:
+ box: chef/macosx-10.10 # private
+ - name: windows-server-2012r2-standard
+ driver:
+ box: chef/windows-server-2012r2-standard # private
+
+suites:
+ - name: chef-unstable-install
+ provisioner:
+ product_name: chef
+ product_version: latest
+ channel: unstable
+ run_list:
+ - name: chefdk-unstable-install
+ excludes: [ 'freebsd-10.0' ]
+ provisioner:
+ product_name: chefdk
+ product_version: latest
+ channel: unstable
+ run_list:
diff --git a/config.ru b/config.ru
new file mode 100644
index 0000000..77f960b
--- /dev/null
+++ b/config.ru
@@ -0,0 +1,7 @@
+require "rubygems"
+require "bundler"
+
+Bundler.require
+
+require_relative "spec/support/bintray_server"
+run BintrayServer
diff --git a/lib/mixlib/install.rb b/lib/mixlib/install.rb
index c86b447..81826fa 100644
--- a/lib/mixlib/install.rb
+++ b/lib/mixlib/install.rb
@@ -1,5 +1,6 @@
#
# Author:: Thom May (<thom at chef.io>)
+# Author:: Patrick Wright (<patrick at chef.io>)
# Copyright:: Copyright (c) 2015 Chef, Inc.
# License:: Apache License, Version 2.0
#
@@ -16,200 +17,162 @@
# limitations under the License.
#
-require "mixlib/install/util"
-require "cgi"
+require "mixlib/versioning"
+require "mixlib/shellout"
+
+require "mixlib/install/backend"
+require "mixlib/install/options"
+require "mixlib/install/generator"
+require "mixlib/install/generator/bourne"
+require "mixlib/install/generator/powershell"
module Mixlib
class Install
- attr_accessor :version
-
- attr_accessor :powershell
-
- attr_accessor :prerelease
-
- attr_accessor :nightlies
-
- attr_accessor :install_flags
-
- attr_accessor :endpoint
- attr_accessor :root
+ attr_reader :options
- attr_accessor :use_sudo
-
- attr_reader :sudo_command
-
- def sudo_command=(cmd)
- if cmd.nil?
- @use_sudo = false
- else
- @sudo_command = cmd
- end
+ def initialize(options = {})
+ @options = Options.new(options)
end
- attr_accessor :http_proxy
- attr_accessor :https_proxy
-
- attr_accessor :omnibus_url
- attr_accessor :install_msi_url
-
- VALID_INSTALL_OPTS = %w(omnibus_url
- endpoint
- http_proxy
- https_proxy
- install_flags
- install_msi_url
- nightlies
- prerelease
- project
- root
- use_sudo
- sudo_command)
-
- def initialize(version, powershell = false, opts = {})
- @version = version || "latest"
- @powershell = powershell
- @http_proxy = nil
- @https_proxy = nil
- @install_flags = nil
- @prerelease = false
- @nightlies = false
- @endpoint = "metadata"
- @omnibus_url = "https://www.chef.io/chef/install.sh"
- @use_sudo = true
- @sudo_command = "sudo -E"
-
- @root = if powershell
- "$env:systemdrive\\opscode\\chef"
- else
- "/opt/chef"
- end
-
- parse_opts(opts)
+ #
+ # Fetch artifact metadata information
+ #
+ # @return [Array<ArtifactInfo>] list of fetched artifact data for the configured
+ # channel, product name, and product version.
+ # @return [ArtifactInfo] fetched artifact data for the configured
+ # channel, product name, product version and platform info
+ def artifact_info
+ Backend.info(options)
end
+ #
+ # Returns an install script for the given options
+ #
+ # @return [String] script for installing with given options
+ #
def install_command
- vars = if powershell
- install_command_vars_for_powershell
- else
- install_command_vars_for_bourne
- end
- shell_code_from_file(vars)
+ Generator.install_command(options)
end
- private
-
- # Generates the install command variables for Bourne shell-based
- # platforms.
- #
- # @return [String] shell variable lines
- # @api private
- def install_command_vars_for_bourne
- flags = %w[latest true nightlies].include?(version) ? "" : "-v #{CGI.escape(version)}"
- flags << " " << "-n" if nightlies
- flags << " " << "-p" if prerelease
- flags << " " << install_flags if install_flags
-
- [
- shell_var("chef_omnibus_root", root),
- shell_var("chef_omnibus_url", omnibus_url),
- shell_var("install_flags", flags.strip),
- shell_var("pretty_version", Util.pretty_version(version)),
- shell_var("sudo_sh", sudo("sh")),
- shell_var("version", version)
- ].join("\n")
+ #
+ # Returns the base installation directory for the given options
+ #
+ # @return [String] the installation directory for the project
+ #
+ def root
+ # This only works for chef and chefdk but they are the only projects
+ # we are supporting as of now.
+ if options.for_ps1?
+ "$env:systemdrive\\opscode\\#{options.product_name}"
+ else
+ "/opt/#{options.product_name}"
+ end
end
- # Generates the install command variables for PowerShell-based platforms.
- #
- # @param version [String] version string
- # @param metadata_url [String] The metadata URL for the Chef Omnitruck API server
- # @param omnibus_root [String] The base directory the project is installed to
- # @return [String] shell variable lines
- # @api private
- def install_command_vars_for_powershell
- [
- shell_var("chef_omnibus_root", root),
- shell_var("msi", "$env:TEMP\\chef-#{version}.msi"),
- ].tap { |vars|
- if install_msi_url
- vars << shell_var("chef_msi_url", install_msi_url)
- else
- vars << shell_var("chef_metadata_url", windows_metadata_url)
- vars << shell_var("pretty_version", Util.pretty_version(version))
- vars << shell_var("version", version)
- end
- }.join("\n")
+ #
+ # Returns the current version of the installed product.
+ # Returns nil if the product is not installed.
+ #
+ def current_version
+ # Note that this logic does not work for products other than
+ # chef & chefdk since version-manifest is created under the
+ # install directory which can be different than the product name (e.g.
+ # chef-server -> /opt/opscode). But this is OK for now since
+ # chef & chefdk are the only supported products.
+ version_manifest_file = if options.for_ps1?
+ "$env:systemdrive\\opscode\\#{options.product_name}\\version-manifest.json"
+ else
+ "/opt/#{options.product_name}/version-manifest.json"
+ end
+
+ if File.exist? version_manifest_file
+ JSON.parse(File.read(version_manifest_file))["build_version"]
+ end
end
- def validate_opts!(opt)
- err_msg = ["#{opt} is not a valid option",
- "valid options are #{VALID_INSTALL_OPTS.join(' ')}"].join(',')
- fail ArgumentError, err_msg unless VALID_INSTALL_OPTS.include?(opt.to_s)
+ #
+ # Returns true if an upgradable version is available, false otherwise.
+ #
+ def upgrade_available?
+ return true if current_version.nil?
+
+ artifact = artifact_info
+ artifact = artifact.first if artifact.is_a? Array
+ available_ver = Mixlib::Versioning.parse(artifact.version)
+ current_ver = Mixlib::Versioning.parse(current_version)
+ (available_ver > current_ver)
end
- # rubocop:disable Metrics/MethodLength
- def parse_opts(opts)
- opts.each do |opt, setting|
- validate_opts!(opt)
- case opt.to_s
- when 'project', 'endpoint'
- self.endpoint = metadata_endpoint_from_project(setting)
- else
- send("#{opt.to_sym}=", setting)
- end
- end
+ #
+ # Automatically set the platform options
+ #
+ def detect_platform
+ options.set_platform_info(self.class.detect_platform)
+ self
end
- def shell_code_from_file(vars)
- fn = File.join(
- File.dirname(__FILE__),
- %w[.. .. support],
- "install_command"
- )
- Util.shell_code_from_file(vars, fn, powershell,
- http_proxy: http_proxy, https_proxy: https_proxy)
+ #
+ # Returns a Hash containing the platform info options
+ #
+ def self.detect_platform
+ output = if Gem.win_platform?
+ # For Windows we write the detect platform script and execute the
+ # powershell.exe program with Mixlib::ShellOut
+ Dir.mktmpdir do |d|
+ File.open(File.join(d, "detect_platform.ps1"), "w+") do |f|
+ f.puts self.detect_platform_ps1
+ end
+
+ Mixlib::ShellOut.new("powershell.exe -file #{File.join(d, "detect_platform.ps1")}").run_command
+ end
+ else
+ Mixlib::ShellOut.new(self.detect_platform_sh).run_command
+ end
+
+ platform_info = output.stdout.split
+
+ {
+ platform: platform_info[0],
+ platform_version: platform_info[1],
+ architecture: platform_info[2],
+ }
end
- # Builds a shell variable assignment string for the required shell type.
#
- # @param name [String] variable name
- # @param value [String] variable value
- # @return [String] shell variable assignment
- # @api private
- def shell_var(name, value)
- Util.shell_var(name, value, powershell)
+ # Returns the platform_detection.sh script
+ #
+ def self.detect_platform_sh
+ Mixlib::Install::Generator::Bourne.detect_platform_sh
end
- # @return the correct Chef Omnitruck API metadata endpoint, based on project
- def metadata_endpoint_from_project(project = nil)
- if project.nil? || project.downcase == "chef"
- "metadata"
- else
- "metadata-#{project.downcase}"
- end
+ #
+ # Returns the platform_detection.ps1 script
+ #
+ def self.detect_platform_ps1
+ Mixlib::Install::Generator::PowerShell.detect_platform_ps1
end
- def windows_metadata_url
- base = if omnibus_url =~ %r{/install.sh$}
- "#{File.dirname(omnibus_url)}/"
- end
-
- url = "#{base}#{endpoint}"
- url << "?p=windows&m=x86_64&pv=2008r2" # same package for all versions
- url << "&v=#{CGI.escape(version.to_s.downcase)}"
- url << "&prerelease=true" if prerelease
- url << "&nightlies=true" if nightlies
- url
+ #
+ # Returns the install.sh script
+ # Supported context parameters:
+ # ------------------
+ # base_url [String]
+ # url pointing to the omnitruck to be queried by the script.
+ #
+ def self.install_sh(context = {})
+ Mixlib::Install::Generator::Bourne.install_sh(context)
end
- # Conditionally prefixes a command with a sudo command.
#
- # @param command [String] command to be prefixed
- # @return [String] the command, conditionaly prefixed with sudo
- # @api private
- def sudo(script)
- use_sudo ? "#{sudo_command} #{script}" : script
+ # Returns the install.ps1 script
+ # Supported context parameters:
+ # ------------------
+ # base_url [String]
+ # url pointing to the omnitruck to be queried by the script.
+ #
+ def self.install_ps1(context = {})
+ Mixlib::Install::Generator::PowerShell.install_ps1(context)
end
end
end
diff --git a/lib/mixlib/install/artifact_info.rb b/lib/mixlib/install/artifact_info.rb
new file mode 100644
index 0000000..057b99b
--- /dev/null
+++ b/lib/mixlib/install/artifact_info.rb
@@ -0,0 +1,83 @@
+#
+# Author:: Patrick Wright (<patrick at chef.io>)
+# Copyright:: Copyright (c) 2015 Chef, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+module Mixlib
+ class Install
+ class ArtifactInfo
+ attr_reader :url
+ attr_reader :md5
+ attr_reader :sha256
+ attr_reader :sha1
+ attr_reader :version
+
+ attr_reader :platform
+ attr_reader :platform_version
+ attr_reader :architecture
+
+ def initialize(data)
+ @url = data[:url]
+ @md5 = data[:md5]
+ @sha256 = data[:sha256]
+ @sha1 = data[:sha1]
+ @version = data[:version]
+ @platform = data[:platform]
+ @platform_version = data[:platform_version]
+ @architecture = data[:architecture]
+ end
+
+ def self.from_json(json, platform_info)
+ ArtifactInfo.new(JSON.parse(json, symbolize_names: true).merge(platform_info))
+ end
+
+ def self.from_metadata_map(json)
+ artifacts = []
+
+ JSON.parse(json, symbolize_names: true).each do |p, p_data|
+ p_data.each do |pv, pv_data|
+ pv_data.each do |m, metadata|
+ artifacts << ArtifactInfo.new(metadata.merge(
+ platform: p,
+ platform_version: pv,
+ architecture: m
+ ))
+ end
+ end
+ end
+
+ artifacts
+ end
+
+ def to_hash
+ {
+ url: url,
+ md5: md5,
+ sha256: sha256,
+ sha1: sha1,
+ version: version,
+ platform: platform,
+ platform_version: platform_version,
+ architecture: architecture,
+ }
+ end
+
+ def clone_with(data)
+ ArtifactInfo.new(self.to_hash.merge(data))
+ end
+ end
+ end
+end
diff --git a/lib/mixlib/install/backend.rb b/lib/mixlib/install/backend.rb
new file mode 100644
index 0000000..7560859
--- /dev/null
+++ b/lib/mixlib/install/backend.rb
@@ -0,0 +1,36 @@
+#
+# Author:: Patrick Wright (<patrick at chef.io>)
+# Copyright:: Copyright (c) 2015 Chef, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "mixlib/install/backend/artifactory"
+require "mixlib/install/backend/bintray"
+
+module Mixlib
+ class Install
+ class Backend
+ def self.info(options)
+ backend = if options.for_artifactory?
+ Backend::Artifactory.new(options)
+ else
+ Backend::Bintray.new(options)
+ end
+
+ backend.info
+ end
+ end
+ end
+end
diff --git a/lib/mixlib/install/backend/artifactory.rb b/lib/mixlib/install/backend/artifactory.rb
new file mode 100644
index 0000000..77936a7
--- /dev/null
+++ b/lib/mixlib/install/backend/artifactory.rb
@@ -0,0 +1,205 @@
+#
+# Author:: Patrick Wright (<patrick at chef.io>)
+# Copyright:: Copyright (c) 2015 Chef, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "net/http"
+require "json"
+require "mixlib/install/artifact_info"
+require "artifactory"
+require "mixlib/install/backend/base"
+require "mixlib/install/product"
+
+module Mixlib
+ class Install
+ class Backend
+ class Artifactory < Base
+ class ConnectionError < StandardError; end
+ class AuthenticationError < StandardError; end
+ class NoArtifactsError < StandardError; end
+
+ ENDPOINT = "http://artifactory.chef.co".freeze
+
+ # These credentials are read-only credentials in Chef's artifactory
+ # server which is only available in Chef's internal network.
+ ARTIFACTORY_USERNAME = "mixlib-install".freeze
+ ARTIFACTORY_PASSWORD = "%mKPtzbT1JH1wm333kjkkjs39oeuFLgZ8vNoOdLBPd)TZAJsURs9w0HloWR$l6h".freeze
+
+ # Create filtered list of artifacts
+ #
+ # @return [Array<ArtifactInfo>] list of artifacts for the configured
+ # channel, product name, and product version.
+ def available_artifacts
+ if options.latest_version?
+ artifactory_latest
+ else
+ artifactory_artifacts(options.product_version)
+ end
+ end
+
+ #
+ # Get artifacts for the latest version, channel and product_name
+ #
+ # @return [Array<ArtifactInfo>] Array of info about found artifacts
+ def artifactory_latest
+ # Get the list of builds from the REST api.
+ # We do this because a user in the readers group does not have
+ # permissions to run aql against builds.
+ builds = client.get("/api/build/#{package_name}")
+
+ if builds.nil?
+ raise NoArtifactsError, <<-MSG
+Can not find any builds for #{options.product_name} in #{::Artifactory.endpoint}.
+ MSG
+ end
+
+ # Output we get is something like:
+ # {
+ # "buildsNumbers": [
+ # {"uri"=>"/12.5.1+20151213083009", "started"=>"2015-12-13T08:40:19.238+0000"},
+ # {"uri"=>"/12.6.0+20160111212038", "started"=>"2016-01-12T00:25:35.762+0000"},
+ # ...
+ # ]
+ # }
+ # First we sort based on started
+ builds["buildsNumbers"].sort_by! { |b| b["started"] }.reverse!
+
+ # Now check if the build is in the requested channel or not
+ # Note that if you do this for any channel other than :unstable
+ # it will run a high number of queries but it is fine because we
+ # are using artifactory only for :unstable channel
+ builds["buildsNumbers"].each do |build|
+ version = build["uri"].delete("/")
+ artifacts = artifactory_artifacts(version)
+
+ return artifacts unless artifacts.empty?
+ end
+
+ # we could not find any matching artifacts
+ []
+ end
+
+ #
+ # Get artifacts for a given version, channel and product_name
+ #
+ # @return [Array<ArtifactInfo>] Array of info about found artifacts
+ def artifactory_artifacts(version)
+ results = artifactory_query(<<-QUERY
+items.find(
+ {"repo": "omnibus-#{options.channel}-local"},
+ {"@omnibus.project": "#{package_name}"},
+ {"@omnibus.version": "#{version}"},
+ {"name": {"$nmatch": "*.metadata.json" }}
+).include("repo", "path", "name", "property")
+ QUERY
+ )
+
+ # Merge artifactory properties and downloadUri to a flat Hash
+ results.collect! do |result|
+ { "downloadUri" => generate_download_uri(result) }.merge(
+ map_properties(result["properties"])
+ )
+ end
+
+ # Convert results to build records
+ results.map { |a| create_artifact(a) }
+ end
+
+ #
+ # Run an artifactory query for the given query.
+ #
+ # @return [Array<Hash>] Array of results returned from artifactory
+ def artifactory_query(query)
+ results = artifactory_request do
+ client.post("/api/search/aql", query, "Content-Type" => "text/plain")
+ end
+
+ results["results"]
+ end
+
+ def create_artifact(artifact_map)
+ ArtifactInfo.new(
+ md5: artifact_map["omnibus.md5"],
+ sha256: artifact_map["omnibus.sha256"],
+ version: artifact_map["omnibus.version"],
+ platform: artifact_map["omnibus.platform"],
+ platform_version: artifact_map["omnibus.platform_version"],
+ architecture: artifact_map["omnibus.architecture"],
+ url: artifact_map["downloadUri"]
+ )
+ end
+
+ private
+
+ # Converts Array<Hash> where the Hash is a key pair and
+ # value pair to a simplifed key/pair Hash
+ #
+ def map_properties(properties)
+ return {} if properties.nil?
+ properties.each_with_object({}) do |prop, h|
+ h[prop["key"]] = prop["value"]
+ end
+ end
+
+ # Construct the downloadUri from raw artifactory data
+ #
+ def generate_download_uri(result)
+ uri = []
+ uri << endpoint.sub(/\/$/, "")
+ uri << result["repo"]
+ uri << result["path"]
+ uri << result["name"]
+ uri.join("/")
+ end
+
+ def client
+ @client ||= ::Artifactory::Client.new(
+ endpoint: endpoint,
+ username: ARTIFACTORY_USERNAME,
+ password: ARTIFACTORY_PASSWORD
+ )
+ end
+
+ def endpoint
+ @endpoint ||= ENV.fetch("ARTIFACTORY_ENDPOINT", ENDPOINT)
+ end
+
+ def artifactory_request
+ begin
+ results = yield
+ rescue Errno::ETIMEDOUT, ::Artifactory::Error::ConnectionError
+ raise ConnectionError, <<-MSG
+Artifactory endpoint '#{endpoint}' is unreachable. Check that
+the endpoint is correct and there is an open connection to Chef's private network.
+ MSG
+ rescue ::Artifactory::Error::HTTPError => e
+ if e.code == 401 && e.message =~ /Bad credentials/
+ raise AuthenticationError, "Artifactory server denied credentials."
+ else
+ raise e
+ end
+ end
+
+ results
+ end
+
+ def package_name
+ @package_name ||= PRODUCT_MATRIX.lookup(options.product_name, options.product_version).package_name
+ end
+ end
+ end
+ end
+end
diff --git a/lib/mixlib/install/backend/base.rb b/lib/mixlib/install/backend/base.rb
new file mode 100644
index 0000000..5c4fe47
--- /dev/null
+++ b/lib/mixlib/install/backend/base.rb
@@ -0,0 +1,112 @@
+#
+# Author:: Patrick Wright (<patrick at chef.io>)
+# Copyright:: Copyright (c) 2016 Chef, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+module Mixlib
+ class Install
+ class Backend
+ class Base
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ #
+ # Returns the list of artifacts from the configured backend based on the
+ # configured product_name, product_version and channel.
+ #
+ # @abstract Subclasses should define this method.
+ #
+ # @return Array<ArtifactInfo>
+ # List of ArtifactInfo objects for the available artifacts.
+ def available_artifacts
+ raise "Must implement available_artifacts method that returns Array<ArtifactInfo>"
+ end
+
+ #
+ # See #filter_artifacts
+ def info
+ filter_artifacts(available_artifacts)
+ end
+
+ #
+ # Returns true if platform filters are available, false otherwise.
+ #
+ # Note that we assume #set_platform_info method is used on the Options
+ # class to set the platform options.
+ #
+ # @return TrueClass, FalseClass
+ def platform_filters_available?
+ !options.platform.nil?
+ end
+
+ #
+ # Filters and returns the available artifacts based on the configured
+ # platform filtering options.
+ #
+ # @return ArtifactInfo, Array<ArtifactInfo>, []
+ # If the result is a single artifact, this returns ArtifactInfo.
+ # If the result is a list of artifacts, this returns Array<ArtifactInfo>.
+ # If no suitable artifact is found, this returns [].
+ def filter_artifacts(artifacts)
+ return artifacts unless platform_filters_available?
+
+ # First filter the artifacts based on the platform and architecture
+ artifacts.select! do |a|
+ a.platform == options.platform && a.architecture == options.architecture
+ end
+
+ # Now we are going to filter based on platform_version.
+ # We will return the artifact with an exact match if available.
+ # Otherwise we will search for a compatible artifact and return it
+ # if the compat options is set.
+ closest_compatible_artifact = nil
+
+ artifacts.each do |a|
+ return a if a.platform_version == options.platform_version
+
+ # We skip the artifacts produced for windows since their platform
+ # version is always set to 2008r2 which breaks our `to_f` comparison.
+ next if a.platform == "windows"
+
+ # Calculate the closest compatible version.
+ # For an artifact to be compatible it needs to be smaller than the
+ # platform_version specified in options.
+ # To find the closest compatible one we keep a max of the compatible
+ # artifacts.
+ if closest_compatible_artifact.nil? ||
+ (a.platform_version.to_f > closest_compatible_artifact.platform_version.to_f &&
+ a.platform_version.to_f < options.platform_version.to_f )
+ closest_compatible_artifact = a
+ end
+ end
+
+ # If the compat flag is set and if we have found a compatible artifact
+ # we are going to use it.
+ if options.platform_version_compatibility_mode && closest_compatible_artifact
+ return closest_compatible_artifact
+ end
+
+ # Otherwise, we return an empty array indicating we do not have any matching artifacts
+ return []
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/mixlib/install/backend/bintray.rb b/lib/mixlib/install/backend/bintray.rb
new file mode 100644
index 0000000..809aee3
--- /dev/null
+++ b/lib/mixlib/install/backend/bintray.rb
@@ -0,0 +1,338 @@
+#
+# Author:: Patrick Wright (<patrick at chef.io>)
+# Copyright:: Copyright (c) 2016 Chef, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "json"
+require "mixlib/install/backend/base"
+require "mixlib/install/artifact_info"
+
+#
+# Add method to Array class to support
+# searching for substrings that match against
+# the items in the Array
+#
+class Array
+ def fuzzy_include?(search_value, regex_format = "%s")
+ inject(false) do |is_found, array_value|
+ is_found || !!(search_value =~ /#{regex_format % array_value}/)
+ end
+ end
+end
+
+module Mixlib
+ class Install
+ class Backend
+ class Bintray < Base
+ class UnknownArchitecture < StandardError; end
+ class VersionNotFound < StandardError; end
+ # Bintray credentials for api read access. These are here intentionally.
+ BINTRAY_USERNAME = "mixlib-install at chef".freeze
+ BINTRAY_PASSWORD = "a83d3a2ffad50eb9a2230f281a2e19b70fe0db2d".freeze
+
+ ENDPOINT = "https://bintray.com/api/v1/".freeze
+ DOWNLOAD_URL_ENDPOINT = "https://packages.chef.io".freeze
+ COMPAT_DOWNLOAD_URL_ENDPOINT = "http://chef.bintray.com".freeze
+
+ def endpoint
+ @endpoint ||= ENV.fetch("BINTRAY_ENDPOINT", ENDPOINT)
+ end
+
+ #
+ # Makes a GET request to bintray for the given path.
+ #
+ # @param [String] path
+ # "/api/v1/packages/chef" is prepended to the given path.
+ #
+ # @return [String] JSON parsed string of the bintray response
+ #
+ def bintray_get(path)
+ uri = URI.parse(endpoint)
+ http = Net::HTTP.new(uri.host, uri.port)
+ http.use_ssl = (uri.scheme == "https")
+
+ full_path = File.join(uri.path, "packages/chef", path)
+ request = Net::HTTP::Get.new(full_path)
+ request.basic_auth(BINTRAY_USERNAME, BINTRAY_PASSWORD)
+
+ res = http.request(request)
+
+ # Raise if response is not 2XX
+ res.value
+ JSON.parse(res.body)
+ end
+
+ #
+ # Get latest version for product/channel
+ #
+ # @return [String] latest version value
+ #
+ def latest_version
+ result = bintray_get("#{options.channel}/#{options.product_name}/versions/_latest")
+ result["name"]
+ end
+
+ #
+ # Get artifacts for a given version, channel and product_name
+ #
+ # @return [Array<ArtifactInfo>] Array of info about found artifacts
+ #
+ def available_artifacts
+ version = options.latest_version? ? latest_version : options.product_version
+ begin
+ results = bintray_get("#{options.channel}/#{options.product_name}/versions/#{version}/files")
+ rescue Net::HTTPServerException => e
+ if e.message =~ /404 "Not Found"/
+ raise VersionNotFound,
+ "Specified version (#{version}) not found for #{options.product_name} in #{options.channel} channel."
+ else
+ raise
+ end
+ end
+
+ #
+ # Delete files that we don't want as part of the artifact info array
+ # Windows: .asc files
+ # MAC OS _X: .pkg files which are uploaded along with dmg files for
+ # some chef versions.
+ #
+ %w{ asc pkg }.each do |ext|
+ results.reject! { |r| r["name"].end_with?(".#{ext}") }
+ end
+
+ # Convert results to build records
+ results.map! { |a| create_artifact(a) }
+
+ windows_artifact_fixup!(results)
+ end
+
+ # On windows, if we do not have a native 64-bit package available
+ # in the discovered artifacts, we will make 32-bit artifacts available
+ # for 64-bit architecture.
+ def windows_artifact_fixup!(artifacts)
+ new_artifacts = [ ]
+ native_artifacts = [ ]
+
+ artifacts.each do |r|
+ next if r.platform != "windows"
+
+ # Store all native 64-bit artifacts and clone 32-bit artifacts to
+ # be used as 64-bit.
+ case r.architecture
+ when "i386"
+ new_artifacts << r.clone_with(architecture: "x86_64")
+ when "x86_64"
+ native_artifacts << r.clone
+ else
+ puts "Unknown architecture '#{r.architecture}' for windows."
+ end
+ end
+
+ # Now discard the cloned artifacts if we find an equivalent native
+ # artifact
+ native_artifacts.each do |r|
+ new_artifacts.delete_if do |x|
+ x.platform_version == r.platform_version
+ end
+ end
+
+ # add the remaining cloned artifacts to the original set
+ artifacts += new_artifacts
+ end
+
+ #
+ # Creates an instance of ArtifactInfo
+ #
+ # @param artifact_map
+ # {
+ # "name" => "chef-12.8.1-1.powerpc.bff",
+ # "path" => "aix/6.1/chef-12.8.1-1.powerpc.bff",
+ # "version" => "12.8.1",
+ # "sha1" => "1206f7be7be8bbece1e9943dcdc0d22fe538718b",
+ # "sha256" => "e49321095a04f51385a59b3f3d7223cd1bddefc2e2f4280edfb0934d00a4fa3f"
+ # }
+ #
+ # @return [ArtifactInfo] ArtifactInfo instance
+ #
+ def create_artifact(artifact_map)
+ platform_info = parse_platform_info(artifact_map)
+
+ ArtifactInfo.new(
+ sha1: artifact_map["sha1"],
+ sha256: artifact_map["sha256"],
+ version: artifact_map["version"],
+ platform: platform_info[:platform],
+ platform_version: platform_info[:platform_version],
+ architecture: platform_info[:architecture],
+ url: url(artifact_map)
+ )
+ end
+
+ #
+ # Creates the URL for the artifact.
+ #
+ # For some older platform & platform_version combinations we need to
+ # use COMPAT_DOWNLOAD_URL_ENDPOINT since these versions have an
+ # OpenSSL version that can not verify the DOWNLOAD_URL_ENDPOINT
+ # based urls
+ #
+ # @param artifact_map
+ # see #create_artifact for details.
+ #
+ # @return [String] url for the artifact
+ #
+ def url(artifact_map)
+ platform_info = parse_platform_info(artifact_map)
+
+ base_url = case "#{platform_info[:platform]}-#{platform_info[:platform_version]}"
+ when "freebsd-9", "el-5", "solaris2-5.9", "solaris2-5.10"
+ COMPAT_DOWNLOAD_URL_ENDPOINT
+ else
+ DOWNLOAD_URL_ENDPOINT
+ end
+
+ "#{base_url}/#{options.channel}/#{artifact_map["path"]}"
+ end
+
+ #
+ # Parses platform info
+ #
+ # @param artifact_map
+ # {
+ # "name" => "chef-12.8.1-1.powerpc.bff",
+ # "path" => "aix/6.1/chef-12.8.1-1.powerpc.bff",
+ # "version" => "12.8.1",
+ # "sha1" => "1206f7be7be8bbece1e9943dcdc0d22fe538718b",
+ # "sha256" => "e49321095a04f51385a59b3f3d7223cd1bddefc2e2f4280edfb0934d00a4fa3f"
+ # }
+ #
+ # @return [Hash] platform, platform_version, architecture
+ #
+ def parse_platform_info(artifact_map)
+ # platform/platform_version/filename
+ path = artifact_map["path"].split("/")
+ platform = path[0]
+ platform_version = path[1]
+ platform, platform_version = normalize_platform(platform, platform_version)
+
+ filename = artifact_map["name"]
+ architecture = parse_architecture_from_file_name(filename)
+
+ {
+ platform: platform,
+ platform_version: platform_version,
+ architecture: architecture,
+ }
+ end
+
+ #
+ # Normalizes platform and platform_version information that we receive
+ # from bintray. There are a few entries that we historically published
+ # that we need to normalize. They are:
+ # * solaris -> solaris2 & 10 -> 5.10 for solaris.
+ #
+ # @param [String] platform
+ # @param [String] platform_version
+ #
+ # @return Array<String> [platform, platform_version]
+ def normalize_platform(platform, platform_version)
+ if platform == "solaris"
+ platform = "solaris2"
+
+ # Here platform_version is set to either 10 or 11 and we would like
+ # to normalize that to 5.10 and 5.11.
+
+ platform_version = "5.#{platform_version}"
+ end
+
+ [platform, platform_version]
+ end
+
+ #
+ # Determines the architecture for which a file is published from from
+ # filename.
+ #
+ # We determine the architecture based on the filename of the artifact
+ # since architecture the artifact is published for is not available
+ # in bintray.
+ #
+ # IMPORTANT: This function is heavily used by omnitruck poller. Make
+ # sure you test with `./poller` if you change this function.
+ #
+ # @param [String] filename
+ #
+ # @return [String]
+ # one of the standardized architectures for Chef packages:
+ # x86_64, i386, powerpc, sparc, ppc64, ppc64le
+ def parse_architecture_from_file_name(filename)
+ #
+ # We first map the different variations of architectures that we have
+ # used historically to our final set.
+ #
+ if %w{ x86_64 amd64 x64 }.fuzzy_include?(filename)
+ "x86_64"
+ elsif %w{ i386 x86 i86pc i686 }.fuzzy_include?(filename)
+ "i386"
+ elsif %w{ powerpc }.fuzzy_include?(filename)
+ "powerpc"
+ elsif %w{ sparc sun4u sun4v }.fuzzy_include?(filename)
+ "sparc"
+ # Note that ppc64le should come before ppc64 otherwise our search
+ # will think ppc64le matches ppc64. Ubuntu also calls it ppc64el.
+ elsif %w{ ppc64le ppc64el }.fuzzy_include?(filename)
+ "ppc64le"
+ elsif %w{ ppc64 }.fuzzy_include?(filename)
+ "ppc64"
+ #
+ # From here on we need to deal with historical versions
+ # that we have published without any architecture in their
+ # names.
+ #
+ #
+ # All dmg files are published for x86_64
+ elsif filename.end_with?(".dmg")
+ "x86_64"
+ #
+ # The msi files we catch here are versions that are older than the
+ # ones which we introduced 64 builds. Therefore they should map to
+ # i386
+ elsif filename.end_with?(".msi")
+ "i386"
+ #
+ # sh files are the packaging format we were using before dmg on Mac.
+ # They map to x86_64
+ elsif filename.end_with?(".sh")
+ "x86_64"
+ #
+ # We have two common file names for solaris packages. E.g:
+ # chef-11.12.8-2.solaris2.5.10.solaris
+ # chef-11.12.8-2.solaris2.5.9.solaris
+ # These were build on two boxes:
+ # Solaris 9 => sparc
+ # Solaris 10 => i386
+ elsif filename.end_with?(".solaris2.5.10.solaris")
+ "i386"
+ elsif filename.end_with?(".solaris2.5.9.solaris")
+ "sparc"
+ else
+ raise UnknownArchitecture,
+ "architecture can not be determined for '#{filename}'"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/mixlib/install/backend/omnitruck.rb b/lib/mixlib/install/backend/omnitruck.rb
new file mode 100644
index 0000000..05ca41d
--- /dev/null
+++ b/lib/mixlib/install/backend/omnitruck.rb
@@ -0,0 +1,75 @@
+#
+# Author:: Patrick Wright (<patrick at chef.io>)
+# Copyright:: Copyright (c) 2015 Chef, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "net/http"
+require "json"
+require "mixlib/install/artifact_info"
+require "mixlib/install/backend/base"
+
+module Mixlib
+ class Install
+ class Backend
+ class Omnitruck < Base
+ ENDPOINT = "https://omnitruck.chef.io/".freeze
+
+ def endpoint
+ @endpoint ||= ENV.fetch("OMNITRUCK_ENDPOINT", ENDPOINT)
+ end
+
+ def available_artifacts
+ # If we are querying a single platform we need to call metadata
+ # endpoint otherwise we need to call versions endpoint in omnitruck
+ if options.platform
+ build = omnitruck_get("metadata", p: options.platform,
+ pv: options.platform_version,
+ m: options.architecture,
+ v: options.product_version
+ )
+ ArtifactInfo.from_json(build,
+ platform: options.platform,
+ platform_version: options.platform_version,
+ architecture: options.architecture
+ )
+ else
+ builds = omnitruck_get("versions", v: options.product_version)
+ ArtifactInfo.from_metadata_map(builds)
+ end
+ end
+
+ private
+
+ def omnitruck_get(resource, parameters)
+ uri = URI.parse(endpoint)
+ http = Net::HTTP.new(uri.host, uri.port)
+ http.use_ssl = (uri.scheme == "https")
+
+ path = "/#{options.channel}/#{options.product_name}/#{resource}"
+ full_path = [path, URI.encode_www_form(parameters)].join("?")
+ request = Net::HTTP::Get.new(full_path)
+ request["Accept"] = "application/json"
+
+ res = http.request(request)
+
+ # Raise if response is not 2XX
+ res.value
+ res.body
+ end
+ end
+ end
+ end
+end
diff --git a/lib/mixlib/install/generator.rb b/lib/mixlib/install/generator.rb
new file mode 100644
index 0000000..3b9b97e
--- /dev/null
+++ b/lib/mixlib/install/generator.rb
@@ -0,0 +1,33 @@
+#
+# Copyright:: Copyright (c) 2015 Chef, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "mixlib/install/generator/bourne"
+require "mixlib/install/generator/powershell"
+
+module Mixlib
+ class Install
+ class Generator
+ def self.install_command(options)
+ if options.for_ps1?
+ PowerShell.new(options).install_command
+ else
+ Bourne.new(options).install_command
+ end
+ end
+ end
+ end
+end
diff --git a/lib/mixlib/install/generator/base.rb b/lib/mixlib/install/generator/base.rb
new file mode 100644
index 0000000..98a95f6
--- /dev/null
+++ b/lib/mixlib/install/generator/base.rb
@@ -0,0 +1,65 @@
+#
+# Copyright:: Copyright (c) 2015 Chef, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "erb"
+require "ostruct"
+require "mixlib/install/backend/omnitruck"
+
+module Mixlib
+ class Install
+ class Generator
+ class Base
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ #
+ # Returns the base path where the script fragments are located for
+ # the generator as a String.
+ #
+ def self.script_base_path
+ raise "You must define a script_base_path for your Generator::Base class."
+ end
+
+ #
+ # Gets the contents of the given script.
+ #
+ def self.get_script(name, context = {})
+ script_path = File.join(script_base_path, name)
+
+ # If there is an erb template we render it, otherwise we just read
+ # and returnt the contents of the script
+ if File.exist? "#{script_path}.erb"
+ # Default values to use incase they are not set in the context
+ context[:base_url] ||= Mixlib::Install::Backend::Omnitruck::ENDPOINT
+
+ context_object = OpenStruct.new(context).instance_eval { binding }
+ ERB.new(File.read("#{script_path}.erb")).result(context_object)
+ else
+ File.read(script_path)
+ end
+ end
+
+ def get_script(name, context = {})
+ self.class.get_script(name, context)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/mixlib/install/generator/bourne.rb b/lib/mixlib/install/generator/bourne.rb
new file mode 100644
index 0000000..654765f
--- /dev/null
+++ b/lib/mixlib/install/generator/bourne.rb
@@ -0,0 +1,75 @@
+#
+# Copyright:: Copyright (c) 2015 Chef, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "mixlib/install/generator/base"
+require "mixlib/install/backend/artifactory"
+
+module Mixlib
+ class Install
+ class Generator
+ class Bourne < Base
+ def self.install_sh(context)
+ install_command = []
+ install_command << get_script("helpers.sh")
+ install_command << get_script("script_cli_parameters.sh")
+ install_command << get_script("platform_detection.sh")
+ install_command << get_script("fetch_metadata.sh", context)
+ install_command << get_script("fetch_package.sh")
+ install_command << get_script("install_package.sh")
+ install_command.join("\n\n")
+ end
+
+ def self.detect_platform_sh
+ get_script("platform_detection.sh")
+ end
+
+ def self.script_base_path
+ File.join(File.dirname(__FILE__), "bourne/scripts")
+ end
+
+ def install_command
+ install_command = []
+ install_command << get_script("helpers.sh")
+ install_command << render_variables
+ install_command << get_script("platform_detection.sh")
+ if options.for_artifactory?
+ install_command << artifactory_urls
+ else
+ install_command << get_script("fetch_metadata.sh")
+ end
+ install_command << get_script("fetch_package.sh")
+ install_command << get_script("install_package.sh")
+
+ install_command.join("\n\n")
+ end
+
+ def render_variables
+ <<EOS
+project=#{options.product_name}
+version=#{options.product_version}
+channel=#{options.channel}
+EOS
+ end
+
+ def artifactory_urls
+ artifacts = Mixlib::Install::Backend::Artifactory.new(options).info
+ get_script("artifactory_urls.sh", artifacts: artifacts)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/mixlib/install/generator/bourne/scripts/artifactory_urls.sh.erb b/lib/mixlib/install/generator/bourne/scripts/artifactory_urls.sh.erb
new file mode 100644
index 0000000..f0264e2
--- /dev/null
+++ b/lib/mixlib/install/generator/bourne/scripts/artifactory_urls.sh.erb
@@ -0,0 +1,28 @@
+# artifactory_urls.sh
+############
+# This section sets up the information for a build to be installed from artifactory
+# then uses $platform, $platform_version and $machine to select the correct
+# version.
+#
+# Inputs:
+# $platform:
+# $platform_version:
+# $machine:
+#
+# Outputs:
+# $download_url:
+# $sha256:
+############
+
+<% artifacts.each do |artifact| %>
+ artifact_info_dir="$tmp_dir/artifact_info/<%= File.join(artifact.platform, artifact.platform_version, artifact.architecture)%>"
+ mkdir -p $artifact_info_dir
+ touch "$artifact_info_dir/artifact_info"
+ echo "url <%= artifact.url%>" >> "$artifact_info_dir/artifact_info"
+ echo "sha256 <%= artifact.sha256%>" >> "$artifact_info_dir/artifact_info"
+<% end %>
+
+artifact_info_for_platform="$tmp_dir/artifact_info/$platform/$platform_version/$machine/artifact_info"
+
+download_url=`awk '$1 == "url" { print $2 }' "$artifact_info_for_platform"`
+sha256=`awk '$1 == "sha256" { print $2 }' "$artifact_info_for_platform"`
diff --git a/lib/mixlib/install/generator/bourne/scripts/fetch_metadata.sh.erb b/lib/mixlib/install/generator/bourne/scripts/fetch_metadata.sh.erb
new file mode 100644
index 0000000..83d319d
--- /dev/null
+++ b/lib/mixlib/install/generator/bourne/scripts/fetch_metadata.sh.erb
@@ -0,0 +1,46 @@
+# fetch_metadata.sh
+############
+# This section calls omnitruck to get the information about the build to be
+# installed.
+#
+# Inputs:
+# $channel:
+# $project:
+# $version:
+# $platform:
+# $platform_version:
+# $machine:
+# $tmp_dir:
+#
+# Outputs:
+# $download_url:
+# $sha256:
+############
+
+echo "Getting information for $project $channel $version for $platform..."
+
+metadata_filename="$tmp_dir/metadata.txt"
+metadata_url="<%= base_url %>$channel/$project/metadata?v=$version&p=$platform&pv=$platform_version&m=$machine"
+
+do_download "$metadata_url" "$metadata_filename"
+
+cat "$metadata_filename"
+
+echo ""
+# check that all the mandatory fields in the downloaded metadata are there
+if grep '^url' $metadata_filename > /dev/null && grep '^sha256' $metadata_filename > /dev/null; then
+ echo "downloaded metadata file looks valid..."
+else
+ echo "downloaded metadata file is corrupted or an uncaught error was encountered in downloading the file..."
+ # this generally means one of the download methods downloaded a 404 or something like that and then reported a successful exit code,
+ # and this should be fixed in the function that was doing the download.
+ report_bug
+ exit 1
+fi
+
+download_url=`awk '$1 == "url" { print $2 }' "$metadata_filename"`
+sha256=`awk '$1 == "sha256" { print $2 }' "$metadata_filename"`
+
+############
+# end of fetch_metadata.sh
+############
diff --git a/lib/mixlib/install/generator/bourne/scripts/fetch_package.sh b/lib/mixlib/install/generator/bourne/scripts/fetch_package.sh
new file mode 100644
index 0000000..94a1507
--- /dev/null
+++ b/lib/mixlib/install/generator/bourne/scripts/fetch_package.sh
@@ -0,0 +1,53 @@
+# fetch_package.sh
+############
+# This section fetchs a package from $download_url and verifies its metadata.
+#
+# Inputs:
+# $download_url:
+# $tmp_dir:
+# Optional Inputs:
+# $cmdline_filename: Name of the package downloaded on local disk.
+# $cmdline_dl_dir: Name of the directory downloaded package will be saved to on local disk.
+#
+# Outputs:
+# $download_filename: Name of the downloaded file on local disk.
+# $filetype: Type of the file downloaded.
+############
+
+filename=`echo $download_url | sed -e 's/^.*\///'`
+filetype=`echo $filename | sed -e 's/^.*\.//'`
+
+# use either $tmp_dir, the provided directory (-d) or the provided filename (-f)
+if test "x$cmdline_filename" != "x"; then
+ download_filename="$cmdline_filename"
+elif test "x$cmdline_dl_dir" != "x"; then
+ download_filename="$cmdline_dl_dir/$filename"
+else
+ download_filename="$tmp_dir/$filename"
+fi
+
+# ensure the parent directory where to download the installer always exists
+download_dir=`dirname $download_filename`
+(umask 077 && mkdir -p $download_dir) || exit 1
+
+# check if we have that file locally available and if so verify the checksum
+cached_file_available="false"
+if test -f $download_filename; then
+ echo "$download_filename already exists, verifiying checksum..."
+ if do_checksum "$download_filename" "$sha256"; then
+ echo "checksum compare succeeded, using existing file!"
+ cached_file_available="true"
+ else
+ echo "checksum mismatch, downloading latest version of the file"
+ fi
+fi
+
+# download if no local version of the file available
+if test "x$cached_file_available" != "xtrue"; then
+ do_download "$download_url" "$download_filename"
+ do_checksum "$download_filename" "$sha256" || checksum_mismatch
+fi
+
+############
+# end of fetch_package.sh
+############
diff --git a/lib/mixlib/install/generator/bourne/scripts/helpers.sh b/lib/mixlib/install/generator/bourne/scripts/helpers.sh
new file mode 100644
index 0000000..675ae26
--- /dev/null
+++ b/lib/mixlib/install/generator/bourne/scripts/helpers.sh
@@ -0,0 +1,331 @@
+#!/bin/sh
+# WARNING: REQUIRES /bin/sh
+#
+# - must run on /bin/sh on solaris 9
+# - must run on /bin/sh on AIX 6.x
+#
+# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# helpers.sh
+############
+# This section has some helper functions to make life easier.
+#
+# Outputs:
+# $tmp_dir: secure-ish temp directory that can be used during installation.
+############
+
+# Check whether a command exists - returns 0 if it does, 1 if it does not
+exists() {
+ if command -v $1 >/dev/null 2>&1
+ then
+ return 0
+ else
+ return 1
+ fi
+}
+
+# Output the instructions to report bug about this script
+report_bug() {
+ echo "Version: $version"
+ echo ""
+ echo "Please file a Bug Report at https://github.com/chef/omnitruck/issues/new"
+ echo "Alternatively, feel free to open a Support Ticket at https://www.chef.io/support/tickets"
+ echo "More Chef support resources can be found at https://www.chef.io/support"
+ echo ""
+ echo "Please include as many details about the problem as possible i.e., how to reproduce"
+ echo "the problem (if possible), type of the Operating System and its version, etc.,"
+ echo "and any other relevant details that might help us with troubleshooting."
+ echo ""
+}
+
+checksum_mismatch() {
+ echo "Package checksum mismatch!"
+ report_bug
+ exit 1
+}
+
+unable_to_retrieve_package() {
+ echo "Unable to retrieve a valid package!"
+ report_bug
+ echo "Metadata URL: $metadata_url"
+ if test "x$download_url" != "x"; then
+ echo "Download URL: $download_url"
+ fi
+ if test "x$stderr_results" != "x"; then
+ echo "\nDEBUG OUTPUT FOLLOWS:\n$stderr_results"
+ fi
+ exit 1
+}
+
+http_404_error() {
+ echo "Omnitruck artifact does not exist for version $version on platform $platform"
+ echo ""
+ echo "Either this means:"
+ echo " - We do not support $platform"
+ echo " - We do not have an artifact for $version"
+ echo ""
+ echo "This is often the latter case due to running a prerelease or RC version of chef"
+ echo "or a gem version which was only pushed to rubygems and not omnitruck."
+ echo ""
+ echo "You may be able to set your knife[:bootstrap_version] to the most recent stable"
+ echo "release of Chef to fix this problem (or the most recent stable major version number)."
+ echo ""
+ echo "In order to test the version parameter, adventurous users may take the Metadata URL"
+ echo "below and modify the '&v=<number>' parameter until you successfully get a URL that"
+ echo "does not 404 (e.g. via curl or wget). You should be able to use '&v=11' or '&v=12'"
+ echo "succesfully."
+ echo ""
+ echo "If you cannot fix this problem by setting the bootstrap_version, it probably means"
+ echo "that $platform is not supported."
+ echo ""
+ # deliberately do not call report_bug to suppress bug report noise.
+ echo "Metadata URL: $metadata_url"
+ if test "x$download_url" != "x"; then
+ echo "Download URL: $download_url"
+ fi
+ if test "x$stderr_results" != "x"; then
+ echo "\nDEBUG OUTPUT FOLLOWS:\n$stderr_results"
+ fi
+ exit 1
+}
+
+capture_tmp_stderr() {
+ # spool up /tmp/stderr from all the commands we called
+ if test -f "$tmp_dir/stderr"; then
+ output=`cat $tmp_dir/stderr`
+ stderr_results="${stderr_results}\nSTDERR from $1:\n\n$output\n"
+ rm $tmp_dir/stderr
+ fi
+}
+
+# do_wget URL FILENAME
+do_wget() {
+ echo "trying wget..."
+ wget -O "$2" "$1" 2>$tmp_dir/stderr
+ rc=$?
+ # check for 404
+ grep "ERROR 404" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ echo "ERROR 404"
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "wget"
+ return 1
+ fi
+
+ return 0
+}
+
+# do_curl URL FILENAME
+do_curl() {
+ echo "trying curl..."
+ curl --retry 5 -sL -D $tmp_dir/stderr "$1" > "$2"
+ rc=$?
+ # check for 404
+ grep "404 Not Found" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ echo "ERROR 404"
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "curl"
+ return 1
+ fi
+
+ return 0
+}
+
+# do_fetch URL FILENAME
+do_fetch() {
+ echo "trying fetch..."
+ fetch -o "$2" "$1" 2>$tmp_dir/stderr
+ # check for bad return status
+ test $? -ne 0 && return 1
+ return 0
+}
+
+# do_perl URL FILENAME
+do_perl() {
+ echo "trying perl..."
+ perl -e 'use LWP::Simple; getprint($ARGV[0]);' "$1" > "$2" 2>$tmp_dir/stderr
+ rc=$?
+ # check for 404
+ grep "404 Not Found" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ echo "ERROR 404"
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "perl"
+ return 1
+ fi
+
+ return 0
+}
+
+# do_python URL FILENAME
+do_python() {
+ echo "trying python..."
+ python -c "import sys,urllib2 ; sys.stdout.write(urllib2.urlopen(sys.argv[1]).read())" "$1" > "$2" 2>$tmp_dir/stderr
+ rc=$?
+ # check for 404
+ grep "HTTP Error 404" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ echo "ERROR 404"
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "python"
+ return 1
+ fi
+ return 0
+}
+
+# returns 0 if checksums match
+do_checksum() {
+ if exists sha256sum; then
+ echo "Comparing checksum with sha256sum..."
+ checksum=`sha256sum $1 | awk '{ print $1 }'`
+ return `test "x$checksum" = "x$2"`
+ elif exists shasum; then
+ echo "Comparing checksum with shasum..."
+ checksum=`shasum -a 256 $1 | awk '{ print $1 }'`
+ return `test "x$checksum" = "x$2"`
+ else
+ echo "WARNING: could not find a valid checksum program, pre-install shasum or sha256sum in your O/S image to get valdation..."
+ return 0
+ fi
+}
+
+# do_download URL FILENAME
+do_download() {
+ echo "downloading $1"
+ echo " to file $2"
+
+ url=`echo $1`
+ if test "x$platform" = "xsolaris2"; then
+ if test "x$platform_version" = "x5.9" -o "x$platform_version" = "x5.10"; then
+ # solaris 9 lacks openssl, solaris 10 lacks recent enough credentials - your base O/S is completely insecure, please upgrade
+ url=`echo $url | sed -e 's/https/http/'`
+ fi
+ fi
+
+ # we try all of these until we get success.
+ # perl, in particular may be present but LWP::Simple may not be installed
+
+ if exists wget; then
+ do_wget $url $2 && return 0
+ fi
+
+ if exists curl; then
+ do_curl $url $2 && return 0
+ fi
+
+ if exists fetch; then
+ do_fetch $url $2 && return 0
+ fi
+
+ if exists perl; then
+ do_perl $url $2 && return 0
+ fi
+
+ if exists python; then
+ do_python $url $2 && return 0
+ fi
+
+ unable_to_retrieve_package
+}
+
+# install_file TYPE FILENAME
+# TYPE is "rpm", "deb", "solaris", "sh", etc.
+install_file() {
+ echo "Installing $project $version"
+ case "$1" in
+ "rpm")
+ if test "x$platform" = "xnexus" || test "x$platform" = "xios_xr"; then
+ echo "installing with yum..."
+ yum install -yv "$2"
+ else
+ echo "installing with rpm..."
+ rpm -Uvh --oldpackage --replacepkgs "$2"
+ fi
+ ;;
+ "deb")
+ echo "installing with dpkg..."
+ dpkg -i "$2"
+ ;;
+ "bff")
+ echo "installing with installp..."
+ installp -aXYgd "$2" all
+ ;;
+ "solaris")
+ echo "installing with pkgadd..."
+ echo "conflict=nocheck" > $tmp_dir/nocheck
+ echo "action=nocheck" >> $tmp_dir/nocheck
+ echo "mail=" >> $tmp_dir/nocheck
+ pkgrm -a $tmp_dir/nocheck -n $project >/dev/null 2>&1 || true
+ pkgadd -G -n -d "$2" -a $tmp_dir/nocheck $project
+ ;;
+ "pkg")
+ echo "installing with installer..."
+ cd / && /usr/sbin/installer -pkg "$2" -target /
+ ;;
+ "dmg")
+ echo "installing dmg file..."
+ hdiutil detach "/Volumes/chef_software" >/dev/null 2>&1 || true
+ hdiutil attach "$2" -mountpoint "/Volumes/chef_software"
+ cd / && /usr/sbin/installer -pkg `find "/Volumes/chef_software" -name \*.pkg` -target /
+ hdiutil detach "/Volumes/chef_software"
+ ;;
+ "sh" )
+ echo "installing with sh..."
+ sh "$2"
+ ;;
+ *)
+ echo "Unknown filetype: $1"
+ report_bug
+ exit 1
+ ;;
+ esac
+ if test $? -ne 0; then
+ echo "Installation failed"
+ report_bug
+ exit 1
+ fi
+}
+
+if test "x$TMPDIR" = "x"; then
+ tmp="/tmp"
+else
+ tmp=$TMPDIR
+fi
+# secure-ish temp dir creation without having mktemp available (DDoS-able but not expliotable)
+tmp_dir="$tmp/install.sh.$$"
+(umask 077 && mkdir $tmp_dir) || exit 1
+
+############
+# end of helpers.sh
+############
diff --git a/lib/mixlib/install/generator/bourne/scripts/install_package.sh b/lib/mixlib/install/generator/bourne/scripts/install_package.sh
new file mode 100644
index 0000000..99641f2
--- /dev/null
+++ b/lib/mixlib/install/generator/bourne/scripts/install_package.sh
@@ -0,0 +1,33 @@
+# install_package.sh
+############
+# Installs a package and removed the temp directory.
+#
+# Inputs:
+# $download_filename: Name of the file to be installed.
+# $filetype: Type of the file to be installed.
+# $version: The version requested. Used only for warning user if not set.
+############
+
+if test "x$version" = "x"; then
+ echo
+ echo "WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING"
+ echo
+ echo "You are installing an omnibus package without a version pin. If you are installing"
+ echo "on production servers via an automated process this is DANGEROUS and you will"
+ echo "be upgraded without warning on new releases, even to new major releases."
+ echo "Letting the version float is only appropriate in desktop, test, development or"
+ echo "CI/CD environments."
+ echo
+ echo "WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING"
+ echo
+fi
+
+install_file $filetype "$download_filename"
+
+if test "x$tmp_dir" != "x"; then
+ rm -r "$tmp_dir"
+fi
+
+############
+# end of install_package.sh
+############
diff --git a/lib/mixlib/install/generator/bourne/scripts/platform_detection.sh b/lib/mixlib/install/generator/bourne/scripts/platform_detection.sh
new file mode 100644
index 0000000..cfa86f7
--- /dev/null
+++ b/lib/mixlib/install/generator/bourne/scripts/platform_detection.sh
@@ -0,0 +1,182 @@
+# platform_detection.sh
+############
+# This section makes platform detection compatible with omnitruck on the system
+# it runs.
+#
+# Outputs:
+# $platform: Name of the platform.
+# $platform_version: Version of the platform.
+# $machine: System's architecture.
+############
+
+#
+# Platform and Platform Version detection
+#
+# NOTE: This should now match ohai platform and platform_version matching.
+# do not invented new platform and platform_version schemas, just make this behave
+# like what ohai returns as platform and platform_version for the server.
+#
+# ALSO NOTE: Do not mangle platform or platform_version here. It is less error
+# prone and more future-proof to do that in the server, and then all omnitruck clients
+# will 'inherit' the changes (install.sh is not the only client of the omnitruck
+# endpoint out there).
+#
+
+machine=`uname -m`
+os=`uname -s`
+
+if test -f "/etc/lsb-release" && grep -q DISTRIB_ID /etc/lsb-release && ! grep -q wrlinux /etc/lsb-release; then
+ platform=`grep DISTRIB_ID /etc/lsb-release | cut -d "=" -f 2 | tr '[A-Z]' '[a-z]'`
+ platform_version=`grep DISTRIB_RELEASE /etc/lsb-release | cut -d "=" -f 2`
+elif test -f "/etc/debian_version"; then
+ platform="debian"
+ platform_version=`cat /etc/debian_version`
+elif test -f "/etc/redhat-release"; then
+ platform=`sed 's/^\(.\+\) release.*/\1/' /etc/redhat-release | tr '[A-Z]' '[a-z]'`
+ platform_version=`sed 's/^.\+ release \([.0-9]\+\).*/\1/' /etc/redhat-release`
+
+ # If /etc/redhat-release exists, we act like RHEL by default
+ if test "$platform" = "fedora"; then
+ # FIXME: stop remapping fedora to el
+ # FIXME: remove client side platform_version mangling and hard coded yolo
+ # Change platform version for use below.
+ platform_version="6.0"
+ fi
+
+ if test "$platform" = "xenserver"; then
+ # Current XenServer 6.2 is based on CentOS 5, platform is not reset to "el" server should hanlde response
+ platform="xenserver"
+ else
+ # FIXME: use "redhat"
+ platform="el"
+ fi
+
+elif test -f "/etc/system-release"; then
+ platform=`sed 's/^\(.\+\) release.\+/\1/' /etc/system-release | tr '[A-Z]' '[a-z]'`
+ platform_version=`sed 's/^.\+ release \([.0-9]\+\).*/\1/' /etc/system-release | tr '[A-Z]' '[a-z]'`
+ # amazon is built off of fedora, so act like RHEL
+ if test "$platform" = "amazon linux ami"; then
+ # FIXME: remove client side platform_version mangling and hard coded yolo, and remapping to deprecated "el"
+ platform="el"
+ platform_version="6.0"
+ fi
+# Apple OS X
+elif test -f "/usr/bin/sw_vers"; then
+ platform="mac_os_x"
+ # Matching the tab-space with sed is error-prone
+ platform_version=`sw_vers | awk '/^ProductVersion:/ { print $2 }' | cut -d. -f1,2`
+
+ # x86_64 Apple hardware often runs 32-bit kernels (see OHAI-63)
+ x86_64=`sysctl -n hw.optional.x86_64`
+ if test $x86_64 -eq 1; then
+ machine="x86_64"
+ fi
+elif test -f "/etc/release"; then
+ machine=`/usr/bin/uname -p`
+ if grep -q SmartOS /etc/release; then
+ platform="smartos"
+ platform_version=`grep ^Image /etc/product | awk '{ print $3 }'`
+ else
+ platform="solaris2"
+ platform_version=`/usr/bin/uname -r`
+ fi
+elif test -f "/etc/SuSE-release"; then
+ if grep -q 'Enterprise' /etc/SuSE-release;
+ then
+ platform="sles"
+ platform_version=`awk '/^VERSION/ {V = $3}; /^PATCHLEVEL/ {P = $3}; END {print V "." P}' /etc/SuSE-release`
+ else
+ platform="suse"
+ platform_version=`awk '/^VERSION =/ { print $3 }' /etc/SuSE-release`
+ fi
+elif test "x$os" = "xFreeBSD"; then
+ platform="freebsd"
+ platform_version=`uname -r | sed 's/-.*//'`
+elif test "x$os" = "xAIX"; then
+ platform="aix"
+ platform_version="`uname -v`.`uname -r`"
+ machine="powerpc"
+elif test -f "/etc/os-release"; then
+ . /etc/os-release
+ if test "x$CISCO_RELEASE_INFO" != "x"; then
+ . $CISCO_RELEASE_INFO
+ fi
+
+ platform=$ID
+ platform_version=$VERSION
+fi
+
+if test "x$platform" = "x"; then
+ echo "Unable to determine platform version!"
+ report_bug
+ exit 1
+fi
+
+#
+# NOTE: platform manging in the install.sh is DEPRECATED
+#
+# - install.sh should be true to ohai and should not remap
+# platform or platform versions.
+#
+# - remapping platform and mangling platform version numbers is
+# now the complete responsibility of the server-side endpoints
+#
+
+major_version=`echo $platform_version | cut -d. -f1`
+case $platform in
+ # FIXME: should remove this case statement completely
+ "el")
+ # FIXME: "el" is deprecated, should use "redhat"
+ platform_version=$major_version
+ ;;
+ "debian")
+ if test "x$major_version" = "x5"; then
+ # This is here for potential back-compat.
+ # We do not have 5 in versions we publish for anymore but we
+ # might have it for earlier versions.
+ platform_version="6"
+ else
+ platform_version=$major_version
+ fi
+ ;;
+ "freebsd")
+ platform_version=$major_version
+ ;;
+ "sles")
+ platform_version=$major_version
+ ;;
+ "suse")
+ platform_version=$major_version
+ ;;
+esac
+
+# normalize the architecture we detected
+case $machine in
+ "x86_64"|"amd64"|"x64")
+ machine="x86_64"
+ ;;
+ "i386"|"i86pc"|"x86"|"i686")
+ machine="i386"
+ ;;
+ "sparc"|"sun4u"|"sun4v")
+ machine="sparc"
+ ;;
+esac
+
+if test "x$platform_version" = "x"; then
+ echo "Unable to determine platform version!"
+ report_bug
+ exit 1
+fi
+
+if test "x$platform" = "xsolaris2"; then
+ # hack up the path on Solaris to find wget, pkgadd
+ PATH=/usr/sfw/bin:/usr/sbin:$PATH
+ export PATH
+fi
+
+echo "$platform $platform_version $machine"
+
+############
+# end of platform_detection.sh
+############
diff --git a/lib/mixlib/install/generator/bourne/scripts/script_cli_parameters.sh b/lib/mixlib/install/generator/bourne/scripts/script_cli_parameters.sh
new file mode 100644
index 0000000..844eafa
--- /dev/null
+++ b/lib/mixlib/install/generator/bourne/scripts/script_cli_parameters.sh
@@ -0,0 +1,36 @@
+# script_cli_parameters.sh
+############
+# This section reads the CLI parameters for the install script and translates
+# them to the local parameters to be used later by the script.
+#
+# Outputs:
+# $version: Requested version to be installed.
+# $channel: Channel to install the product from
+# $project: Project to be installed
+# $cmdline_filename: Name of the package downloaded on local disk.
+# $cmdline_dl_dir: Name of the directory downloaded package will be saved to on local disk.
+############
+
+# Defaults
+channel="stable"
+project="chef"
+
+while getopts pnv:c:f:P:d: opt
+do
+ case "$opt" in
+
+ v) version="$OPTARG";;
+ c) channel="$OPTARG";;
+ p) channel="current";; # compat for prerelease option
+ n) channel="current";; # compat for nightlies option
+ f) cmdline_filename="$OPTARG";;
+ P) project="$OPTARG";;
+ d) cmdline_dl_dir="$OPTARG";;
+ \?) # unknown flag
+ echo >&2 \
+ "usage: $0 [-P project] [-c release_channel] [-v version] [-f filename | -d download_dir]"
+ exit 1;;
+ esac
+done
+
+shift `expr $OPTIND - 1`
diff --git a/lib/mixlib/install/generator/powershell.rb b/lib/mixlib/install/generator/powershell.rb
new file mode 100644
index 0000000..c5a82cf
--- /dev/null
+++ b/lib/mixlib/install/generator/powershell.rb
@@ -0,0 +1,101 @@
+#
+# Copyright:: Copyright (c) 2015 Chef, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "mixlib/install/generator/base"
+
+module Mixlib
+ class Install
+ class Generator
+ class PowerShell < Base
+ def self.install_ps1(context)
+ install_project_module = []
+ install_project_module << get_script("helpers.ps1")
+ install_project_module << get_script("get_project_metadata.ps1", context)
+ install_project_module << get_script("install_project.ps1")
+
+ install_command = []
+ install_command << ps1_modularize(install_project_module.join("\n"), "Omnitruck")
+ install_command.join("\n\n")
+ end
+
+ def self.detect_platform_ps1
+ detect_platform_command = []
+ detect_platform_command << get_script("helpers.ps1")
+ detect_platform_command << get_script("platform_detection.ps1")
+ detect_platform_command.join("\n\n")
+ end
+
+ def self.script_base_path
+ File.join(File.dirname(__FILE__), "powershell/scripts")
+ end
+
+ def install_command
+ install_project_module = []
+ install_project_module << get_script("helpers.ps1")
+ install_project_module << if options.for_artifactory?
+ artifactory_urls
+ else
+ get_script("get_project_metadata.ps1")
+ end
+ install_project_module << get_script("install_project.ps1")
+
+ install_command = []
+ install_command << ps1_modularize(install_project_module.join("\n"), "Omnitruck")
+ install_command << render_command
+ install_command.join("\n\n")
+ end
+
+ def self.ps1_modularize(module_body, module_name)
+ ps1_module = []
+ ps1_module << "new-module -name #{module_name} -scriptblock {"
+ ps1_module << module_body
+ ps1_module << "}"
+ ps1_module.join("\n")
+ end
+
+ def ps1_modularize(module_body, module_name)
+ self.class.ps1_modularize(module_body, module_name)
+ end
+
+ def artifactory_urls
+ get_script("get_project_metadata_for_artifactory.ps1",
+ artifacts: artifacts)
+ end
+
+ def artifacts
+ @artifacts ||= Mixlib::Install::Backend::Artifactory.new(options).info
+ end
+
+ def product_version
+ if options.for_artifactory?
+ artifacts.first.version
+ else
+ options.product_version
+ end
+ end
+
+ def render_command
+ cmd = "install -project #{options.product_name}"
+ cmd << " -version #{product_version}"
+ cmd << " -channel #{options.channel}"
+ cmd << " -architecture #{options.architecture}" if options.architecture
+ cmd << "\n"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/mixlib/install/generator/powershell/scripts/get_project_metadata.ps1.erb b/lib/mixlib/install/generator/powershell/scripts/get_project_metadata.ps1.erb
new file mode 100644
index 0000000..ab8b596
--- /dev/null
+++ b/lib/mixlib/install/generator/powershell/scripts/get_project_metadata.ps1.erb
@@ -0,0 +1,85 @@
+function Get-ProjectMetadata {
+ <#
+ .SYNOPSIS
+ Get metadata for a Chef Software, Inc. project
+ .DESCRIPTION
+ Get metadata for project
+ .EXAMPLE
+ iex (new-object net.webclient).downloadstring('https://omnitruck.chef.io/install.ps1'); Get-ProjectMetadata -project chef -channel stable
+
+ Gets the download url and SHA256 checksum for the latest stable release of Chef.
+ .EXAMPLE
+ iex (irm 'https://omnitruck.chef.io/install.ps1'); Get-ProjectMetadata -project chefdk -channel stable -version 0.8.0
+
+ Gets the download url and SHA256 checksum for ChefDK 0.8.0.
+ #>
+ [cmdletbinding()]
+ param (
+ # Base url to retrieve metadata from.
+ [uri]$base_server_uri = '<%= base_url %>',
+ [string]
+ # Project to install
+ [string]
+ $project = 'chef',
+ # Version of the application to install
+ # This parameter is optional, if not supplied it will provide the latest version,
+ # and if an iteration number is not specified, it will grab the latest available iteration.
+ # Partial version numbers are also acceptable (using v=11
+ # will grab the latest 11.x client which matches the other flags).
+ [string]
+ $version,
+ # Release channel to install from
+ [validateset('current', 'stable')]
+ [string]
+ $channel = 'stable',
+ # The following legacy switches are just aliases for the current channel
+ [switch]
+ $prerelease,
+ [switch]
+ $nightlies,
+ [validateset('auto', 'i386', 'x86_64')]
+ [string]
+ $architecture = 'auto'
+ )
+
+ # The following legacy switches are just aliases for the current channel
+ if (($prerelease -eq $true)) { $channel = 'current'}
+ if (($nightlies -eq $true)) { $channel = 'current'}
+
+ # PowerShell is only on Windows ATM
+ $platform = 'windows'
+ Write-Verbose "Platform: $platform"
+
+ $platform_version = Get-PlatformVersion
+ Write-Verbose "Platform Version: $platform_version"
+
+ # Custom architecture detection based on channel
+ if ($architecture -eq 'auto') {
+ if (((get-wmiobject win32_operatingsystem).osarchitecture -like '64-bit') -and ($channel -like 'current')) {
+ $architecture = 'x86_64'
+ } else {
+ $architecture = 'i386'
+ }
+ }
+
+ Write-Verbose "Architecture: $architecture"
+ Write-Verbose "Project: $project"
+
+ $metadata_base_url = "$($channel)/$($project)/metadata"
+ $metadata_array = ("?v=$($version)",
+ "p=$platform",
+ "pv=$platform_version",
+ "m=$architecture")
+ $metadata_base_url += [string]::join('&', $metadata_array)
+ $metadata_url = new-uri $base_server_uri $metadata_base_url
+
+ Write-Verbose "Downloading $project details from $metadata_url"
+ $package_metadata = (Get-WebContent $metadata_url).trim() -split '\n' |
+ foreach { $hash = @{} } {$key, $value = $_ -split '\s+'; $hash.Add($key, $value)} {$hash}
+
+ Write-Verbose "Project details: "
+ foreach ($key in $package_metadata.keys) {
+ Write-Verbose "`t$key = $($package_metadata[$key])"
+ }
+ $package_metadata
+}
diff --git a/lib/mixlib/install/generator/powershell/scripts/get_project_metadata_for_artifactory.ps1.erb b/lib/mixlib/install/generator/powershell/scripts/get_project_metadata_for_artifactory.ps1.erb
new file mode 100644
index 0000000..521ee22
--- /dev/null
+++ b/lib/mixlib/install/generator/powershell/scripts/get_project_metadata_for_artifactory.ps1.erb
@@ -0,0 +1,75 @@
+function Get-ProjectMetadata {
+ <#
+ .SYNOPSIS
+ Get metadata for a Chef Software, Inc. project
+ .DESCRIPTION
+ Get metadata for project
+ .EXAMPLE
+ iex (new-object net.webclient).downloadstring('https://omnitruck.chef.io/install.ps1'); Get-ProjectMetadata -project chef -channel stable
+
+ Gets the download url and SHA256 checksum for the latest stable release of Chef.
+ .EXAMPLE
+ iex (irm 'https://omnitruck.chef.io/install.ps1'); Get-ProjectMetadata -project chefdk -channel stable -version 0.8.0
+
+ Gets the download url, and SHA256 checksum for ChefDK 0.8.0.
+ #>
+ [cmdletbinding()]
+ param (
+ # Base url to retrieve metadata from.
+ [uri]$base_server_uri = '<%= base_url %>',
+ [string]
+ # Project to install
+ [string]
+ $project = 'chef',
+ # Version of the application to install
+ # This parameter is optional, if not supplied it will provide the latest version,
+ # and if an iteration number is not specified, it will grab the latest available iteration.
+ # Partial version numbers are also acceptable (using v=11
+ # will grab the latest 11.x client which matches the other flags).
+ [string]
+ $version,
+ # Release channel to install from
+ $channel = 'unstable',
+ # The following legacy switches are just aliases for the current channel
+ [switch]
+ $prerelease,
+ [switch]
+ $nightlies,
+ [validateset('auto', 'i386', 'x86_64')]
+ [string]
+ $architecture = 'auto'
+ )
+
+ # PowerShell is only on Windows ATM
+ $platform = 'windows'
+ Write-Verbose "Platform: $platform"
+
+ $platform_version = Get-PlatformVersion
+ Write-Verbose "Platform Version: $platform_version"
+
+ if ($architecture -eq 'auto') {
+ $architecture = Get-PlatformArchitecture
+ }
+
+ Write-Verbose "Architecture: $architecture"
+ Write-Verbose "Project: $project"
+
+ <% artifacts.each do |artifact| %>
+ $artifact_info_dir = "$($env:temp)/artifact_info/<%= File.join(artifact.platform, artifact.platform_version, artifact.architecture)%>"
+ New-Item -ItemType directory -Path $artifact_info_dir -force
+ New-Item -ItemType file "$($artifact_info_dir)/artifact_info" -force
+ "url <%= artifact.url%>" | out-file "$artifact_info_dir/artifact_info"
+ "sha256 <%= artifact.sha256%>" | out-file "$artifact_info_dir/artifact_info" -Append
+ <% end %>
+
+ $artifact_info_for_platform = Get-Content "$($env:temp)/artifact_info/$($platform)/$($platform_version)/$($architecture)/artifact_info"
+
+ $package_metadata = ($artifact_info_for_platform).trim() -split '\n' |
+ foreach { $hash = @{} } {$key, $value = $_ -split '\s+'; $hash.Add($key, $value)} {$hash}
+
+ Write-Verbose "Project details: "
+ foreach ($key in $package_metadata.keys) {
+ Write-Verbose "`t$key = $($package_metadata[$key])"
+ }
+ $package_metadata
+}
diff --git a/lib/mixlib/install/generator/powershell/scripts/helpers.ps1 b/lib/mixlib/install/generator/powershell/scripts/helpers.ps1
new file mode 100644
index 0000000..4a4ca2c
--- /dev/null
+++ b/lib/mixlib/install/generator/powershell/scripts/helpers.ps1
@@ -0,0 +1,80 @@
+function Get-PlatformVersion {
+ switch -regex ((get-wmiobject win32_operatingsystem).version) {
+ '10\.0\.\d+' {$platform_version = '2012r2'}
+ '6\.3\.\d+' {$platform_version = '2012r2'}
+ '6\.2\.\d+' {$platform_version = '2012'}
+ '6\.1\.\d+' {$platform_version = '2008r2'}
+ '6\.0\.\d+' {$platform_version = '2008'}
+ }
+ return $platform_version
+}
+
+function Get-PlatformArchitecture {
+ if ((get-wmiobject win32_operatingsystem).osarchitecture -like '64-bit') {
+ $architecture = 'x86_64'
+ } else {
+ $architecture = 'i386'
+ }
+ return $architecture
+}
+
+function New-Uri {
+ param ($baseuri, $newuri)
+
+ new-object System.Uri $baseuri, $newuri
+}
+
+function Get-WebContent {
+ param ($uri, $filepath)
+ $proxy = New-Object -TypeName System.Net.WebProxy
+ $wc = new-object System.Net.WebClient
+ $proxy.Address = $env:http_proxy
+ $wc.Proxy = $proxy
+
+ try {
+ if ([string]::IsNullOrEmpty($filepath)) {
+ $wc.downloadstring($uri)
+ }
+ else {
+ $wc.downloadfile($uri, $filepath)
+ }
+ }
+ catch {
+ $exception = $_.Exception
+ Write-Host "There was an error: "
+ do {
+ Write-Host "`t$($exception.message)"
+ $exception = $exception.innerexception
+ } while ($exception)
+ throw "Failed to download from $uri."
+ }
+}
+
+function Test-ProjectPackage {
+ [cmdletbinding()]
+ param ($Path, $Algorithm = 'SHA256', $Hash)
+
+ if (-not (get-command get-filehash)) {
+ function disposable($o){($o -is [IDisposable]) -and (($o | get-member | foreach-object {$_.name}) -contains 'Dispose')}
+ function use($obj, [scriptblock]$sb){try {& $sb} catch [exception]{throw $_} finally {if (disposable $obj) {$obj.Dispose()}} }
+ function Get-FileHash ($Path, $Algorithm) {
+ $Path = (resolve-path $path).providerpath
+ $hash = @{Algorithm = $Algorithm; Path = $Path}
+ use ($c = New-Object -TypeName Security.Cryptography.SHA256Managed) {
+ use ($in = (gi $path).OpenRead()) {
+ $hash.Hash = ([BitConverter]::ToString($c.ComputeHash($in))).Replace("-", "").ToUpper()
+ }
+ }
+ new-object PSObject -Property $hash
+ }
+ }
+ Write-Verbose "Testing the $Algorithm hash for $path."
+ $ActualHash = (Get-FileHash -Algorithm $Algorithm -Path $Path).Hash.ToLower()
+ Write-Verbose "`tDesired Hash - '$hash'"
+ Write-Verbose "`tActual Hash - '$ActualHash'"
+ $Valid = $ActualHash -eq $Hash
+ if (-not $Valid) {
+ Write-Error "Failed to validate the downloaded installer. The expected $Algorithm hash was '$Hash' and the actual hash was '$ActualHash' for $path"
+ }
+ return $Valid
+}
diff --git a/lib/mixlib/install/generator/powershell/scripts/install_project.ps1 b/lib/mixlib/install/generator/powershell/scripts/install_project.ps1
new file mode 100644
index 0000000..39a2c09
--- /dev/null
+++ b/lib/mixlib/install/generator/powershell/scripts/install_project.ps1
@@ -0,0 +1,95 @@
+function Install-Project {
+ <#
+ .SYNOPSIS
+ Install a Chef Software, Inc. product
+ .DESCRIPTION
+ Install a Chef Software, Inc. product
+ .EXAMPLE
+ iex (new-object net.webclient).downloadstring('https://omnitruck.chef.io/install.ps1'); Install-Project -project chef -channel stable
+
+ Installs the latest stable version of Chef.
+ .EXAMPLE
+ iex (irm 'https://omnitruck.chef.io/install.ps1'); Install-Project -project chefdk -channel current
+
+ Installs the latest integration build of the Chef Development Kit
+ #>
+ [cmdletbinding(SupportsShouldProcess=$true)]
+ param (
+ # Project to install
+ [string]
+ $project = 'chef',
+ # Release channel to install from
+ [validateset('current', 'stable', 'unstable')]
+ [string]
+ $channel = 'stable',
+ # Version of the application to install
+ # This parameter is optional, if not supplied it will provide the latest version,
+ # and if an iteration number is not specified, it will grab the latest available iteration.
+ # Partial version numbers are also acceptable (using v=11
+ # will grab the latest 11.x client which matches the other flags).
+ [string]
+ $version,
+ # Full path for the downloaded installer.
+ [string]
+ $filename,
+ # Full path to the location to download the installer
+ [string]
+ $download_directory = $env:temp,
+ # The following legacy switches are just aliases for the current channel
+ [switch]
+ $prerelease,
+ [switch]
+ $nightlies,
+ [validateset('auto', 'i386', 'x86_64')]
+ [string]
+ $architecture = 'auto'
+ )
+
+ $package_metadata = Get-ProjectMetadata -project $project -channel $channel -version $version -prerelease:$prerelease -nightlies:$nightlies -architecture $architecture
+
+ if (-not [string]::IsNullOrEmpty($filename)) {
+ $download_directory = split-path $filename
+ $filename = split-path $filename -leaf
+ if ([string]::IsNullOrEmpty($download_directory)) {
+ $download_directory = $pwd
+ }
+ }
+ else {
+ $filename = ($package_metadata.url -split '/')[-1]
+ }
+ Write-Verbose "Download directory: $download_directory"
+ Write-Verbose "Filename: $filename"
+
+ if (-not (test-path $download_directory)) {
+ mkdir $download_directory
+ }
+ $download_directory = (resolve-path $download_directory).providerpath
+ $download_destination = join-path $download_directory $filename
+
+ if ((test-path $download_destination) -and
+ (Test-ProjectPackage -Path $download_destination -Algorithm 'SHA256' -Hash $package_metadata.sha256 -ea SilentlyContinue)){
+ Write-Verbose "Found existing valid installer at $download_destination."
+ }
+ else {
+ if ($pscmdlet.ShouldProcess("$($package_metadata.url)", "Download $project")) {
+ Write-Verbose "Downloading $project from $($package_metadata.url) to $download_destination."
+ Get-WebContent $package_metadata.url -filepath $download_destination
+ }
+ }
+
+ if ($pscmdlet.ShouldProcess("$download_destination", "Installing")){
+ if (Test-ProjectPackage -Path $download_destination -Algorithm 'SHA256' -Hash $package_metadata.sha256) {
+ Write-Host "Installing $project from $download_destination"
+ $p = Start-Process -FilePath "msiexec" -ArgumentList "/qn /i $download_destination" -Passthru -Wait
+ $p.WaitForExit()
+ if ($p.ExitCode -ne 0) {
+ throw "msiexec was not successful. Received exit code $($p.ExitCode)"
+ }
+ }
+ else {
+ throw "Failed to validate the downloaded installer for $project."
+ }
+ }
+}
+set-alias install -value Install-Project
+export-modulemember -function 'Install-Project','Get-ProjectMetadata' -alias 'install'
diff --git a/lib/mixlib/install/generator/powershell/scripts/platform_detection.ps1 b/lib/mixlib/install/generator/powershell/scripts/platform_detection.ps1
new file mode 100644
index 0000000..5d348fa
--- /dev/null
+++ b/lib/mixlib/install/generator/powershell/scripts/platform_detection.ps1
@@ -0,0 +1,4 @@
+$platform_version = Get-PlatformVersion
+$architecture = Get-PlatformArchitecture
+
+Write-Host "windows $platform_version $architecture"
diff --git a/lib/mixlib/install/options.rb b/lib/mixlib/install/options.rb
new file mode 100644
index 0000000..dac1aa1
--- /dev/null
+++ b/lib/mixlib/install/options.rb
@@ -0,0 +1,144 @@
+#
+# Author:: Patrick Wright (<patrick at chef.io>)
+# Copyright:: Copyright (c) 2015 Chef, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "mixlib/install/product"
+require "mixlib/versioning"
+
+module Mixlib
+ class Install
+ class Options
+ class InvalidOptions < ArgumentError; end
+
+ attr_reader :options
+
+ OMNITRUCK_CHANNELS = [:stable, :current]
+ ARTIFACTORY_CHANNELS = [:unstable]
+ ALL_SUPPORTED_CHANNELS = OMNITRUCK_CHANNELS + ARTIFACTORY_CHANNELS
+ SUPPORTED_PRODUCT_NAMES = PRODUCT_MATRIX.products
+ SUPPORTED_SHELL_TYPES = [:ps1, :sh]
+ SUPPORTED_OPTIONS = [
+ :architecture,
+ :channel,
+ :platform,
+ :platform_version,
+ :product_name,
+ :product_version,
+ :shell_type,
+ :platform_version_compatibility_mode,
+ ]
+
+ def initialize(options)
+ @options = options
+
+ validate!
+ end
+
+ def validate!
+ validate_options!
+ end
+
+ def validate_options!
+ errors = []
+
+ errors << validate_product_names
+ errors << validate_channels
+ errors << validate_shell_type
+
+ unless errors.compact.empty?
+ raise InvalidOptions, errors.join("\n")
+ end
+ end
+
+ SUPPORTED_OPTIONS.each do |option|
+ define_method option do
+ options[option] || options[option.to_s] || default_options[option]
+ end
+ end
+
+ def for_artifactory?
+ ARTIFACTORY_CHANNELS.include?(channel)
+ end
+
+ def for_bintray?
+ [:stable, :current].include?(channel)
+ end
+
+ def for_omnitruck?
+ OMNITRUCK_CHANNELS.include?(channel)
+ end
+
+ def for_ps1?
+ platform == "windows" || shell_type == :ps1
+ end
+
+ def latest_version?
+ product_version.to_sym == :latest
+ end
+
+ #
+ # Set the platform info on the instance
+ # info [Hash]
+ # Hash with keys :platform, :platform_version and :architecture
+ #
+ def set_platform_info(info)
+ options[:platform] = info[:platform]
+ options[:platform_version] = info[:platform_version]
+ options[:architecture] = info[:architecture]
+
+ validate_options!
+ end
+
+ private
+
+ def default_options
+ {
+ shell_type: :sh,
+ platform_version_compatibility_mode: false,
+ }
+ end
+
+ def validate_product_names
+ unless SUPPORTED_PRODUCT_NAMES.include? product_name
+ <<-EOS
+Unknown product name #{product_name}.
+Must be one of: #{SUPPORTED_PRODUCT_NAMES.join(", ")}
+ EOS
+ end
+ end
+
+ def validate_channels
+ unless ALL_SUPPORTED_CHANNELS.include? channel
+ <<-EOS
+Unknown channel #{channel}.
+Must be one of: #{ALL_SUPPORTED_CHANNELS.join(", ")}
+ EOS
+ end
+ end
+
+ def validate_shell_type
+ unless SUPPORTED_SHELL_TYPES.include? shell_type
+ <<-EOS
+Unknown shell type.
+Must be one of: #{SUPPORTED_SHELL_TYPES.join(", ")}
+ EOS
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/mixlib/install/product.rb b/lib/mixlib/install/product.rb
new file mode 100644
index 0000000..71ba9da
--- /dev/null
+++ b/lib/mixlib/install/product.rb
@@ -0,0 +1,293 @@
+#
+# Copyright:: Copyright (c) 2015 Chef, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "mixlib/versioning"
+
+module Mixlib
+ class Install
+ class Product
+ def initialize(&block)
+ instance_eval(&block)
+ end
+
+ DSL_PROPERTIES = [
+ :config_file,
+ :ctl_command,
+ :package_name,
+ :product_name,
+ ]
+
+ #
+ # DSL methods can receive either a String or a Proc to calculate the
+ # value of the property later on based on the version.
+ # We error out if we get both the String and Proc, and we return the value
+ # of the property if we do not receive anything.
+ #
+ # @param [String] prop_string
+ # value to be set in String form
+ # @param [Proc] block
+ # value to be set in Proc form
+ #
+ # @return [String] value of the property
+ #
+ DSL_PROPERTIES.each do |prop|
+ define_method prop do |prop_string = nil, &block|
+ if block.nil?
+ if prop_string.nil?
+ value = instance_variable_get("@#{prop}".to_sym)
+ return nil if value.nil?
+
+ if value.is_a? String
+ value
+ else
+ value.call(version_for(version))
+ end
+ else
+ instance_variable_set("@#{prop}".to_sym, prop_string)
+ end
+ else
+ raise "Can not use String and Proc at the same time for #{prop}." if !prop_string.nil?
+ instance_variable_set("@#{prop}".to_sym, block)
+ end
+ end
+ end
+
+ #
+ # Sets or retrieves the version for the product. This is used later
+ # when we are reading the value of a property if a Proc is specified
+ #
+ def version(value = nil)
+ if value.nil?
+ @version
+ else
+ @version = value
+ end
+ end
+
+ #
+ # Helper method to convert versions from String to Mixlib::Version
+ #
+ # @param [String] version_string
+ # value to be set in String form
+ #
+ # @return [Mixlib::Version]
+ def version_for(version_string)
+ Mixlib::Versioning.parse(version_string)
+ end
+ end
+
+ class ProductMatrix
+ def initialize(&block)
+ @product_map = {}
+ instance_eval(&block)
+ end
+
+ #
+ # The only DSL method of this class. It creates a Product with given
+ # `key` and stores it.
+ #
+ def product(key, &block)
+ @product_map[key] = Product.new(&block)
+ end
+
+ #
+ # Fetches the keys of available products.
+ #
+ # @return Array[String] of keys
+ def products
+ @product_map.keys
+ end
+
+ #
+ # Looks up a product and sets version on it to be used later by the
+ # Product.
+ #
+ # @param [String] key
+ # Lookup key of the product.
+ # @param [String] version
+ # Version to be set for the product. By default version is set to :latest
+ #
+ # @return [Product]
+ def lookup(key, version = :latest)
+ product = @product_map[key]
+ # We set the lookup version for the product to a very high number in
+ # order to mimic :latest so that one does not need to handle this
+ # symbol explicitly when constructing logic based on version numbers.
+ version = "1000.1000.1000" if version.to_sym == :latest
+ product.version(version)
+ product
+ end
+ end
+ end
+end
+
+#
+# If you are making a change to PRODUCT_MATRIX, please make sure
+# you run `bundle exec rake matrix` at the home of this repository
+# to update PRODUCT_MATRIX.md.
+#
+PRODUCT_MATRIX = Mixlib::Install::ProductMatrix.new do
+ # Products in alphabetical order
+
+ product "analytics" do
+ product_name "Analytics Platform"
+ package_name "opscode-analytics"
+ ctl_command "opscode-analytics-ctl"
+ config_file "/etc/opscode-analytics/opscode-analytics.rb"
+ end
+
+ product "angry-omnibus-toolchain" do
+ product_name "Angry Omnibus Toolchain"
+ package_name "angry-omnibus-toolchain"
+ end
+
+ product "angrychef" do
+ product_name "Angry Chef Client"
+ package_name "angrychef"
+ end
+
+ product "chef" do
+ product_name "Chef Client"
+ package_name "chef"
+ end
+
+ product "chef-backend" do
+ product_name "Chef Backend"
+ package_name "chef-backend"
+ ctl_command "chef-backend-ctl"
+ config_file "/etc/chef-backend/chef-backend.rb"
+ end
+
+ product "chef-server" do
+ product_name "Chef Server"
+ package_name do |v|
+ if (v < version_for("12.0.0")) && (v > version_for("11.0.0"))
+ "chef-server"
+ else
+ "chef-server-core"
+ end
+ end
+ ctl_command "chef-server-ctl"
+ config_file "/etc/opscode/chef-server.rb"
+ end
+
+ product "chef-server-ha-provisioning" do
+ product_name "Chef Server HA Provisioning for AWS"
+ package_name "chef-server-ha-provisioning"
+ end
+
+ product "chefdk" do
+ product_name "Chef Development Kit"
+ package_name "chefdk"
+ end
+
+ product "compliance" do
+ product_name "Chef Compliance"
+ package_name "chef-compliance"
+ ctl_command "chef-compliance-ctl"
+ config_file "/etc/chef-compliance/chef-compliance.rb"
+ end
+
+ product "delivery" do
+ product_name "Delivery"
+ package_name "delivery"
+ ctl_command "delivery-ctl"
+ config_file "/etc/delivery/delivery.rb"
+ end
+
+ product "delivery-cli" do
+ product_name "Delivery CLI"
+ package_name "delivery-cli"
+ end
+
+ product "ha" do
+ product_name "Chef Server High Availability addon"
+ package_name "chef-ha"
+ config_file "/etc/opscode/chef-server.rb"
+ end
+
+ product "manage" do
+ product_name "Management Console"
+ package_name do |v|
+ v < version_for("2.0.0") ? "opscode-manage" : "chef-manage"
+ end
+ ctl_command do |v|
+ v < version_for("2.0.0") ? "opscode-manage-ctl" : "chef-manage-ctl"
+ end
+ config_file do |v|
+ if v < version_for("2.0.0")
+ "/etc/opscode-manage/manage.rb"
+ else
+ "/etc/chef-manage/manage.rb"
+ end
+ end
+ end
+
+ product "marketplace" do
+ product_name "Chef Cloud Marketplace addon"
+ package_name "chef-marketplace"
+ ctl_command "chef-marketplace-ctl"
+ config_file "/etc/chef-marketplace/marketplace.rb"
+ end
+
+ product "omnibus-toolchain" do
+ product_name "Omnibus Toolchain"
+ package_name "omnibus-toolchain"
+ end
+
+ product "private-chef" do
+ product_name "Enterprise Chef (legacy)"
+ package_name "private-chef"
+ ctl_command "private-chef-ctl"
+ config_file "/etc/opscode/private-chef.rb"
+ end
+
+ product "push-jobs-client" do
+ product_name "Chef Push Server"
+ package_name do |v|
+ v < version_for("1.3.0") ? "opscode-push-jobs-client" : "push-jobs-client"
+ end
+ end
+
+ product "push-jobs-server" do
+ product_name "Chef Push Server"
+ package_name "opscode-push-jobs-server"
+ ctl_command "opscode-push-jobs-server-ctl"
+ config_file "/etc/opscode-push-jobs-server/opscode-push-jobs-server.rb"
+ end
+
+ product "reporting" do
+ product_name "Chef Server Reporting addon"
+ package_name "opscode-reporting"
+ ctl_command "opscode-reporting-ctl"
+ config_file "/etc/opscode-reporting/opscode-reporting.rb"
+ end
+
+ product "supermarket" do
+ product_name "Supermarket"
+ package_name "supermarket"
+ ctl_command "supermarket-ctl"
+ config_file "/etc/supermarket/supermarket.json"
+ end
+
+ product "sync" do
+ product_name "Chef Server Replication addon"
+ package_name "chef-sync"
+ ctl_command "chef-sync-ctl"
+ config_file "/etc/chef-sync/chef-sync.rb"
+ end
+end
diff --git a/lib/mixlib/install/script_generator.rb b/lib/mixlib/install/script_generator.rb
new file mode 100644
index 0000000..2e43630
--- /dev/null
+++ b/lib/mixlib/install/script_generator.rb
@@ -0,0 +1,217 @@
+#
+# Author:: Thom May (<thom at chef.io>)
+# Author:: Patrick Wright (<patrick at chef.io>)
+# Copyright:: Copyright (c) 2015 Chef, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "mixlib/install/util"
+require "cgi"
+
+module Mixlib
+ class Install
+ class ScriptGenerator
+ attr_accessor :version
+
+ attr_accessor :powershell
+
+ attr_accessor :prerelease
+
+ attr_accessor :nightlies
+
+ attr_accessor :install_flags
+
+ attr_accessor :endpoint
+
+ attr_accessor :root
+
+ attr_accessor :use_sudo
+
+ attr_reader :sudo_command
+
+ def sudo_command=(cmd)
+ if cmd.nil?
+ @use_sudo = false
+ else
+ @sudo_command = cmd
+ end
+ end
+
+ attr_accessor :http_proxy
+ attr_accessor :https_proxy
+
+ attr_accessor :omnibus_url
+ attr_accessor :install_msi_url
+
+ VALID_INSTALL_OPTS = %w{omnibus_url
+ endpoint
+ http_proxy
+ https_proxy
+ install_flags
+ install_msi_url
+ nightlies
+ prerelease
+ project
+ root
+ use_sudo
+ sudo_command}
+
+ def initialize(version, powershell = false, opts = {})
+ @version = (version || "latest").to_s.downcase
+ @powershell = powershell
+ @http_proxy = nil
+ @https_proxy = nil
+ @install_flags = nil
+ @prerelease = false
+ @nightlies = false
+ @endpoint = "metadata"
+ @omnibus_url = "https://www.chef.io/chef/install.sh"
+ @use_sudo = true
+ @sudo_command = "sudo -E"
+
+ @root = if powershell
+ "$env:systemdrive\\opscode\\chef"
+ else
+ "/opt/chef"
+ end
+
+ parse_opts(opts)
+ end
+
+ def install_command
+ vars = if powershell
+ install_command_vars_for_powershell
+ else
+ install_command_vars_for_bourne
+ end
+ shell_code_from_file(vars)
+ end
+
+ private
+
+ # Generates the install command variables for Bourne shell-based
+ # platforms.
+ #
+ # @return [String] shell variable lines
+ # @api private
+ def install_command_vars_for_bourne
+ flags = %w{latest true nightlies}.include?(version) ? "" : "-v #{CGI.escape(version)}"
+ flags << " " << "-n" if nightlies
+ flags << " " << "-p" if prerelease
+ flags << " " << install_flags if install_flags
+
+ [
+ shell_var("chef_omnibus_root", root),
+ shell_var("chef_omnibus_url", omnibus_url),
+ shell_var("install_flags", flags.strip),
+ shell_var("pretty_version", Util.pretty_version(version)),
+ shell_var("sudo_sh", sudo("sh")),
+ shell_var("version", version),
+ ].join("\n")
+ end
+
+ # Generates the install command variables for PowerShell-based platforms.
+ #
+ # @param version [String] version string
+ # @param metadata_url [String] The metadata URL for the Chef Omnitruck API server
+ # @param omnibus_root [String] The base directory the project is installed to
+ # @return [String] shell variable lines
+ # @api private
+ def install_command_vars_for_powershell
+ [
+ shell_var("chef_omnibus_root", root),
+ shell_var("msi", "$env:TEMP\\chef-#{version}.msi"),
+ ].tap { |vars|
+ if install_msi_url
+ vars << shell_var("chef_msi_url", install_msi_url)
+ else
+ vars << shell_var("chef_metadata_url", windows_metadata_url)
+ vars << shell_var("pretty_version", Util.pretty_version(version))
+ vars << shell_var("version", version)
+ end
+ }.join("\n")
+ end
+
+ def validate_opts!(opt)
+ err_msg = ["#{opt} is not a valid option",
+ "valid options are #{VALID_INSTALL_OPTS.join(" ")}"].join(",")
+ raise ArgumentError, err_msg unless VALID_INSTALL_OPTS.include?(opt.to_s)
+ end
+
+ def parse_opts(opts)
+ opts.each do |opt, setting|
+ validate_opts!(opt)
+ case opt.to_s
+ when "project", "endpoint"
+ self.endpoint = metadata_endpoint_from_project(setting)
+ else
+ send("#{opt.to_sym}=", setting)
+ end
+ end
+ end
+
+ def shell_code_from_file(vars)
+ fn = File.join(
+ File.dirname(__FILE__),
+ %w{.. .. .. support},
+ "install_command"
+ )
+ Util.shell_code_from_file(vars, fn, powershell,
+ http_proxy: http_proxy, https_proxy: https_proxy)
+ end
+
+ # Builds a shell variable assignment string for the required shell type.
+ #
+ # @param name [String] variable name
+ # @param value [String] variable value
+ # @return [String] shell variable assignment
+ # @api private
+ def shell_var(name, value)
+ Util.shell_var(name, value, powershell)
+ end
+
+ # @return the correct Chef Omnitruck API metadata endpoint, based on project
+ def metadata_endpoint_from_project(project = nil)
+ if project.nil? || project.casecmp("chef") == 0
+ "metadata"
+ else
+ "metadata-#{project.downcase}"
+ end
+ end
+
+ def windows_metadata_url
+ base = if omnibus_url =~ %r{/install.sh$}
+ "#{File.dirname(omnibus_url)}/"
+ end
+
+ url = "#{base}#{endpoint}"
+ url << "?p=windows&m=x86_64&pv=2008r2" # same package for all versions
+ url << "&v=#{CGI.escape(version)}" unless %w{latest true nightlies}.include?(version)
+ url << "&prerelease=true" if prerelease
+ url << "&nightlies=true" if nightlies
+ url
+ end
+
+ # Conditionally prefixes a command with a sudo command.
+ #
+ # @param command [String] command to be prefixed
+ # @return [String] the command, conditionaly prefixed with sudo
+ # @api private
+ def sudo(script)
+ use_sudo ? "#{sudo_command} #{script}" : script
+ end
+ end
+ end
+end
diff --git a/lib/mixlib/install/version.rb b/lib/mixlib/install/version.rb
index 9007f28..40012f0 100644
--- a/lib/mixlib/install/version.rb
+++ b/lib/mixlib/install/version.rb
@@ -1,5 +1,5 @@
module Mixlib
class Install
- VERSION = "0.7.1"
+ VERSION = "1.0.12"
end
end
diff --git a/metadata.yml b/metadata.yml
deleted file mode 100644
index 9a75bc3..0000000
--- a/metadata.yml
+++ /dev/null
@@ -1,101 +0,0 @@
---- !ruby/object:Gem::Specification
-name: mixlib-install
-version: !ruby/object:Gem::Version
- version: 0.7.1
-platform: ruby
-authors:
-- Thom May
-autorequire:
-bindir: bin
-cert_chain: []
-date: 2016-01-19 00:00:00.000000000 Z
-dependencies:
-- !ruby/object:Gem::Dependency
- name: bundler
- requirement: !ruby/object:Gem::Requirement
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- version: '0'
- type: :development
- prerelease: false
- version_requirements: !ruby/object:Gem::Requirement
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- version: '0'
-- !ruby/object:Gem::Dependency
- name: rake
- requirement: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '10.0'
- type: :development
- prerelease: false
- version_requirements: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '10.0'
-- !ruby/object:Gem::Dependency
- name: rspec
- requirement: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '3.3'
- type: :development
- prerelease: false
- version_requirements: !ruby/object:Gem::Requirement
- requirements:
- - - "~>"
- - !ruby/object:Gem::Version
- version: '3.3'
-description:
-email:
-- thom at chef.io
-executables: []
-extensions: []
-extra_rdoc_files: []
-files:
-- ".gitignore"
-- ".rspec"
-- ".rubocop.yml"
-- ".travis.yml"
-- Gemfile
-- LICENSE
-- README.md
-- Rakefile
-- lib/mixlib/install.rb
-- lib/mixlib/install/util.rb
-- lib/mixlib/install/version.rb
-- mixlib-install.gemspec
-- support/install_command.ps1
-- support/install_command.sh
-homepage: https://chef.io
-licenses:
-- Apache-2.0
-metadata: {}
-post_install_message:
-rdoc_options: []
-require_paths:
-- lib
-required_ruby_version: !ruby/object:Gem::Requirement
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- version: '0'
-required_rubygems_version: !ruby/object:Gem::Requirement
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- version: '0'
-requirements: []
-rubyforge_project:
-rubygems_version: 2.4.8
-signing_key:
-specification_version: 4
-summary: A mixin to help with omnitruck installs
-test_files: []
-has_rdoc:
diff --git a/mixlib-install.gemspec b/mixlib-install.gemspec
index a805b93..b619a7e 100644
--- a/mixlib-install.gemspec
+++ b/mixlib-install.gemspec
@@ -6,8 +6,8 @@ require "mixlib/install/version"
Gem::Specification.new do |spec|
spec.name = "mixlib-install"
spec.version = Mixlib::Install::VERSION
- spec.authors = ["Thom May"]
- spec.email = ["thom at chef.io"]
+ spec.authors = ["Thom May", "Patrick Wright"]
+ spec.email = ["thom at chef.io", "patrick at chef.io"]
spec.license = "Apache-2.0"
spec.summary = "A mixin to help with omnitruck installs"
@@ -17,9 +17,14 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
- # spec.required_ruby_version = ">= 2.0.0"
+ spec.add_dependency "artifactory"
+ spec.add_dependency "mixlib-versioning"
+ spec.add_dependency "mixlib-shellout"
spec.add_development_dependency "bundler"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec", "~> 3.3"
+ spec.add_development_dependency "pry"
+ spec.add_development_dependency "vcr"
+ spec.add_development_dependency "webmock", "~> 1.0"
end
diff --git a/support/install_command.ps1 b/support/install_command.ps1
index 345b1a0..887fb9d 100644
--- a/support/install_command.ps1
+++ b/support/install_command.ps1
@@ -3,7 +3,7 @@ Function Check-UpdateChef($root, $version) {
elseif ("$version" -eq "true") { return $false }
elseif ("$version" -eq "latest") { return $true }
- Try { $chef_version = (Get-Content $root\version-manifest.txt | select-object -first 1) }
+ Try { $chef_version = (Get-Content $root\version-manifest.txt -ErrorAction stop | select-object -first 1) }
Catch {
Try { $chef_version = (& $root\bin\chef-solo.bat -v) }
Catch { $chef_version = " " }
@@ -18,35 +18,45 @@ Function Get-ChefMetadata($url) {
Finally { if ($c -ne $null) { $c.Dispose() } }
$md = ConvertFrom-StringData $response.Replace("`t", "=")
- return @($md.url, $md.md5)
+ return @($md.url, $md.sha256)
}
-Function Get-MD5Sum($src) {
+Function Get-SHA256($src) {
Try {
- $c = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
+ $c = New-Object -TypeName System.Security.Cryptography.SHA256Managed
$bytes = $c.ComputeHash(($in = (Get-Item $src).OpenRead()))
return ([System.BitConverter]::ToString($bytes)).Replace("-", "").ToLower()
} Finally { if (($c -ne $null) -and ($c.GetType().GetMethod("Dispose") -ne $null)) { $c.Dispose() }; if ($in -ne $null) { $in.Dispose() } }
}
-Function Download-Chef($url, $md5, $dst) {
+Function Download-Chef($url, $sha256, $dst) {
Try {
Log "Downloading package from $url"
($c = Make-WebClient).DownloadFile($url, $dst)
Log "Download complete."
} Finally { if ($c -ne $null) { $c.Dispose() } }
- if ($md5 -eq $null) { Log "Skipping md5 verification" }
- elseif (($dmd5 = Get-MD5Sum $dst) -eq $md5) { Log "Successfully verified $dst" }
- else { throw "MD5 for $dst $dmd5 does not match $md5" }
+ if ($sha256 -eq $null) { Log "Skipping sha256 verification" }
+ elseif (($dsha256 = Get-SHA256 $dst) -eq $sha256) { Log "Successfully verified $dst" }
+ else { throw "SHA256 for $dst $dsha256 does not match $sha256" }
}
Function Install-Chef($msi) {
Log "Installing Chef Omnibus package $msi"
- $p = Start-Process -FilePath "msiexec.exe" -ArgumentList "/qn /i $msi" -Passthru -Wait
-
- if ($p.ExitCode -ne 0) { throw "msiexec was not successful. Received exit code $($p.ExitCode)" }
-
+ $installingChef = $True
+ $installAttempts = 0
+ while ($installingChef) {
+ $installAttempts++
+ $p = Start-Process -FilePath "msiexec.exe" -ArgumentList "/qn /i $msi" -Passthru -Wait
+ $p.WaitForExit()
+ if ($p.ExitCode -eq 1618) {
+ Log "Another msi install is in progress (exit code 1618), retrying ($($installAttempts))..."
+ continue
+ } elseif ($p.ExitCode -ne 0) {
+ throw "msiexec was not successful. Received exit code $($p.ExitCode)"
+ }
+ $installingChef = $False
+ }
Remove-Item $msi -Force
Log "Installation complete"
}
@@ -72,12 +82,12 @@ $msi = Unresolve-Path $msi
if (Check-UpdateChef $chef_omnibus_root $version) {
Write-Host "-----> Installing Chef Omnibus ($pretty_version)`n"
if ($chef_metadata_url -ne $null) {
- $url, $md5 = Get-ChefMetadata "$chef_metadata_url"
+ $url, $sha256 = Get-ChefMetadata "$chef_metadata_url"
} else {
$url = $chef_msi_url
- $md5 = $null
+ $sha256 = $null
}
- Download-Chef "$url" $md5 $msi
+ Download-Chef "$url" $sha256 $msi
Install-Chef $msi
} else {
Write-Host "-----> Chef Omnibus installation detected ($pretty_version)`n"
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-mixlib-install.git
More information about the Pkg-ruby-extras-commits
mailing list