bin/mu-deploy
#!/usr/local/ruby-current/bin/ruby
# Copyright:: Copyright (c) 2014 eGlobalTech, Inc., all rights reserved
#
# Licensed under the BSD-3 license (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License in the root of the project or at
#
# http://egt-labs.com/mu/LICENSE.html
#
# 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 File.realpath(File.expand_path(File.dirname(__FILE__)+"/mu-load-config.rb"))
# now we have our global config available as the read-only hash $MU_CFG
require 'rubygems'
require 'bundler/setup'
require 'json'
require 'erb'
require 'optimist'
require 'json-schema'
require 'mu'
$opts = Optimist::options do
banner <<-EOS
Usage:
#{$0} [-e <environment>] [-r region] [-v] [-d] [-w] [-c] [-n] [-s] [-j] [-p parameter=value] /path/to/stack/config.[json|yaml] [-u deploy_id [-l]] [-r deploy_id]
EOS
opt :environment, "Environment to set on creation.", :require => false, :default => "dev"
opt :region, "Default region for newly-created cloud resources.", :require => false, :default => MU.myRegion, :type => :string
opt :nocleanup, "Skip cleaning up resources on failed deployments. Used for debugging.", :require => false, :default => false
opt :web, "Generate web-friendly (HTML) output.", :require => false, :default => false, :type => :boolean
opt :dryrun, "Do not build a stack, only run configuration validation.", :require => false, :default => false, :type => :boolean
opt :skipinitialupdates, "Node bootstrapping normally runs an internal recipe that does a full system update. This disables that behavior.", :require => false, :default => false, :type => :boolean
opt :parameter, "Pass a parameter to the configuration parser (Name=Value). This will be presented to your config file as the ERB variable $Name.", :require => false, :type => :string, :multi => true
opt :update, "Update the stored configuration of an existing deployment, instead of creating a new deploy.", :require => false, :type => :string
opt :cloud, "Pass a parameter named 'cloud' to the stack, and set it as the default cloud platform for all resources if not explicitly declared. Must be one of: #{MU::Cloud.availableClouds.join(", ")}", :require => false, :type => :string
opt :cloudformation, "Emit Amazon Web Services targets into a CloudFormation template instead of building real services.", :require => false, :default => false, :type => :boolean
opt :cloudformation_output, "When emitting a CloudFormation template, put the final product in this location instead of in /tmp. Takes a local file path or an s3:// URI. S3 uploads will be given AUTHENTICATED-READ permissions.", :require => false, :type => :string
opt :verbose, "Display debugging output.", :require => false, :default => false, :type => :boolean
opt :quiet, "Display minimal output.", :require => false, :default => false, :type => :boolean
opt :color, "Display log output in human-friendly colors.", :require => false, :default => true, :type => :boolean
opt :credentials, "Set the default credential set to use for resources which do not specify a default", :require => false, :type => :string
end
verbosity = MU::Logger::NORMAL
verbosity = MU::Logger::LOUD if $opts[:verbose]
verbosity = MU::Logger::QUIET if $opts[:quiet]
if $opts[:verbose] and $opts[:quiet]
MU.log "Cannot set both --verbose and --quiet", MU::ERR
exit 1
end
if $opts[:liveupdate] and !$opts[:update]
MU.log "--liveupdate only valid when combined with --update", MU::ERR
exit 1
end
MU.setVar("curRegion", $opts[:region]) if $opts[:region]
MU.setLogging(verbosity, $opts[:web], STDOUT, $opts[:color])
# Parse any paramater options into something useable.
params = Hash.new
$opts[:parameter].each { |param|
name, value = param.split(/\s*=\s*/, 2)
params[name] = value
}
# We want our config files (which can be ERB templates) to have this variable
# available to them.
$environment = $opts[:environment]
if !ARGV[0] or ARGV[0].empty?
MU.log("You must specify a stack configuration file!", MU::ERR, html: $opts[:web])
exit 1
end
begin
config = File.realdirpath(ARGV[0])
File.read(config)
rescue Errno::ENOENT => e
MU.log "#{e.message}", MU::ERR, html: $opts[:web]
exit 1
end
MU.log "Loading #{config}", html: $opts[:web], details: $opts
conf_engine = MU::Config.new(config, $opts[:skipinitialupdates], params: params, updating: $opts[:update], default_credentials: $opts[:credentials], cloud: $opts[:cloud])
stack_conf = conf_engine.config
if $opts[:dryrun] or $opts[:verbose]
puts MU::Config.stripConfig(stack_conf).to_yaml
conf_engine.visualizeDependencies
end
if $opts[:dryrun]
MU.log("#{$config} loaded successfully.", html: $opts[:web])
if MU::Cloud::AWS.hosted # XXX actually, check whether we're targeting AWS resources
# I do not understand why this is necessary, but here we are.
Thread.handle_interrupt(MU::Cloud::MuCloudResourceNotImplemented => :never) {
begin
Thread.handle_interrupt(MU::Cloud::MuCloudResourceNotImplemented => :immediate) {
MU.log "Cost calculator not available for this stack, as it uses a resource not implemented in Mu's CloudFormation layer.", MU::NOTICE, verbosity: MU::Logger::NORMAL
Thread.current.exit
}
ensure
end
}
begin
cost_dummy_deploy = MU::Deploy.new(
$opts[:environment],
verbosity: MU::Logger::SILENT,
color: $opts[:color],
force_cloudformation: true,
cloudformation_path: "/dev/null",
nocleanup: false,
no_artifacts: true,
stack_conf: stack_conf
)
cost_dummy_deploy.run
rescue MU::Cloud::MuCloudResourceNotImplemented, MU::Cloud::MuCloudFlagNotImplemented
MU.log "Cost calculator not available for this stack, as it uses a resource not implemented in Mu's CloudFormation layer.", MU::NOTICE, verbosity: MU::Logger::NORMAL
end
end
exit
end
if $opts[:update]
deploy = MU::MommaCat.new($opts[:update])
# TODO consider whether this is useful/valid
# old_conf = JSON.parse(File.read(deploy.deploy_dir+"/basket_of_kittens.json"))
# stack_conf = old_conf.merge(stack_conf)
deploy.updateBasketofKittens(stack_conf, skip_validation: true)
deployer = MU::Deploy.new(
deploy.environment,
verbosity: verbosity,
color: $opts[:color],
webify_logs: $opts[:web],
nocleanup: true, # don't accidentally blow up an existing deploy
stack_conf: stack_conf,
deploy_id: $opts[:update],
deploy_obj: deploy
)
deployer.run
exit 0
end
$application_cookbook = stack_conf["application_cookbook"]
Dir.chdir(MU.installDir)
cfm_path = "/tmp/cloudformation-#{stack_conf['appname']}.json"
if !$opts[:cloudformation_output].nil?
cfm_path = $opts[:cloudformation_output]
end
deployer = MU::Deploy.new(
$opts[:environment],
verbosity: verbosity,
color: $opts[:color],
webify_logs: $opts[:web],
nocleanup: $opts[:nocleanup],
cloudformation_path: cfm_path,
force_cloudformation: $opts[:cloudformation],
stack_conf: stack_conf
)
deployer.run