class OpenStax::Aws::DeploymentBase

Constants

RESERVED_ENV_NAMES

Attributes

dry_run[R]
env_name[R]
name[R]
region[R]

Public Class Methods

inherited(child_class) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 208
def inherited(child_class)
  # Copy any tags defined in the parent to the child so that it can access them
  # while not using class variables that are shared across all classes in the
  # that inherit here
  child_class.instance_variable_set("@tags", tags.dup)
end
logger() click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 215
def logger
  OpenStax::Aws.configuration.logger
end
new(env_name: nil, region:, name:, dry_run: true) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 41
def initialize(env_name: nil, region:, name:, dry_run: true)
  if RESERVED_ENV_NAMES.include?(env_name)
    raise "#{env_name} is a reserved word and cannot be used as an environment name"
  end

  # Allow a blank env_name but normalize it to `nil`
  @env_name = env_name.blank? ? nil : env_name

  if @env_name && !@env_name.match(/^[a-zA-Z][a-zA-Z0-9-]*$/)
    raise "The environment name must consist only of letters, numbers, and hyphens, " \
          "and must start with a letter."
  end

  @region = region
  @name = name
  @dry_run = dry_run
end
sam_build_directory(*directory_parts) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 87
def sam_build_directory(*directory_parts)
  if method_defined?("sam_build_directory")
    raise "Can only set buisam_build_directoryld_directory once per class definition"
  end

  define_method "sam_build_directory" do
    File.expand_path(File.join(*directory_parts))
  end
end
secrets(id, &block) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 97
def secrets(id, &block)
  if id.blank?
    raise "The first argument to `secrets` must be a non-blank ID"
  end

  if !id.to_s.match(/^[a-zA-Z][a-zA-Z0-9_]*$/)
    raise "The first argument to `secrets` must consist only of letters, numbers, and underscores, " \
          "and must start with a letter."
  end

  if method_defined?("#{id}_secrets")
    raise "Can only define the `#{id}` secrets once per class definition"
  end

  if method_defined?("#{id}_stack")
    raise "Cannot define `#{id}` secrets because there is a stack with that ID"
  end

  define_method("#{id}_secrets") do |for_create_or_update: false|
    secrets_factory = SecretsFactory.new(
      region: region,
      namespace: [env_name, name],
      context: self,
      dry_run: dry_run,
      for_create_or_update: for_create_or_update,
      shared_substitutions_block: @shared_secrets_substitutions_block
    )

    secrets_factory.namespace(id)
    secrets_factory.instance_exec({}, &block)
    secrets_factory.instance
  end
end
secrets_substitutions(&block) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 192
def secrets_substitutions(&block)
  define_method("shared_secrets_substitutions_block") do
    block
  end
end
stack(id, &block) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 135
def stack(id, &block)
  if id.blank?
    raise "The first argument to `stack` must be a non-blank ID"
  end

  if !id.to_s.match(/^[a-zA-Z][a-zA-Z0-9_]*$/)
    raise "The first argument to `stack` must consist only of letters, numbers, and underscores, " \
          "and must start with a letter."
  end

  if method_defined?("#{id}_secrets")
    raise "Cannot define `#{id}` stack because there are secrets with that ID"
  end

  stack_ids.push(id)

  define_method("#{id}_stack") do
    instance_variable_get("@#{id}_stack") || begin
      stack_factory = StackFactory.new(id: id, deployment: self)
      stack_factory.instance_eval(&block) if block_given?

      # Fill in missing attributes using deployment variables and conventions

      if stack_factory.name.blank?
        stack_factory.name([env_name,name,id].compact.join("-").gsub("_","-"))
      end

      if stack_factory.region.blank?
        stack_factory.region(region)
      end

      if stack_factory.dry_run.nil?
        stack_factory.dry_run(dry_run)
      end

      if stack_factory.enable_termination_protection.nil?
        stack_factory.enable_termination_protection(is_production?)
      end

      if stack_factory.absolute_template_path.blank?
        stack_factory.autoset_absolute_template_path(respond_to?(:template_directory) ? template_directory : "")
      end

      # Populate parameter defaults that match convention names

      if OpenStax::Aws.configuration.infer_parameter_defaults
        defaults = parameter_defaults_from_template(stack_factory.absolute_template_path)
        defaults.each{|key,value| stack_factory.parameter_defaults[key] ||= value}
      end

      stack_factory.build.tap do |stack|
        instance_variable_set("@#{id}_stack", stack)
      end
    end
  end
end
stack_ids() click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 131
def stack_ids
  @stack_ids ||= []
end
tag(key, value=nil, &block) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 198
def tag(key, value=nil, &block)
  raise 'The first argument to `tag` must not be blank' if key.blank?
  raise '`tag` must be given a value or a block' if value.nil? && !block_given?
  tags[key] = block_given? ? block : value
end
tags() click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 204
def tags
  @tags ||= HashWithIndifferentAccess.new
end
template_directory(*directory_parts) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 77
def template_directory(*directory_parts)
  if method_defined?("template_directory")
    raise "Can only set template_directory once per class definition"
  end

  define_method "template_directory" do
    File.join(*directory_parts)
  end
end

Public Instance Methods

built_in_parameter_default(parameter_name) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 250
def built_in_parameter_default(parameter_name)
  case parameter_name
  when "EnvName"
    env_name
  when /(.+)StackName$/
    send("#{$1}Stack".underscore).name rescue nil
  end
