#!/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 ] [-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 :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]) 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::WARN, 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::WARN, 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) 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