class MU::Cloud::AWS::CDN
A scheduled task facility as configured in {MU::Config::BasketofKittens::cdns}
Public Class Methods
Remove all cdns associated with the currently loaded deployment. @param noop [Boolean]: If true, will only print what would be done @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server @return [void]
# File modules/mu/providers/aws/cdn.rb, line 145 def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, credentials: nil, flags: {}) resp = MU::Cloud::AWS.cloudfront(credentials: credentials).list_distributions if resp and resp.distribution_list and resp.distribution_list.items delete_threads = [] ids = Hash[resp.distribution_list.items.map { |distro| [distro.arn, distro] }] ids.each_key { |arn| tags = MU::Cloud::AWS.cloudfront(credentials: credentials).list_tags_for_resource(resource: arn).tags.items found_muid = found_master = false name = nil tags.each { |tag| name = tag.value if tag.key == "Name" found_muid = true if tag.key == "MU-ID" && tag.value == deploy_id found_master = true if tag.key == "MU-MASTER-IP" && tag.value == MU.mu_public_ip } if found_muid and (ignoremaster or found_master) delete_threads << Thread.new(arn, name) { |my_arn, my_name| current = MU::Cloud::AWS.cloudfront(credentials: credentials).get_distribution_config(id: ids[my_arn].id) etag = current.etag if !noop if current.distribution_config.enabled newcfg = MU.structToHash(current.distribution_config) newcfg[:enabled] = false MU.log "Disabling CloudFront distribution #{my_name ? my_name : ids[my_arn].id})", MU::NOTICE updated = MU::Cloud::AWS.cloudfront(credentials: credentials).update_distribution(id: ids[my_arn].id, distribution_config: newcfg, if_match: etag) etag = updated.etag end end MU.log "Deleting CloudFront distribution #{my_name ? my_name : ids[my_arn].id})" if !noop ready?(ids[my_arn].id, credentials: credentials) MU::Cloud::AWS.cloudfront(credentials: credentials).delete_distribution(id: ids[my_arn].id, if_match: etag) end } end } delete_threads.each { |t| t.join } end resp = MU::Cloud::AWS.cloudfront(credentials: credentials).list_cloud_front_origin_access_identities if resp and resp.cloud_front_origin_access_identity_list and resp.cloud_front_origin_access_identity_list.items.each and deploy_id =~ /-\d{10}-[A-Z]{2}/ resp.cloud_front_origin_access_identity_list.items.each { |ident| if ident.comment =~ /^#{Regexp.quote(deploy_id)}-/ MU.log "Deleting CloudFront origin access identity #{ident.id} (#{ident.comment})" if !noop getresp = MU::Cloud::AWS.cloudfront(credentials: credentials).get_cloud_front_origin_access_identity(id: ident.id) begin MU::Cloud::AWS.cloudfront(credentials: credentials).delete_cloud_front_origin_access_identity(id: ident.id, if_match: getresp.etag) rescue ::Aws::CloudFront::Errors::CloudFrontOriginAccessIdentityInUse => e MU.log "Got #{e.message} deleting #{ident.id}; it likely belongs to a distribution we can't to delete", MU::WARN, details: ident end end end } end end
Locate an existing event. @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching CloudWatch Event
# File modules/mu/providers/aws/cdn.rb, line 213 def self.find(**args) found = {} MU::Cloud::AWS.cloudfront(credentials: args[:credentials]).list_distributions.distribution_list.items.each { |d| next if args[:cloud_id] and ![d.id, d.arn].include?(args[:cloud_id]) found[d.id] = d } found end
Does this resource type exist as a global (cloud-wide) artifact, or is it localized to a region/zone? @return [Boolean]
# File modules/mu/providers/aws/cdn.rb, line 131 def self.isGlobal? true end
Initialize this cloud resource object. Calling super
will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like +@vpc+, for us. @param args [Hash]: Hash
of named arguments passed via Ruby's double-splat
# File modules/mu/providers/aws/cdn.rb, line 23 def initialize(**args) super @mu_name ||= @deploy.getResourceName(@config["name"]) end
Denote whether this resource implementation is experiment, ready for testing, or ready for production use.
# File modules/mu/providers/aws/cdn.rb, line 137 def self.quality MU::Cloud::ALPHA end
Wait until a distribution is ready (status is Deployed
) @param id [String] @param credentials [String]
# File modules/mu/providers/aws/cdn.rb, line 121 def self.ready?(id, credentials: nil) desc = nil MU.retrier([], loop_if: Proc.new { !desc or desc.status != "Deployed" }, wait: 30, max:60) { desc = MU::Cloud::AWS.cloudfront(credentials: credentials).get_distribution(id: id).distribution } 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
# File modules/mu/providers/aws/cdn.rb, line 340 def self.schema(_config) toplevel_required = [] schema = { "disabled" => { "type" => "boolean", "description" => "Flag this CloudFront distribution as disabled", "default" => false }, "certificate" => MU::Config::Ref.schema(type: "certificate", desc: "Required if any domains have been specified with +aliases+; parser will attempt to autodetect a valid ACM or IAM certificate if not specified.", omit_fields: ["cloud", "tag", "deploy_id"]), "behaviors" => { "items" => { "properties" => { "min_ttl" => { "type" => "integer", "description" => "The minimum amount of time that you want objects to stay in CloudFront caches before CloudFront forwards another request to your origin to determine whether the object has been updated.", "default" => 0 }, "default_ttl" => { "type" => "integer", "description" => "The default amount of time that you want objects to stay in CloudFront caches before CloudFront forwards another request to your origin to determine whether the object has been updated.", "default" => 86400 }, "max_ttl" => { "type" => "integer", "description" => "The maximum amount of time that you want objects to stay in CloudFront caches before CloudFront forwards another request to your origin to determine whether the object has been updated.", "default" => 31536000 }, "protocol_policy" => { "type" => "string", "enum" => %w{allow-all https-only redirect-to-https}, "default" => "redirect-to-https" }, "functions" => { "type" => "array", "items" => MU::Config::Ref.schema(type: "functions", desc: "Add a Lambda function which can be invoked on requests or responses through this distribution.") }, "forwarded_values" => { "type" => "object", "description" => "HTTP request artifacts to include in requests passed to our back-end +origin+", "default" => { "query_string" => false }, "properties" => { "query_string" => { "type" => "boolean", "description" => "Indicates whether you want CloudFront to forward query strings to the origin that is associated with this cache behavior and cache based on the query string parameters.", "default" => false }, "cookies" => { "type" => "object", "description" => "A complex type that specifies whether you want CloudFront to forward cookies to the origin and, if so, which ones.", "default" => { "forward" => "none" }, "properties" => { "forward" => { "type" => "string", "description" => "Specifies which cookies to forward to the origin for this cache behavior: all, none, or the list of cookies specified in +whitelisted_names+", "enum" => %w{none whitelist all} }, "whitelisted_names" => { "type" => "array", "items" => { "description" => "Required if you specify whitelist for the value of +forward+", "type" => "string" } }, } }, "headers" => { "type" => "array", "items" => { "description" => "Specifies the headers, if any, that you want CloudFront to forward to the origin for this cache behavior (whitelisted headers).", "type" => "string" } }, "query_string_cache_keys" => { "type" => "array", "items" => { "description" => "Indicates whether you want CloudFront to forward query strings to the origin that is associated with this cache behavior and cache based on the query string parameters", "type" => "string" } } } } } } }, "origins" => { "items" => { "properties" => { "bucket" => MU::Config::Ref.schema(type: "buckets", desc: "Reference an S3 bucket for use as an origin"), "endpoint" => MU::Config::Ref.schema(type: "endpoints", desc: "Reference an API Gateway for use as an origin"), "loadbalancer" => MU::Config::Ref.schema(type: "loadbalancers", desc: "Reference a Load Balancer for use as an origin"), "connection_attempts" => { "type" => "integer", "default" => 3 }, "connection_timeout" => { "type" => "integer", "default" => 10 }, "protocol_policy" => { "type" => "string", "enum" => %w{http-only https-only match-viewer}, "default" => "match-viewer" }, "ssl_protocols" => { "type" => "array", "default" => ["TLSv1.2"], "items" => { "type" => "string", "enum" => %w{SSLv3 TLSv1 TLSv1.1 TLSv1.2}, } }, "http_port" => { "type" => "integer", "default" => 80 }, "https_port" => { "type" => "integer", "default" => 443 }, "custom_headers" => { "type" => "array", "items" => { "description" => "A list of HTTP header names and values that CloudFront adds to requests it sends to the origin.", "type" => "object", "required" => ["key", "value"], "properties" => { "key" => { "type" => "string" }, "value" => { "type" => "string" }, } } } } } } } schema["behaviors"]["items"]["properties"]["functions"]["items"]["include_body"] = { "type" => "boolean", "default" => false } schema["behaviors"]["items"]["properties"]["functions"]["items"]["event_type"] = { "type" => "string", "enum" => %w{viewer-request viewer-response origin-request origin-response}, "default" => "viewer-request" } [toplevel_required, schema] end
Cloud-specific pre-processing of {MU::Config::BasketofKittens::cdns}, bare and unvalidated. @param cdn [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
# File modules/mu/providers/aws/cdn.rb, line 502 def self.validateConfig(cdn, configurator) ok = true cdn['origins'].each { |o| count = 0 ['bucket', 'endpoint', 'loadbalancer'].each { |sib_type| if o[sib_type] if count > 0 ok = false MU.log "Origin in CloudFront distro #{cdn['name']} may specify at most one of bucket, endpoint, or loadbalancer.", MU::ERR end target_ref = MU::Config::Ref.get(o[sib_type]) if target_ref.name MU::Config.addDependency(cdn, target_ref.name, sib_type, their_phase: "groom") end count += 1 end } } cert_domains = nil if cdn['certificate'] cert_arn, cert_domains = MU::Cloud::AWS.resolveSSLCertificate(cdn['certificate'], region: cdn['region'], credentials: cdn['credentials']) if !cert_arn MU.log "Failed to find an ACM or IAM certificate specified in CloudFront distribution #{cdn['name']}", MU::ERR, details: cdn['certificate'].to_h ok = false end end if cdn['aliases'] cdn['aliases'].each { |a| if !cdn['certificate'] foundcert, cert_domains = MU::Cloud::AWS.findSSLCertificate(name: a, region: cdn['region'], credentials: cdn['credentials'], raise_on_missing: false) if !foundcert foundcert, cert_domains = MU::Cloud::AWS.findSSLCertificate(name: a.sub(/^[^\.]+\./, '*.'), region: cdn['region'], credentials: cdn['credentials'], raise_on_missing: false) end if !foundcert MU.log "Failed to find an ACM or IAM certificate matching #{a} for CloudFront distribution #{cdn['name']}", MU::ERR ok = false else cdn['certificate'] = { "id" => foundcert, "credentials" => cdn['credentials'] } MU.log "Auto-detected SSL certificate for CloudFront distribution #{cdn['name']} alias #{a}", MU::NOTICE, details: cdn['certificate']['id'] end else if !MU::Cloud::AWS.nameMatchesCertificate(a, cdn['certificate']['id']) MU.log "Alias #{a} in CloudFront distro #{cdn['name']} does not appear to fit any domains on our SSL certificate", MU::ERR, details: cert_domains ok = false end end } end if cdn['dns_records'] and cdn['certificate'] cdn['dns_records'].each { |rec| next if !rec['name'] dnsname = MU::Cloud.resourceClass("AWS", "DNSZone").recordToName(rec) if MU::Cloud::AWS.nameMatchesCertificate(dnsname, cdn['certificate']['id']) cdn['aliases'] ||= [] cdn['aliases'] << dnsname if !cdn['aliases'].include?(dnsname) end } end path_patterns = {} cdn['behaviors'].each { |b| b['path_pattern'] ||= "*" path_patterns[b['path_pattern']] ||= 0 path_patterns[b['path_pattern']] += 1 } path_patterns.each_pair { |pattern, origins| if origins > 1 MU.log "CDN #{cdn['name']} has #{origins.to_s} uses of path_pattern '#{pattern}' in its behavior list (must be unique)", MU::ERR, details: cdn['behaviors'] ok = false end } ok end
Public Instance Methods
Canonical Amazon Resource Number for this resource @return [String]
# File modules/mu/providers/aws/cdn.rb, line 103 def arn cloud_desc ? cloud_desc.arn : nil end
Called automatically by {MU::Deploy#createResources}
# File modules/mu/providers/aws/cdn.rb, line 29 def create resp = MU::Cloud::AWS.cloudfront(credentials: @credentials).create_cloud_front_origin_access_identity( cloud_front_origin_access_identity_config: { caller_reference: @mu_name, comment: @mu_name } ) @origin_access_identity = "origin-access-identity/cloudfront/"+resp.cloud_front_origin_access_identity.id params = get_properties begin MU.log "Creating CloudFront distribution #{@mu_name}", details: params MU.retrier([Aws::CloudFront::Errors::InvalidOrigin], wait: 10, max: 6) { resp = MU::Cloud::AWS.cloudfront(credentials: @credentials).create_distribution_with_tags( distribution_config_with_tags: { distribution_config: params, tags: { items: @tags.each_key.map { |k| { :key => k, :value => @tags[k] } } } } ) @cloud_id = resp.distribution.id } ready? rescue ::Aws::CloudFront::Errors::InvalidViewerCertificate => e cert_arn, cert_domains = MU::Cloud::AWS.findSSLCertificate( name: @config['certificate']["name"], id: @config['certificate']["id"], region: @config['certificate']['region'], credentials: @config['certificate']['credentials'] ) raise MuError.new e.message, details: { "aliases" => @config['aliases'], "certificate domains" => cert_domains } rescue ::Aws::CloudFront::Errors::InvalidOrigin => e raise MuError.new e.message, details: params[:origins] rescue ::Aws::CloudFront::Errors::InvalidArgument => e raise MuError.new e.message, details: params end end
Called automatically by {MU::Deploy#createResources}
# File modules/mu/providers/aws/cdn.rb, line 71 def groom params = get_properties if !@config['dns_records'].nil? if !MU::Cloud::AWS.isGovCloud? MU::Cloud.resourceClass("AWS", "DNSZone").createRecordsFromConfig(@config['dns_records'], target: cloud_desc.domain_name) end end MU.log "CloudFront Distribution #{@config['name']} at #{cloud_desc.domain_name}", MU::SUMMARY if @config['aliases'] @config['aliases'].each { |a| MU.log "Alias for CloudFront Distribution #{@config['name']}: #{a}", MU::SUMMARY } end # Make sure we show up in the bucket policy of our target bucket, # if it's a sibling in this deploy cloud_desc(use_cache: false).origins.items.each { |o| if o.s3_origin_config id = o.s3_origin_config.origin_access_identity.sub(/^origin-access-identity\/cloudfront\//, '') bucketref = get_bucketref_from_domain(o.domain_name) next if !bucketref or !bucketref.kitten resp = MU::Cloud::AWS.cloudfront(credentials: @credentials).get_cloud_front_origin_access_identity(id: id) # bucketref.kitten.allowPrincipal("arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity "+id, doc_id: "PolicyForCloudFrontPrivateContent", permissions: ["GetObject"]) bucketref.kitten.allowPrincipal(resp.cloud_front_origin_access_identity.s3_canonical_user_id, doc_id: "PolicyForCloudFrontPrivateContent", permissions: ["GetObject"], name: @mu_name) end } end
Return the metadata for this cdn @return [Hash]
# File modules/mu/providers/aws/cdn.rb, line 109 def notify MU.structToHash(cloud_desc, stringify_keys: true) end
Wait until the distribution is ready (status is Deployed
)
# File modules/mu/providers/aws/cdn.rb, line 114 def ready? self.class.ready?(@cloud_id, credentials: @credentials) end
Reverse-map our cloud description into a runnable config hash. We assume that any values we have in +@config+ are placeholders, and calculate our own accordingly based on what's live in the cloud.
# File modules/mu/providers/aws/cdn.rb, line 227 def toKitten(**_args) bok = { "cloud" => "AWS", "credentials" => @credentials, "cloud_id" => @cloud_id } if !cloud_desc MU.log "toKitten failed to load a cloud_desc from #{@cloud_id}", MU::ERR, details: @config return nil end resp = MU::Cloud::AWS.cloudfront(credentials: @credentials).list_tags_for_resource(resource: arn) if resp and resp.tags and resp.tags.items tags = MU.structToHash(resp.tags.items, stringify_keys: true) bok['name'] = MU::Adoption.tagsToName(tags) bok['tags'] = tags if !tags.empty? end if !bok['name'] bok['name'] = if cloud_desc.domain_name !~ /\.cloudfront\.net$/ cloud_desc.domain_name.sub(/\..*/, '') elsif cloud_desc.aliases and !cloud_desc.aliases.items.empty? cloud_desc.aliases.items.first.sub(/\..*/, '') # XXX maybe try to guess from the name of an origin resource? else @cloud_id end end cloud_desc.origins.items.each { |o| bok['origins'] ||= [] origin = { "path" => o.origin_path, "name" => o.id } if o.s3_origin_config origin["bucket"] = get_bucketref_from_domain(o.domain_name) end origin["domain_name"] = o.domain_name if !origin["bucket"] if o.custom_origin_config origin["http_port"] = o.custom_origin_config.http_port origin["https_port"] = o.custom_origin_config.https_port origin["protocol_policy"] = o.custom_origin_config.origin_protocol_policy origin["ssl_protocols"] = o.custom_origin_config.origin_ssl_protocols.items end if o.custom_headers and !o.custom_headers.empty? end bok['origins'] << origin } if cloud_desc.aliases and cloud_desc.aliases.items and !cloud_desc.aliases.items.empty? bok['aliases'] = cloud_desc.aliases.items end bok['disabled'] = true if !cloud_desc.enabled bok['behaviors'] = [] add_behavior = Proc.new { |b, default| behavior = {} behavior["origin"] = b.target_origin_id behavior["path_pattern"] = b.path_pattern if b.respond_to?(:path_pattern) behavior["protocol_policy"] = b.viewer_protocol_policy if b.lambda_function_associations and !b.lambda_function_associations.items.empty? b.lambda_function_associations.items.each { |f| behavior['functions'] ||= [] f.lambda_function_arn.match(/^arn:.*?:lambda:([^:]+?):(\d*):function:([^:]+)/) region = Regexp.last_match[1] acct = Regexp.last_match[2] id = Regexp.last_match[3] behavior['functions'] << MU::Config::Ref.get( id: id, region: region, type: "functions", event_type: f.event_type, include_body: f.include_body, cloud: "AWS", credentials: @credentials, habitat: MU::Config::Ref.get( id: acct, cloud: "AWS", credentials: @credentials ) ) } [:min_ttl, :default_ttl, :max_ttl].each { |ttl| behavior[ttl.to_s] = b.send(ttl) } end bok['behaviors'] << behavior } add_behavior.call(cloud_desc.default_cache_behavior, true) if cloud_desc.cache_behaviors and !cloud_desc.cache_behaviors.items.empty? cloud_desc.cache_behaviors.items.each { |b| add_behavior.call(b, false) } end bok end
Private Instance Methods
# File modules/mu/providers/aws/cdn.rb, line 762 def get_bucketref_from_domain(domain_name) buckets = MU::Cloud.resourceClass("AWS", "Bucket").find(credentials: @credentials, allregions: true, cloud_id: domain_name.sub(/\..*/, '')) if buckets and buckets.size == 1 return MU::Config::Ref.get( id: buckets.keys.first, type: "buckets", region: buckets.values.first["region"], credentials: @credentials, cloud: "AWS" ) else MU.log "Failed to locate or isolate a bucket object from #{domain_name}", MU::WARN, details: buckets.keys end nil end
# File modules/mu/providers/aws/cdn.rb, line 587 def get_properties params = { default_root_object: @config['default_object'], caller_reference: @mu_name, # eh, probably should be random origins: { quantity: @config['origins'].size, items: [] }, comment: @deploy.deploy_id, enabled: !(@config['disabled']) } if @config['certificate'] params[:viewer_certificate] = { ssl_support_method: "sni-only" } if @config['certificate']['id'] =~ /^arn:aws(?:-us-gov)?:iam/ params[:viewer_certificate][:iam_certificate_id] = @config['certificate']['id'] params[:viewer_certificate][:certificate_source] = "iam" elsif @config['certificate']['id'] =~ /^arn:aws(?:-us-gov)?:acm/ params[:viewer_certificate][:acm_certificate_arn] = @config['certificate']['id'] params[:viewer_certificate][:certificate_source] = "acm" end end @config['origins'].each { |o| origin = { id: o['name'], } sib_obj = nil ['bucket', 'endpoint', 'loadbalancer'].each { |sib_type| if o[sib_type] sib_obj = MU::Config::Ref.get(o[sib_type]).kitten(@deploy, cloud: "AWS") if !sib_obj raise MuError.new "Failed to resolve #{sib_type} referenced in CloudFront distribution #{@config['name']}", details: o[sib_type].to_h end break end } if o['bucket'] origin[:domain_name] = sib_obj.cloud_desc["name"]+".s3.amazonaws.com" origin[:origin_path] = o['path'] if o['path'] origin[:s3_origin_config] = { origin_access_identity: @origin_access_identity } elsif o['endpoint'] origin[:domain_name] = sib_obj.cloud_id+".execute-api."+sib_obj.config['region']+".amazonaws.com" origin[:custom_origin_config] = { origin_protocol_policy: "https-only" } if sib_obj.config['deploy_to'] origin[:origin_path] ||= "/"+sib_obj.config['deploy_to'] end elsif o['loadbalancer'] origin[:domain_name] = sib_obj.cloud_desc.dns_name origin[:origin_path] = o['path'] if o['path'] else # XXX make sure parser guarantees these are present origin[:domain_name] = o['domain_name'] origin[:origin_path] = o['path'] end if o['custom_headers'] origin[:custom_headers] = { quantity: o['custom_headers'].size, items: o['custom_headers'].map { |h| { header_name: h['key'], header_value: h['value'] } } } end [:connection_attempts, :connection_timeout].each { |field| origin[field] ||= o[field.to_s] } if !origin[:s3_origin_config] maplet = { 'protocol_policy' => :origin_protocol_policy, 'ssl_protocols' => :origin_ssl_protocols, 'http_port' => :http_port, 'https_port' => :https_port } maplet.each_pair { |field, paramfield| next if !o[field] origin[:custom_origin_config] ||= {} origin[:custom_origin_config][paramfield] ||= if o[field.to_s].is_a?(Array) { quantity: o[field].size, items: o[field] } else o[field] end } end params[:origins][:items] << origin } # if we have any placeholder DNS records that are intended to be # filled out with our runtime @mu_name, do so, and add an alias if # applicable if @config['dns_records'] @config['dns_records'].each { |rec| if !rec['name'] rec['name'] = @mu_name.downcase dnsname = MU::Cloud.resourceClass("AWS", "DNSZone").recordToName(rec) if @config['certificate'] and MU::Cloud::AWS.nameMatchesCertificate(dnsname, @config['certificate']['id']) @config['aliases'] ||= [] @config['aliases'] << dnsname if !@config['aliases'].include?(dnsname) end end } end if @config['aliases'] params[:aliases] = { items: @config['aliases'], quantity: @config['aliases'].size } end # XXX config parser should guarantee a default behavior @config['behaviors'].each { |b| b['origin'] ||= @config['origins'].first['name'] behavior = { target_origin_id: b['origin'], viewer_protocol_policy: b['protocol_policy'], min_ttl: b['min_ttl'], max_ttl: b['max_ttl'], default_ttl: b['default_ttl'], } behavior[:trusted_signers] = { enabled: false, quantity: 0, # items: [] } behavior[:forwarded_values] = { query_string: b['forwarded_values']['query_string'], cookies: { forward: b['forwarded_values']['cookies']['forward'] } } if b['forwarded_values']['cookies']['whitelisted_names'] behavior[:forwarded_values][:cookies][:whitelisted_names] = { quantity: b['forwarded_values']['cookies']['whitelisted_names'].size, items: b['forwarded_values']['cookies']['whitelisted_names'] } end ['headers', 'query_string_cache_keys'].each { |field| if b['forwarded_values'][field] behavior[:forwarded_values][field.to_sym] = { quantity: b['forwarded_values'][field].size, items: b['forwarded_values'][field] } end } if @config['behaviors'].size == 1 or b['path_pattern'] == "*" params[:default_cache_behavior] = behavior else behavior[:path_pattern] = b['path_pattern'] params[:cache_behaviors] ||= { quantity: (@config['behaviors'].size-1), items: [] } params[:cache_behaviors][:items] << behavior end } params end