end
deployed_parameters() click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 244
def deployed_parameters
  stacks.each_with_object({}) do |stack, hash|
    hash[stack.name] = stack.deployed_parameters
  end
end
env_name!() click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 64
def env_name!
  raise "`env_name` is blank" if env_name.blank?
  env_name
end
name!() click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 59
def name!
  raise "`name` is blank" if name.blank?
  name
end
parameter_defaults_from_template(template_or_absolute_template_path) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 220
def parameter_defaults_from_template(template_or_absolute_template_path)
  template = template_or_absolute_template_path.is_a?(String) ?
               Template.from_absolute_file_path(template_or_absolute_template_path) :
               template_or_absolute_template_path

  template.parameter_names.each_with_object({}) do |parameter_name, defaults|
    value = parameter_default(parameter_name) ||
            built_in_parameter_default(parameter_name)

    if !value.nil?
      defaults[parameter_name.underscore.to_sym] = value
    end
  end
end
shared_secrets_substitutions_block() click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 259
def shared_secrets_substitutions_block
  nil # can be overridden by the DSL
end
stacks() click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 235
def stacks
  self.class.stack_ids.map{|id| self.send("#{id}_stack")}
end
status(reload: false) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 239
def status(reload: false)
  @status = nil if reload
  @status ||= Status.new(self)
end
tags() click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 69
def tags
  self.class.tags.each_with_object(HashWithIndifferentAccess.new) do |(key, value), hsh|
    hsh[key] = value.is_a?(Proc) ? instance_eval(&value) : value
  end
end

Protected Instance Methods

auto_scaling_client() click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 292
def auto_scaling_client
  @auto_scaling_client ||= ::Aws::AutoScaling::Client.new(region: region)
end
auto_scaling_group(name:) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 300
def auto_scaling_group(name:)
  ::Aws::AutoScaling::AutoScalingGroup.new(name: name, client: auto_scaling_client)
end
client() click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 288
def client
  @client ||= ::Aws::CloudFormation::Client.new(region: region)
end
cloudfront_client() click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 304
def cloudfront_client
  @cloudfront_client ||= ::Aws::CloudFront::Client.new(region: region)
end
configuration() click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 284
def configuration
  OpenStax::Aws.configuration
end
get_image_tag(image_id:, key:) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 348
def get_image_tag(image_id:, key:)
  tag = image(image_id).tags.find{|tag| tag.key == key}
  raise "No tag with key #{key} on AMI #{image_id}" if tag.nil?
  tag.value
end
image(image_id) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 354
def image(image_id)
  Aws::EC2::Image.new(image_id, region: region)
end
image_sha(image_id) click to toggle source

Returns the SHA on an AMI

# File lib/openstax/aws/deployment_base.rb, line 359
def image_sha(image_id)
  get_image_tag(image_id: image_id, key: "sha")
end
is_production?() click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 269
def is_production?
  env_name == OpenStax::Aws.configuration.production_env_name
end
parameter_default(parameter_name) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 265
def parameter_default(parameter_name)
  nil
end
resource_tag_value(resource:, key:) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 340
def resource_tag_value(resource:, key:)
  begin
    resource.tag(key).value
  rescue NoMethodError => ee
    nil
  end
end
s3_client() click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 308
def s3_client
  @s3_client ||= Aws::S3::Client.new(region: region)
end
s3_key_exists?(bucket:, key:) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 312
def s3_key_exists?(bucket:, key:)
  begin
    s3_client.get_object(bucket: bucket, key: key)
    true
  rescue Aws::S3::Errors::NoSuchKey
    false
  end
end
secrets_namespace(id: 'default') click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 365
def secrets_namespace(id: 'default')
  raise "Override this method in your deployment class and provide a namespace " \
        "for secrets data in the AWS Parameter Store.  The key there will be this namespace " \
        "prefixed by the environment name and suffixed with the secret name."
end
set_desired_capacity(asg_name:, desired_capacity:) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 296
def set_desired_capacity(asg_name:, desired_capacity:)
  auto_scaling_client.set_desired_capacity(auto_scaling_group_name: asg_name, desired_capacity: desired_capacity)
end
subdomain_with_trailing_dot(site_name:) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 273
def subdomain_with_trailing_dot(site_name:)
  parts = []
  parts.push(site_name) if !site_name.blank?
  parts.push(env_name!) unless is_production?

  subdomain = parts.join("-")
  subdomain.blank? ? "" : subdomain + "."
end
wait_for_tag_change(resource:, key:, polling_seconds: 20, timeout_seconds: nil) click to toggle source
# File lib/openstax/aws/deployment_base.rb, line 321
def wait_for_tag_change(resource:, key:, polling_seconds: 20, timeout_seconds: nil)
  keep_polling = true
  started_at = Time.now
  original_value = resource_tag_value(resource: resource, key: key)

  wait_message = OpenStax::Aws::WaitMessage.new(
    message: "Waiting for the #{key} tag on #{resource.name} to change from #{original_value}"
  )

  while keep_polling
    wait_message.say_it

    sleep(polling_seconds)

    keep_polling = false if resource_tag_value(resource: resource, key: key) != original_value
    keep_polling = false if !timeout_seconds.nil? && Time.now - timeout_seconds > started_at
  end
end