Class: Jackal::Stacks::Builder

Inherits:
Callback
  • Object
show all
Includes:
StackCommon
Defined in:
lib/jackal-stacks/builder.rb

Overview

Stack builder

Instance Method Summary collapse

Methods included from StackCommon

#api_config, #determine_namespace, #stack_name, #stacks_api

Instance Method Details

#allowed?(payload) ⇒ TrueClass, FalseClass

Check if this payload is allowed to be processed based on defined restrictions within the configuration

Parameters:

  • payload (Smash)

Returns:

  • (TrueClass, FalseClass)


177
178
179
# File 'lib/jackal-stacks/builder.rb', line 177

def allowed?(payload)
  !!determine_namespace(payload)
end

#build_stack_args(payload, directory) ⇒ Smash

Build configuration arguments for Sfn::Command execution

Parameters:

  • payload (Smash)
  • directory (String)

    directory to unpacked asset

Returns:

  • (Smash)

    stack command options hash



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/jackal-stacks/builder.rb', line 74

def build_stack_args(payload, directory)
  Smash.new(
    :base_directory => File.join(directory, 'cloudformation'),
    :parameters => load_stack_parameters(payload, directory),
    :ui => Bogo::Ui.new(
      :app_name => 'JackalStacks',
      :defaults => true,
      :output_to => StringIO.new('')
    ),
    :interactive_parameters => false,
    :nesting_bucket => config.get(:orchestration, :bucket_name),
    :apply_nesting => true,
    :processing => true,
    :options => {
      :disable_rollback => true,
      :capabilities => ['CAPABILITY_IAM']
    },
    :credentials => config.get(:orchestration, :api, :credentials),
    :file => payload.fetch(:data, :stacks, :template, config.get(:default_template_path)),
    :file_path_prompt => false,
    :poll => false
  )
end

#execute(message) ⇒ Object

Build or update stacks

Parameters:

  • message (Carnivore::Message)


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/jackal-stacks/builder.rb', line 36

def execute(message)
  failure_wrap(message) do |payload|
    directory = asset_store.unpack(asset_store.get(payload.get(:data, :stacks, :asset)), workspace(payload))
    begin
      unless(payload.get(:data, :stacks, :template))
        payload.set(:data, :stacks, :template, 'infrastructure')
      end
      unless(payload.get(:data, :stacks, :name))
        payload.set(:data, :stacks, :name, stack_name(payload))
      end
      store_stable_asset(payload, directory)
      begin
        stack = stacks_api.stacks.get(payload.get(:data, :stacks, :name))
      rescue
        stack = nil
      end
      if(stack)
        info "Stack currently exists. Applying update [#{stack}]"
        run_stack(payload, directory, :update)
        payload.set(:data, :stacks, :updated, true)
      else
        info "Stack does not currently exist. Building new stack [#{payload.get(:data, :stacks, :name)}]"
        init_provider(payload)
        run_stack(payload, directory, :create)
        payload.set(:data, :stacks, :created, true)
      end
    ensure
      FileUtils.rm_rf(directory)
    end
    job_completed(:stacks, payload, message)
  end
end

#init_provider(payload) ⇒ Object

Note:

this currently init’s chef related items

Initialize provider if instructed via config

Parameters:

  • payload (Smash)


185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/jackal-stacks/builder.rb', line 185

def init_provider(payload)
  if(config.get(:init, :chef, :validator) || config.get(:init, :chef, :encrypted_secret))
    bucket = stacks_api.api_for(:storage).buckets.get(config.get(:orchestration, :bucket_name))
    validator_name = name_for(payload, 'validator.pem')
    if(config.get(:init, :chef, :validator) && bucket.files.get(validator_name).nil?)
      file = bucket.files.build(:name => validator_name)
      file.body = OpenSSL::PKey::RSA.new(2048).export
      file.save
    end
    secret_name = name_for(payload, 'encrypted_data_bag_secret')
    if(config.get(:init, :chef, :encrypted_secret) && bucket.files.get(secret_name).nil?)
      file = bucket.files.build(:name => secret_name)
      file.body = SecureRandom.base64(2048)
      file.save
    end
  end
end

#load_stack_parameters(payload, directory) ⇒ Object

Note:

parameter precedence:

  • Hook URL parameters

  • Payload parameters

  • Stacks file parameters

  • Service configuration parameters

Extract any custom parameters from asset store if available, and merge any parameters provided via payload, and finally merge any parameters provided via configuration

Parameters:

  • payload (Smash)
  • directory (String)


109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/jackal-stacks/builder.rb', line 109

