cloudamatic/mu

View on GitHub
bin/mu-adopt

Summary

Maintainability
Test Coverage
#!/usr/local/ruby-current/bin/ruby
#
# Copyright:: Copyright (c) 2019 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.expand_path(File.dirname(__FILE__))+"/mu-load-config.rb"

require 'rubygems'
require 'bundler/setup'
require 'optimist'
require 'mu'

available_types = MU::Cloud.resource_types.keys.map { |t| t.to_s }
grouping_options = {
  "logical" => "Group resources in logical layers (folders and habitats together, users/roles/groups together, network resources together, etc)",
  "omnibus" => "Jam everything into one monolothic configuration"
}

$opt = Optimist::options do
  banner <<-EOS
#{$0}
  EOS
  opt :appname, "The overarching name of the application stack we will generate", :required => false, :default => "mu", :type => :string
  opt :types, "The resource types to scan and import. Valid types: #{available_types.join(", ")}", :required => false, :type => :strings, :default => available_types
  opt :clouds, "The cloud providers to scan and import.", :required => false, :type => :strings, :default => MU::Cloud.availableClouds
  opt :parent, "Where applicable, resources which reside in the root folder or organization are configured with the specified parent in our target BoK", :required => false, :type => :string
  opt :billing, "Force-set this billing entity on created resources, instead of copying from the live resources", :required => false, :type => :string
  opt :sources, "One or more sets of credentials to use when importing resources. By default we will search and import from all sets of available credentials for each cloud provider specified with --clouds", :required => false, :type => :strings
  opt :credentials, "Override the 'credentials' value in our generated Baskets of Kittens to target a single, specific account. Our default behavior is to set each resource to deploy into the account from which it was sourced.", :required => false, :type => :string
  opt :savedeploys, "Generate actual deployment metadata in #{MU.dataDir}/deployments, as though the resources we found were created with mu-deploy. If we are generating more than one configuration, and a resource needs to reference another resource (e.g. to declare a VPC in which to reside), this will allow us to reference them as virtual resource, rather than by raw cloud identifier.", :required => false, :type => :boolean, :default => false
  opt :diff, "List the differences between what we find and an existing, saved deploy from a previous run, if one exists.", :required => false, :type => :boolean
  opt :merge_changes, "When using --diff, merge detected changes into the baseline deploy after reporting on them.", :required => false, :type => :boolean, :default => false
  opt :grouping, "Methods for grouping found resources into separate Baskets.\n\n"+MU::Adoption::GROUPMODES.keys.map { |g| "* "+g.to_s+": "+MU::Adoption::GROUPMODES[g] }.join("\n")+"\n\n", :required => false, :type => :string, :default => "logical"
  opt :habitats, "Limit scope of searches to the named accounts/projects/subscriptions, instead of search all habitats visible to our credentials.", :required => false, :type => :strings
  opt :regions, "Restrict to operating on a subset of available regions, instead of all that we know about.", :require => false, :type => :strings
  opt :scrub, "Whether to set scrub_mu_isms in the BoKs we generate", :default => $MU_CFG.has_key?('adopt_scrub_mu_isms') ? $MU_CFG['adopt_scrub_mu_isms'] : false
  opt :pattern, "Only adopt resources whose resource name would match this pattern. Must be a valid regular expression. Alphabetical characters will be treated case-insensitively.", :required => false, :type => :string
end

ok = true

app_pattern = Regexp.new('^[a-z][0-9a-z\-_]{0,10}[a-z0-9]$', true)

if !$opt[:appname] or !app_pattern.match($opt[:appname])
  MU.log "--appname must match pattern #{app_pattern.to_s}", MU::ERR
  exit 1
end

if $opt[:diff]
  $opt[:savedeploys] = false
end

pattern = nil
if $opt[:pattern]
  begin
    pattern = Regexp.new($opt[:pattern], true)
  rescue RegexpError => e
    MU.log "Invalid --pattern option: #{e.message}", MU::ERR
    exit 1
  end
end

types = []
$opt[:types].each { |t|
  t_name = t.gsub(/-/, "_")
  t_name.gsub!(/^[^a-z0-9]|[^a-z0-9]$/i, "")
  shortclass, name, plural, classname = MU::Cloud.getResourceNames(t_name)
  if !classname
    MU.log "'#{t}' does not map to a valid Mu resource type", MU::ERR
    ok = false
  else
    types << shortclass
  end
}

clouds = []
if !$opt[:clouds] or $opt[:clouds].empty?
  MU.log "At least one cloud must be specified", MU::ERR
  ok = false
end
$opt[:clouds].each { |cloud|
  found_match = false
  MU::Cloud.supportedClouds.each { |known_cloud|
    if cloud.match(/^[^a-z0-9]*?#{Regexp.quote(known_cloud)}[^a-z0-9]*?$/i)
      clouds << known_cloud
      found_match = true
      break
    end
  }
  if !found_match
    MU.log "'#{cloud}' does not map to a valid Mu cloud layer", MU::ERR
    ok = false
  end
}

if !ok
  puts "Invoke with --help for more information."
  exit 1
end

adoption = MU::Adoption.new(clouds: clouds, types: types, parent: $opt[:parent], billing: $opt[:billing], sources: $opt[:sources], credentials: $opt[:credentials], group_by: $opt[:grouping].to_sym, savedeploys: $opt[:savedeploys], diff: $opt[:diff], habitats: $opt[:habitats], scrub_mu_isms: $opt[:scrub], regions: $opt[:regions], merge: $opt[:merge_changes], pattern: pattern)
found = adoption.scrapeClouds
if found.nil? or found.empty?
  MU.log "No resources found to adopt", MU::WARN, details: {"clouds" => clouds, "types" => types }
  exit
end
MU.log "Generating baskets", MU::DEBUG
boks = adoption.generateBaskets(prefix: $opt[:appname])

boks.each_pair { |appname, bok|
  MU.log "Writing to #{appname}.yaml"
  File.open("#{appname}.yaml", "w") { |f|
    f.write JSON.parse(JSON.generate(bok)).to_yaml
  }
#  puts stack_conf.to_yaml
  MU::Cloud.resource_types.each_pair { |type, cfg|
    if bok[cfg[:cfg_plural]]
      MU.log "#{bok[cfg[:cfg_plural]].size.to_s} #{cfg[:cfg_plural]}", MU::NOTICE
    end
  }
}