cloudamatic/mu

View on GitHub
modules/mu/providers/cloudformation/dnszone.rb

Summary

Maintainability
D
1 day
Test Coverage
# Copyright:: Copyright (c) 2016 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.

module MU
  class Cloud
    class CloudFormation
      # A log target as configured in {MU::Config::BasketofKittens::dnszones}
      class DNSZone < MU::Cloud::DNSZone

        @deploy = nil
        attr_reader :mu_name
        attr_reader :config
        attr_reader :cloud_id

        attr_reader :cfm_template
        attr_reader :cfm_name

        # @param mommacat [MU::MommaCat]: A {MU::Mommacat} object containing the deploy of which this resource is/will be a member.
        # @param kitten_cfg [Hash]: The fully parsed and resolved {MU::Config} resource descriptor as defined in {MU::Config::BasketofKittens::dnszones}
        def initialize(mommacat: nil, kitten_cfg: nil, mu_name: nil, cloud_id: nil)
          @deploy = mommacat
          @config = kitten_cfg
          @cloud_id ||= cloud_id
          if !mu_name.nil?
            @mu_name = mu_name
          else
            @mu_name ||= @deploy.getResourceName(@config["name"])
          end
        end

        # Populate @cfm_template with a resource description for this dnszone
        # in CloudFormation language.
        def create
          @cfm_name, @cfm_template = MU::Cloud::CloudFormation.cloudFormationBase("dnszone", self, scrub_mu_isms: @config['scrub_mu_isms'])
          MU::Cloud::CloudFormation.setCloudFormationProp(@cfm_template[@cfm_name], "Name", @config['name'])
          MU::Cloud::CloudFormation.setCloudFormationProp(@cfm_template[@cfm_name], "HostedZoneConfig", { "Comment" => MU.deploy_id })

          if @config['private']
            if @config['all_account_vpcs']
