Class: Jets::Cfn::Ship

Inherits:
Object
  • Object
show all
Includes:
AwsServices, Timing
Defined in:
lib/jets/cfn/ship.rb

Constant Summary

Constants included from Timing

Timing::RECORD_LOG_PATH

Instance Method Summary collapse

Methods included from AwsServices

#cfn, #lambda, #logs, #s3, #s3_resource, #sns, #sts

Methods included from AwsServices::StackStatus

#stack_exists?, #stack_in_progress?

Methods included from Timing

clear, #record_data, #record_log, report

Constructor Details

#initialize(options) ⇒ Ship

Returns a new instance of Ship.



6
7
8
9
10
# File 'lib/jets/cfn/ship.rb', line 6

def initialize(options)
  @options = options
  @parent_stack_name = Jets::Naming.parent_stack_name
  @template_path = Jets::Naming.parent_template_path
end

Instance Method Details

#capabilitiesObject



135
136
137
138
139
140
141
# File 'lib/jets/cfn/ship.rb', line 135

def capabilities
  ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"] # TODO: remove capabilities hardcode
  # return @options[:capabilities] if @options[:capabilities]
  # if @options[:iam]
  #   ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
  # end
end

#command_with_iam(capabilities) ⇒ Object



131
132
133
# File 'lib/jets/cfn/ship.rb', line 131

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

#create_stackObject



48
49
50
51
# File 'lib/jets/cfn/ship.rb', line 48

def create_stack
  # parent stack template is on filesystem and child stacks templates is on s3
  cfn.create_stack(stack_options)
end

#prewarmObject



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/jets/cfn/ship.rb', line 80

def prewarm
  if ENV['SKIP_PREWARMING']
    puts "Skipping prewarming" # useful for testing
    return
  end
  return unless @options[:stack_type] == :full # s3 bucket is available
  return unless Jets.config.prewarm.enable
  return if Jets::Commands::Build.poly_only?

  puts "Prewarming application."
  if Jets::PreheatJob::CONCURRENCY > 1
    Jets::PreheatJob.perform_now(:torch, {quiet: true})
  else
    Jets::PreheatJob.perform_now(:warm, {quiet: true})
  end
end

#prompt_for_iam(capabilities) ⇒ Object



123
124
125
126
127
128
129
# File 'lib/jets/cfn/ship.rb', line 123

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

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

#runObject



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
# File 'lib/jets/cfn/ship.rb', line 12

def run
  # s3 bucket is available only when stack_type is full
  upload_to_s3 if @options[:stack_type] == :full

  stack_in_progress?(@parent_stack_name)

  puts "Deploying CloudFormation stack with jets app!"
  begin
    save_stack
  rescue Aws::CloudFormation::Errors::InsufficientCapabilitiesException => e
    capabilities = e.message.match(/\[(.*)\]/)[1]
    confirm = prompt_for_iam(capabilities)
    if confirm =~ /^y/
      @options.merge!(capabilities: [capabilities])
      puts "Re-running: #{command_with_iam(capabilities).colorize(:green)}"
      retry
    else
      puts "Exited"
      exit 1
    end
  end

  wait_for_stack
  prewarm
  show_api_endpoint
end

#save_stackObject



40
41
42
43
44
45
46
# File 'lib/jets/cfn/ship.rb', line 40

def save_stack
  if stack_exists?(@parent_stack_name)
    update_stack
  else
    create_stack
  end
end

#show_api_endpointObject



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/jets/cfn/ship.rb', line 97

def show_api_endpoint
  return unless @options[:stack_type] == :full # s3 bucket is available
  return if Jets::Router.routes.empty?
  resp, status = stack_status
  return if status.include?("ROLLBACK")

  resp = cfn.describe_stack_resources(stack_name: @parent_stack_name)
  resources = resp.stack_resources
  api_gateway = resources.find { |resource| resource.logical_resource_id == "ApiGateway" }
  stack_id = api_gateway["physical_resource_id"]

  resp = cfn.describe_stacks(stack_name: stack_id)
  stack = resp["stacks"].first
  output = stack["outputs"].find { |o| o["output_key"] == "RestApiUrl" }
  endpoint = output["output_value"]
  puts "API Gateway Endpoint: #{endpoint}"
end

#stack_optionsObject

options common to both create_stack and update_stack



65
66
67
68
69
70
71
72
# File 'lib/jets/cfn/ship.rb', line 65

def stack_options
  {
    stack_name: @parent_stack_name,
    template_body: IO.read(@template_path),
    capabilities: capabilities, # ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
    # disable_rollback: !@options[:rollback],
  }
end

#stack_statusObject



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

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

#update_stackObject



54
55
56
57
58
59
60
61
# File 'lib/jets/cfn/ship.rb', line 54

def update_stack
  begin
    cfn.update_stack(stack_options)
  rescue Aws::CloudFormation::Errors::ValidationError => e
    puts "ERROR: #{e.message}".red
    true # error
  end
end

#upload_to_s3Object

Upload both code and child templates to s3



144
145
146
147
148
149
# File 'lib/jets/cfn/ship.rb', line 144

def upload_to_s3
  raise "Did not specify @options[:s3_bucket] #{@options[:s3_bucket].inspect}" unless @options[:s3_bucket]

  uploader = Upload.new(@options[:s3_bucket])
  uploader.upload
end

#wait_for_stackObject

check for /(_COMPLETE|_FAILED)$/ status



75
76
77
# File 'lib/jets/cfn/ship.rb', line 75

def wait_for_stack
  Jets::Cfn::Status.new(@options).wait
end