Class: Jets::Cfn::Deploy

Inherits:
Stack
  • Object
show all
Defined in:
lib/jets/cfn/deploy.rb

Instance Method Summary collapse

Methods inherited from Stack

#check_stack_exist!, #initialize

Methods included from Stack::Deployable

#check_deployable!

Methods included from Stack::Rollback

#continue_update_rollback!, #delete_rollback_complete!, #rollback_complete?, #update_rollback_failed?

Methods included from Util::Logging

#log

Methods included from AwsServices::AwsHelpers

#find_stack

Methods included from AwsServices

#apigateway, #aws_options, #cfn, #codebuild, #dynamodb, #lambda_client, #logs, #s3, #s3_resource, #sns, #sqs, #ssm, #sts, #wafv2

Methods included from AwsServices::StackStatus

#output_value, #stack_exists?

Methods included from AwsServices::GlobalMemoist

included, #reset_cache!

Constructor Details

This class inherits a constructor from Jets::Cfn::Stack

Instance Method Details

#capabilitiesObject



149
150
151
# File 'lib/jets/cfn/deploy.rb', line 149

def capabilities
  ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
end

#cfn_statusObject

note: required method: rollback.rb module uses



46
47
48
# File 'lib/jets/cfn/deploy.rb', line 46

def cfn_status
  Jets::Cfn::Status.new
end

#changed?Boolean

CloudFormation always performs an update there are nested templates, even when the parent template has not changed at all. Note: Jets ressurects the template body with a slight difference

!Ref S3Bucket vs {Ref: S3Bucket}

However, tested with the exact template body without this difference and cloudformation still performs an update. So we have to check if the template has changed ourselves. This is useful for the bootstrap genesis update.

Returns:

  • (Boolean)


59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/jets/cfn/deploy.rb', line 59

def changed?
  return true if @options[:force_changed]
  return true unless stack_exists?(stack_name)

  template_body = cfn.get_template(stack_name: stack_name).template_body
  existing = Yamler.load(template_body)
  fresh = Yamler.load(template.body)

  FileUtils.mkdir_p("#{Jets.build_root}/cfn/diff")
  IO.write("#{Jets.build_root}/cfn/diff/existing.yml", YAML.dump(existing))
  IO.write("#{Jets.build_root}/cfn/diff/fresh.yml", YAML.dump(fresh))

  existing != fresh
end

#command_with_iam(capabilities) ⇒ Object



145
146
147
# File 'lib/jets/cfn/deploy.rb', line 145

def command_with_iam(capabilities)
  "#{File.basename($0)} #{ARGV.join(" ")} --capabilities #{capabilities}"
end

#create_stackObject



97
98
99
100
# File 'lib/jets/cfn/deploy.rb', line 97

def create_stack
  # initial create stack template is on filesystem
  cfn.create_stack(stack_options)
end

#deploy_messageObject



75
76
77
78
79
80
81
82
83
# File 'lib/jets/cfn/deploy.rb', line 75

def deploy_message
  if @options[:bootstrap_message]
    log.info "#{@options[:bootstrap_message]}: #{stack_name}"
  elsif @options[:bootstrap]
    log.info "Syncing bootstrap: #{stack_name}"
  else
    log.info "Deploying app: #{stack_name}"
  end
end

#prompt_for_iam(capabilities) ⇒ Object



137
138
139
140
141
142
143
# File 'lib/jets/cfn/deploy.rb', line 137

def prompt_for_iam(capabilities)
  log.info "This stack will create IAM resources.  Please approve to run the command again with #{capabilities} capabilities."
  log.info "  #{command_with_iam(capabilities)}"

  log.info "Please confirm (y/n)"
  $stdin.gets # confirm
end

#set_resource_tagsObject



85
86
87
# File 'lib/jets/cfn/deploy.rb', line 85

def set_resource_tags
  @tags = Jets.bootstrap.config.cfn.resource_tags.map { |k, v| {key: k, value: v} }
end

#stack_nameObject



153
154
155
# File 'lib/jets/cfn/deploy.rb', line 153

def stack_name
  Jets::Names.parent_stack_name
end

#stack_optionsObject

options common to both create_stack and update_stack



116
117
118
119
120
121
122
# File 'lib/jets/cfn/deploy.rb', line 116

def stack_options
  {
    stack_name: stack_name,
    capabilities: capabilities,
    tags: @tags
  }.merge(template.template_option)
end

#stack_statusObject



131
132
133
134
135
# File 'lib/jets/cfn/deploy.rb', line 131

def stack_status
  resp = cfn.describe_stacks(stack_name: stack_name)
  status = resp.stacks[0].stack_status
  [resp, status]
end

#syncObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/jets/cfn/deploy.rb', line 3

def sync
  delete_rollback_complete!
  continue_update_rollback!
  check_deployable!

  log.debug "Parent template changed: #{changed?.inspect}"
  # return true when no changes so deploy will continue and start remote runner
  return true unless changed?

  # bootstrap can be "delete" or true
  # Set quiet before stack exists check
  quiet_sync = @options[:bootstrap] == true && stack_exists?(stack_name)
  deploy_message
  set_resource_tags
  begin
    sync_stack
  rescue Aws::CloudFormation::Errors::InsufficientCapabilitiesException => e
    capabilities = e.message.match(/\[(.*)\]/)[1]
    confirm = prompt_for_iam(capabilities)
    if /^y/.match?(confirm)
      @options.merge!(capabilities: [capabilities])
      log.info "Re-running: #{command_with_iam(capabilities).color(:green)}"
      retry
    else
      log.error "ERROR: Unable to deploy #{e.message}"
      exit 1
    end
  end

  # waits for /(_COMPLETE|_FAILED)$/ status to see if successfully deployed
  success = cfn_status.wait(quiet: quiet_sync)
  if success
    log.info "Bootstrap synced" if @options[:bootstrap] == true # dont use quiet_sync
  else
    if quiet_sync # show full cfn stack status if quiet_sync
      cfn_status.run
    end
    cfn_status.failure_message
  end
  success
end

#sync_stackObject



89
90
91
92
93
94
95
# File 'lib/jets/cfn/deploy.rb', line 89

def sync_stack
  if stack_exists?(stack_name)
    update_stack
  else
    create_stack
  end
end

#templateObject



124
125
126
# File 'lib/jets/cfn/deploy.rb', line 124

def template
  Template.new(@options)
end

#update_stackObject



102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/jets/cfn/deploy.rb', line 102

def update_stack
  cfn.update_stack(stack_options)
rescue Aws::CloudFormation::Errors::ValidationError => e
  if e.message.include?("No updates are to be performed")
    log.debug "DEBUG bootstrap sync update_stack #{e.message}" # ERROR: No updates are to be performed.
  else
    # Always show the some other error message
    log.info "ERROR bootstrap sync update_stack #{e.message}" # Some other error
    raise
  end
  true
end