# XXX would need a way to add tails (CloudFormation paramaters, effectively) here in order to prompt the user for stuff like which pre-existing VPCs to plug in. Maybe we can have the config parser do that for is and pass it all in as @config['vpcs'], which would in turn be Refs to parameters? It'd need to know about the -c flag for AWS resources being converted on the fly.
#            MU::Config.getTail("#{parent_name}vpc_id", value: vpc_block["vpc_id"], prettyname: "#{parent_name} Target VPC",  cloudtype: "AWS::EC2::VPC::Id")
              raise MuCloudFlagNotImplemented, "DNSZone parameter 'all_account_vpcs' currently not supported for CloudFormation targets"
            else
              raise MuError, "DNS Zone #{@config['name']} is flagged as private, you must provide a VPC to allow access." if @config['vpcs'].nil? || @config['vpcs'].empty?
              @config['vpcs'].each { |vpc|
                MU::Cloud::CloudFormation.setCloudFormationProp(@cfm_template[@cfm_name], "VPCs", { "VPCId" => vpc['vpc_id'], "VPCRegion" => vpc['region'] })
              }
            end
          end

          @config['records'].each { |dnsrec|
            dnsrec['realtarget'] =
              if dnsrec['mu_type'] == "loadbalancer"

                if @dependencies.has_key?("loadbalancer") && dnsrec['deploy_id'].nil?
                  if dnsrec['type'] == "R53ALIAS"
                    dnsrec['alias_zone'] = { "Fn::GetAtt" => [@dependencies["loadbalancer"][dnsrec['target']].cloudobj.cfm_name, "CanonicalHostedZoneNameID"] }
                  end
                  { "Fn::GetAtt" => [@dependencies["loadbalancer"][dnsrec['target']].cloudobj.cfm_name, "DNSName"] }
                elsif dnsrec['deploy_id']
                  found = MU::MommaCat.findStray("AWS", "loadbalancer", deploy_id: dnsrec["deploy_id"], mu_name: dnsrec["target"], region: @config["region"])
                  raise MuError, "Couldn't find #{dnsrec['mu_type']} #{dnsrec["target"]}" if found.nil? || found.empty?
                  found.first.deploydata['dns']
                end
              elsif dnsrec['mu_type'] == "server"
                public = true
                if dnsrec.has_key?("target_type")
                  public = dnsrec["target_type"] == "private" ? false : true
                end
                if @dependencies.has_key?("server") && dnsrec['deploy_id'].nil?
                  if dnsrec["type"] == "CNAME"
                    if public
                      { "Fn::GetAtt" => [@dependencies["server"][dnsrec['target']].cloudobj.cfm_name, "PublicDnsName"] }
                    else
                      { "Fn::GetAtt" => [@dependencies["server"][dnsrec['target']].cloudobj.cfm_name, "PrivateDnsName"] }
                    end
                  elsif dnsrec["type"] == "A"
                    if public
                      { "Fn::GetAtt" => [@dependencies["server"][dnsrec['target']].cloudobj.cfm_name, "PublicIp"] }
                    else
                      { "Fn::GetAtt" => [@dependencies["server"][dnsrec['target']].cloudobj.cfm_name, "PrivateIp"] }
                    end
                  end
                elsif dnsrec['deploy_id']
                  found = MU::MommaCat.findStray("AWS", "server", deploy_id: dnsrec["deploy_id"], mu_name: dnsrec["target"], region: @config["region"])
                  raise MuError, "Couldn't find #{dnsrec['mu_type']} #{dnsrec["target"]}" if found.nil? || found.empty?
                  deploydata = found.first.deploydata
                  if dnsrec["type"] == "CNAME"
                    if public
                      deploydata['public_dns_name'].empty? ? deploydata['private_dns_name'] : deploydata['public_dns_name']
                    else
                      deploydata['private_dns_name']
                    end
                  elsif dnsrec["type"] == "A"
                    if public
                      deploydata['public_ip_address'] ? deploydata['public_ip_address'] : deploydata['private_ip_address']
                    else
                      deploydata['private_ip_address']
                    end
                  end
                end
              elsif dnsrec['mu_type'] == "database"
                if @dependencies.has_key?(dnsrec['mu_type']) && dnsrec['deploy_id'].nil?
                  { "Fn::GetAtt" => [@dependencies["database"][dnsrec['target']].cloudobj.cfm_name, "Endpoint.Address"] }
                elsif dnsrec['deploy_id']
                  found = MU::MommaCat.findStray("AWS", "database", deploy_id: dnsrec["deploy_id"], mu_name: dnsrec["target"], region: @config["region"])
                  raise MuError, "Couldn't find #{dnsrec['mu_type']} #{dnsrec["target"]}" if found.nil? || found.empty?
                  found.first.deploydata['endpoint']
                end
              end
            if !dnsrec['hosted_zone_id']
              dnsrec['hosted_zone_id'] = { "Ref" => @cfm_name }
            end
            dnsrec['hosted_zone_name'] = @config['name']

            if dnsrec['append_environment_name']
              dnsrec['name'] = dnsrec['name'] + "." + @deploy.environment.downcase
            end
            dnsrec['name'] = dnsrec['name'] + "." + @config['name'] + "."
          }
          records = MU::Cloud::CloudFormation::DNSZone.createRecordsFromConfig(@config['records'])
          records.each_pair { |name, rec|
            MU::Cloud::CloudFormation.setCloudFormationProp(records[name], "DependsOn", @cfm_name)

          }
          @cfm_template.merge!(records)
        end

        # @param cfg [Array]: An array of parsed {MU::Config::BasketofKittens::dnszones::records} objects.
        # @param target [String]: Optional target for the records to be created. Overrides targets embedded in cfg records.
        def self.createRecordsFromConfig(cfg, target: nil)
          templates = {}
          counts = {}
          cfg.each { |dnsrec|
          target = dnsrec['realtarget'] ? dnsrec['realtarget'] : dnsrec['target']
            dnsrec['realtarget'] = target if !dnsrec['realtarget']
            if !counts.has_key?(target)
              counts[target] = 1
            else
              counts[target] = counts[target] + 1
            end
          }
          cfg.each { |dnsrec|
            rec_name, rec_template = MU::Cloud::CloudFormation.cloudFormationBase("dnsrecord", name: dnsrec['name']+dnsrec['target']+dnsrec['type'], scrub_mu_isms: dnsrec['scrub_mu_isms'])
            MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "Name", dnsrec['name'])

            if dnsrec['type'] == "R53ALIAS"
              alias_target = {
                "DNSName" => dnsrec['realtarget'],
                "HostedZoneId" => dnsrec['alias_zone']
              }
              MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "AliasTarget", alias_target)
              MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "Type", "A")
            else
              MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "ResourceRecords", dnsrec['realtarget'])
              MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "TTL", dnsrec['ttl'])
              MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "Type", dnsrec['type'])
            end

            if counts[dnsrec['realtarget']] > 1
              MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "SetIdentifier", rec_name)
            end

            if dnsrec['geo_location']
            MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "SetIdentifier", rec_name)
              loc = {}
              ["continent_code", "country_code", "subdivision_code"].each { |arg|
                if !dnsrec['geo_location'][arg].nil?
                  key = ""
                  arg.split(/_/).each { |chunk| key = key + chunk.capitalize }
                  loc[key] = dnsrec['geo_location'][arg]
                end
              }
              MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "GeoLocation", loc)
            end

            if dnsrec['healthcheck']
              check_name, check_template = MU::Cloud::CloudFormation.cloudFormationBase("dnshealthcheck", name: dnsrec['name']+dnsrec['target']+dnsrec['type'], scrub_mu_isms: dnsrec['scrub_mu_isms'])
              check = {
                "Type" => dnsrec['healthcheck']['method']
              }
              dnsrec['healthcheck']["request_interval"] = dnsrec['healthcheck']["check_interval"]
              dnsrec['healthcheck']["resource_path"] = dnsrec['healthcheck']["path"]
              ["failure_threshold", "resource_path", "port", "search_string", "request_internal"].each { |arg|
                if !dnsrec['healthcheck'][arg].nil?
                  key = ""
                  arg.split(/_/).each { |chunk| key = key + chunk.capitalize }
                  check[key] = dnsrec['healthcheck'][arg]
                end
              }
              if ["A", "AAAA"].include?(dnsrec['type'])
                check["IPAddress"] = dnsrec['realtarget']
              else
                check["FullyQualifiedDomainName"] = dnsrec['realtarget']
              end
              MU::Cloud::CloudFormation.setCloudFormationProp(check_template[check_name], "HealthCheckConfig", check)

              MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "HealthCheckId", { "Ref" => check_name })
              MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "DependsOn", check_name)
              rec_template.merge!(check_template)
            end

            MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "Failover", dnsrec['failover']) if dnsrec['failover']
            MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "Region", dnsrec['region']) if dnsrec['region']
            MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "Weight", dnsrec['weight']) if dnsrec['weight']
            if dnsrec['hosted_zone_id']
              MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "HostedZoneId", dnsrec['hosted_zone_id'])
            elsif dnsrec['hosted_zone_name']
              MU::Cloud::CloudFormation.setCloudFormationProp(rec_template[rec_name], "HostedZoneName", dnsrec['hosted_zone_name'])
            else
              raise MuError, "Records must have either hosted_zone_name or hosted_zone_id into MU::Clouds::CloudFormation::DNSZone.createRecordsFromConfig"
            end
            if rec_template[rec_name]["Properties"]["ResourceRecords"].size == 0
              rec_template[rec_name]["Properties"].delete("ResourceRecords")
            end
            templates.merge!(rec_template)
          }
          templates
        end

        # Return the metadata for this CacheCluster
        # @return [Hash]
        def notify
          {}
        end
        # Placeholder. This is a NOOP for CloudFormation, which doesn't build
        # resources directly.
        def self.find(*args)
          MU.log "find() not implemented for CloudFormation layer", MU::DEBUG
          nil
        end
        # Placeholder. This is a NOOP for CloudFormation, which doesn't build
        # resources directly.
        def self.cleanup(*args)
          MU.log "cleanup() not implemented for CloudFormation layer", MU::DEBUG
          nil
        end
        # Placeholder. This is a NOOP for CloudFormation, which doesn't build
        # resources directly.
        def self.genericMuDNSEntry(*args)
          MU.log "find() not implemented for CloudFormation layer", MU::DEBUG
          nil
        end

        # Cloud-specific configuration properties.
        # @param config [MU::Config]: The calling MU::Config object
        # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
        def self.schema(config)
          MU::Cloud.resourceClass("AWS", "DNSZone").schema(config)
        end

        # Cloud-specific pre-processing of {MU::Config::BasketofKittens::servers}, bare and unvalidated.
        # @param server [Hash]: The resource to process and validate
        # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
        # @return [Boolean]: True if validation succeeded, False otherwise
        def self.validateConfig(server, configurator)
          MU::Cloud.resourceClass("AWS", "DNSZone").validateConfig(server, configurator)
        end

        # Does this resource type exist as a global (cloud-wide) artifact, or
        # is it localized to a region/zone?
        # @return [Boolean]
        def self.isGlobal?
          MU::Cloud.resourceClass("AWS", "DNSZone").isGlobal?
        end

      end
    end
  end
end