bin/mu-gcp-setup
#!/usr/local/ruby-current/bin/ruby
#
# Copyright:: Copyright (c) 2017 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.
# Perform initial Mu setup tasks:
# 1. Set up an appropriate Security Group
# 2. Associate a specific Elastic IP address to this MU server, if required.
# 3. Create an S3 bucket for Mu logs.
require 'etc'
require 'securerandom'
require File.expand_path(File.dirname(__FILE__))+"/mu-load-config.rb"
require 'rubygems'
require 'bundler/setup'
require 'json'
require 'erb'
require 'optimist'
require 'json-schema'
require 'mu'
require 'mu/master/ssl'
Dir.chdir(MU.installDir)
$opts = Optimist::options do
banner <<-EOS
Usage:
#{$0} [-i] [-s] [-l] [-u] [-d]
EOS
# opt :ip, "Attempt to configure the IP requested in the CHEF_PUBLIC_IP environment variable, or if none is set, to associate an arbitrary Elastic IP.", :require => false, :default => false, :type => :boolean
opt :sg, "Attempt to configure a Security Group with appropriate permissions.", :require => false, :default => false, :type => :boolean
opt :logs, "Ensure the presence of an Cloud Storage bucket prefixed with 'Mu_Logs' for use with CloudTrails, syslog, etc.", :require => false, :default => false, :type => :boolean
# opt :dns, "Ensure the presence of a private DNS Zone called for internal amongst Mu resources.", :require => false, :default => false, :type => :boolean
opt :uploadlogs, "Push today's log files to the Cloud Storage bucket created by the -l option.", :require => false, :default => false, :type => :boolean
opt :optdisk, "Create a block volume for /opt and slide our installation onto it", :require => false, :default => false, :type => :boolean
end
if MU::Cloud::Google.hosted? and !$MU_CFG['google']
new_cfg = $MU_CFG.dup
cfg_blob = MU::Cloud::Google.hosted_config
if cfg_blob
cfg_blob['log_bucket_name'] ||= $MU_CFG['hostname']
new_cfg["google"] = { "default" => cfg_blob }
MU.log "Adding auto-detected Google stanza to #{cfgPath}", MU::NOTICE
if new_cfg != $MU_CFG or !cfgExists?
MU.log "Generating #{cfgPath}"
saveMuConfig(new_cfg)
$MU_CFG = new_cfg
end
end
end
my_instance_id = MU::Cloud::AWS.getAWSMetaData("instance-id")
if MU::Cloud::Google.hosted?
instance = MU.myCloudDescriptor
admin_sg_name = MU.myInstanceId+"-"+MU.myVPC+"-ingress-allow"
if !instance.tags.items or !instance.tags.items.include?(admin_sg_name)
newitems = instance.tags.items ? instance.tags.items.dup : []
newitems << admin_sg_name
MU.log "Setting my instance tags", MU::NOTICE, details: newitems
newtags = MU::Cloud::Google.compute(:Tags).new(
fingerprint: instance.tags.fingerprint,
items: newitems
)
MU::Cloud::Google.compute.set_instance_tags(
MU::Cloud::Google.myProject,
MU.myAZ,
MU.myInstanceId,
newtags
)
instance = MU.myCloudDescriptor
end
preferred_ip = MU.mu_public_ip
end
# Create a security group, or manipulate an existing one, so that we have all
# of the appropriate network holes.
if $opts[:sg]
open_ports = [80, 443, MU.mommaCatPort, 7443, 8443, 9443, 8200]
found = MU::MommaCat.findStray("Google", "firewall_rule", dummy_ok: true, cloud_id: admin_sg_name)
found.reject! { |v| v.cloud_desc.network != MU.myVPC }
admin_sg = found.first if !found.nil? and found.size > 0
rules = []
open_ports.each { |port|
rules << {
"proto" => "tcp",
"port" => port.to_s,
"hosts" => ["0.0.0.0/0"]
}
}
# TODO this is getting subsumed in all the 0.0.0.0/0 above; what we really want is a separate rule for this
rules << {
"proto" => "tcp",
"port" => 22,
"hosts" => ["#{preferred_ip}/32"]
}
cfg = {
"name" => admin_sg_name,
"scrub_mu_isms" => true,
"cloud" => "Google",
"rules" => rules,
"project" => MU::Cloud::Google.myProject,
"target_tags" => [admin_sg_name],
"vpc" => {
"id" => MU.myVPC
}
}
if !admin_sg
admin_sg = MU::Cloud::FirewallRule.new(kitten_cfg: cfg, mu_name: admin_sg_name)
begin
admin_sg.create
rescue ::Google::Apis::ClientError => e
raise e if !e.message.match(/alreadyExists: /)
ensure
admin_sg.groom
end
else
admin_sg.groom
end
end
if $opts[:optdisk] and !File.open("/etc/mtab").read.match(/ \/opt[\s\/]/)
myname = MU::Cloud::Google.getGoogleMetaData("instance/name")
wd = Dir.getwd
Dir.chdir("/")
if File.exists?("/opt/opscode/bin/chef-server-ctl")
system("/opt/opscode/bin/chef-server-ctl stop")
end
if !File.exists?("/sbin/mkfs.xfs")
system("/usr/bin/yum -y install xfsprogs")
end
MU::Master.disk(myname+"-mu-opt", "/opt_tmp", 30)
uuid = MU::Master.diskUUID(myname+"-mu-opt")
if !uuid or uuid.empty?
MU.log "Failed to retrieve UUID of block device #{myname}-mu-opt", MU::ERR, details: MU::Cloud::AWS.realDevicePath(myname+"-mu-opt")
exit 1
end
MU.log "Moving contents of /opt to /opt_tmp", MU::NOTICE
system("/bin/mv /opt/* /opt_tmp/")
exit 1 if $?.exitstatus != 0
MU.log "Remounting /opt_tmp /opt", MU::NOTICE
system("/bin/umount /opt_tmp")
exit 1 if $?.exitstatus != 0
system("echo '#{uuid} /opt xfs defaults 0 0' >> /etc/fstab")
system("/bin/mount -a")
exit 1 if $?.exitstatus != 0
if File.exists?("/opt/opscode/bin/chef-server-ctl")
system("/opt/opscode/bin/chef-server-ctl start")
end
Dir.chdir(wd)
end
$bucketname = MU::Cloud::Google.adminBucketName
if $opts[:logs]
MU::Cloud::Google.listCredentials.each { |credset|
bucketname = MU::Cloud::Google.adminBucketName(credset)
exists = false
MU.log "Configuring log and secret Google Cloud Storage bucket '#{bucketname}'"
bucket = nil
begin
bucket = MU::Cloud::Google.storage(credentials: credset).get_bucket(bucketname)
rescue ::Google::Apis::ClientError => e
if e.message.match(/notFound:/)
MU.log "Creating #{bucketname} bucket"
bucketobj = MU::Cloud::Google.storage(:Bucket).new(
name: bucketname,
location: "US", # XXX why is this needed?
versioning: MU::Cloud::Google.storage(:Bucket)::Versioning.new(
enabled: true
),
lifecycle: MU::Cloud::Google.storage(:Bucket)::Lifecycle.new(
rule: [ MU::Cloud::Google.storage(:Bucket)::Lifecycle::Rule.new(
action: MU::Cloud::Google.storage(:Bucket)::Lifecycle::Rule::Action.new(
type: "SetStorageClass",
storage_class: "DURABLE_REDUCED_AVAILABILITY"
),
condition: MU::Cloud::Google.storage(:Bucket)::Lifecycle::Rule::Condition.new(
age: 180
)
)]
)
)
bucket = MU::Cloud::Google.storage(credentials: credset).insert_bucket(
MU::Cloud::Google.defaultProject(credset),
bucketobj
)
else
pp e.backtrace
raise MU::MuError, e.inspect
end
end
ebs_key = nil
begin
ebs_key = MU::Cloud::Google.storage(credentials: credset).get_object(bucketname, "log_vol_ebs_key")
rescue ::Google::Apis::ClientError => e
if e.message.match(/notFound:/)
# XXX this may not be useful outside of AWS
MU.log "Creating new key for encrypted log volume"
key = SecureRandom.random_bytes(32)
f = Tempfile.new("logvolkey") # XXX this is insecure and stupid
f.write key
f.close
objectobj = MU::Cloud::Google.storage(:Object).new(
bucket: bucketname,
name: "log_vol_ebs_key"
)
ebs_key = MU::Cloud::Google.storage(credentials: credset).insert_object(
bucketname,
objectobj,
upload_source: f.path
)
f.unlink
else
raise MuError, e.inspect
end
end
myname = MU::Cloud::Google.getGoogleMetaData("instance/name")
MU::Master.disk("/dev/"+myname+"-mu-logs", "/Mu_Logs", 50, "log_vol_ebs_key", "ram7")
}
end
if $opts[:dns]
end
if $opts[:uploadlogs]
today = Time.new.strftime("%Y%m%d").to_s
["master.log", "nodes.log"].each { |log|
if File.exist?("/Mu_Logs/#{log}-#{today}")
MU.log "Uploading /Mu_Logs/#{log}-#{today} to bucket #{$bucketname}"
MU::Cloud::AWS.s3.put_object(
bucket: $bucketname,
key: "#{log}/#{today}",
body: File.read("/Mu_Logs/#{log}-#{today}")
)
else
MU.log "No log /Mu_Logs/#{log}-#{today} was found", MU::WARN
end
}
end