def load_stack_parameters(payload, directory)
  params = Smash.new
  stacks_file = load_stacks_file(payload, directory)
  s_namespace = determine_namespace(payload)
  template = payload.get(:data, :stacks, :template)
  params.deep_merge!(payload.fetch(:data, :webhook, :query, :stacks, :parameters, Smash.new))
  params.deep_merge!(payload.fetch(:data, :stacks, :parameters, Smash.new))
  params.deep_merge!(
    stacks_file.fetch(s_namespace, template, :parameters,
      stacks_file.fetch(:default, template, :parameters, Smash.new)
    )
  )
  params.deep_merge!(
    config.fetch(:parameter_overrides, s_namespace, template,
      config.fetch(:parameter_overrides, :default, template, Smash.new)
    )
  )
  params
end

#load_stacks_file(payload, directory) ⇒ Smash

Parse the ‘.stacks` file if available

Parameters:

  • payload (Smash)
  • directory (String)

    path to unpacked asset directory

Returns:

  • (Smash)


134
135
136
137
138
139
140
141
# File 'lib/jackal-stacks/builder.rb', line 134

def load_stacks_file(payload, directory)
  stacks_path = File.join(directory, '.stacks')
  if(File.exists?(stacks_path))
    Bogo::Config.new(file_path).data
  else
    Smash.new
  end
end

#name_for(payload, asset_name) ⇒ String

Note:

this is currently a no-op and thus are shared across stacks. currently is stubbed for completion of template and interaction logic

Provide prefixed key name for asset

Parameters:

  • payload (Smash)
  • asset_name (String)

    sset_name [String

Returns:

  • (String)


239
240
241
242
# File 'lib/jackal-stacks/builder.rb', line 239

def name_for(payload, asset_name)
  File.join(determine_namespace(payload), asset_name)
  asset_name
end

#run_stack(payload, directory, action) ⇒ TrueClass

Perform stack action

Parameters:

  • payload (Smash)
  • directory (String)

    directory to unpacked asset

  • action (Symbol, String)

    :create or :update

Returns:

  • (TrueClass)


149
150
151
152
153
154
155
156
157
158
# File 'lib/jackal-stacks/builder.rb', line 149

def run_stack(payload, directory, action)
  unless([:create, :update].include?(action.to_sym))
    abort ArgumentError.new("Invalid action argument `#{action}`. Expecting `create` or `update`!")
  end
  args = build_stack_args(payload, directory)
  stack_name = payload.get(:data, :stacks, :name)
  Sfn::Command.const_get(action.to_s.capitalize).new(args, [stack_name]).execute!
  wait_for_complete(stacks_api.stacks.get(stack_name))
  true
end

#setup(*_) ⇒ Object

Setup callback



11
12
13
14
15
16
17
18
# File 'lib/jackal-stacks/builder.rb', line 11

def setup(*_)
  require 'sfn'
  require 'bogo-ui'
  require 'stringio'
  require 'openssl'
  require 'fileutils'
  require 'batali'
end

#store_stable_asset(payload, directory) ⇒ Object

Store stable asset in object store

Parameters:

  • payload (Smash)


206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/jackal-stacks/builder.rb', line 206

def store_stable_asset(payload, directory)
  if(config.get(:init, :stable))
    ['.batali', 'Gemfile', 'Gemfile.lock'].each do |file|
      file_path = File.join(directory, file)
      if(File.exists?(file_path))
        debug "Removing file from infra directory: #{file}"
        FileUtils.rm(file_path)
      end
    end
    if(File.exists?(File.join(directory, 'batali.manifest')))
      debug 'Installing cookbooks from Batali manifest'
      Dir.chdir(directory) do
        Batali::Command::Install.new({}, []).execute!
      end
    end
    debug "Starting stable asset upload for #{payload[:id]}"
    bucket = stacks_api.api_for(:storage).buckets.get(config.get(:orchestration, :bucket_name))
    stable_name = name_for(payload, 'stable.zip')
    file = bucket.files.get(stable_name) || bucket.files.build(:name => stable_name)
    file.body = asset_store.pack(directory)
    file.save
    debug "Completed stable asset upload for #{payload[:id]}"
  end
end

#valid?(message) ⇒ Truthy, Falsey

Determine validity of message

Parameters:

  • message (Carnivore::Message)

Returns:

  • (Truthy, Falsey)


24
25
26
27
28
29
30
31
# File 'lib/jackal-stacks/builder.rb', line 24

def valid?(message)
  super do |payload|
    (!block_given? || yield(payload)) &&
      payload.get(:data, :stacks, :builder) &&
      payload.get(:data, :stacks, :asset) &&
      allowed?(payload)
  end
end

#wait_for_complete(stack) ⇒ TrueClass

Wait for stack to reach a completion state

Parameters:

  • stack (Miasma::Models::Orchestration::Stack)

Returns:

  • (TrueClass)


164
165
166
167
168
169
170
# File 'lib/jackal-stacks/builder.rb', line 164

def wait_for_complete(stack)
  until(stack.state.to_s.donwcase.end_with?('complete') || stack.state.to_s.donwcase.end_with?('failed'))
    sleep(10)
    stack.reload
  end
  true
end