Class: SfnLambda::Control

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/sfn-lambda/control.rb

Constant Summary collapse

DEFAULTS =
{
  :INLINE_MAX_SIZE => 4096,
  :INLINE_RESTRICTED => ['java8'].freeze,
  :BUILD_REQUIRED => {
    'java8' => {
      :build_command => 'mvn package',
      :output_directory => './target',
      :asset_extension => '.jar'
    }.freeze
  }.freeze
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeself

Create a new control instance



32
33
34
# File 'lib/sfn-lambda/control.rb', line 32

def initialize
  @functions = Smash.new
end

Instance Attribute Details

#callbackObject

Returns the value of attribute callback.



27
28
29
# File 'lib/sfn-lambda/control.rb', line 27

def callback
  @callback
end

#functionsObject (readonly)

Returns the value of attribute functions.



26
27
28
# File 'lib/sfn-lambda/control.rb', line 26

def functions
  @functions
end

Instance Method Details

#[](key) ⇒ Object

Get configuration value for control via sfn configuration and fall back to defined defaults if not set

Returns:

  • (Object)

    configuration value



197
198
199
# File 'lib/sfn-lambda/control.rb', line 197

def [](key)
  callback.config.fetch(:lambda, :config, key.to_s.downcase, DEFAULTS[key.to_s.upcase.to_sym])
end

#apply_build!(info) ⇒ TrueClass, FalseClass

Build the lambda asset if building is a requirement

Parameters:

  • info (Hash)

Returns:

  • (TrueClass, FalseClass)

    build was performed



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/sfn-lambda/control.rb', line 112

def apply_build!(info)
  if(build_info = self[:build_required][info[:runtime]])
    Open3.popen3(build_info[:build_command], :chdir => info[:path]) do |stdin, stdout, stderr, wait_thread|
      exit_status = wait_thread.value
      unless(exit_status.success?)
        callback.ui.error "Failed to build lambda assets for storage from path: #{info[:path]}"
        callback.ui.debug "Build command used which generated failure: `#{build_info[:build_command]}`"
        callback.ui.debug "STDOUT: #{stdout.read}"
        callback.ui.debug "STDERR: #{stderr.read}"
        raise "Failed to build lambda asset for storage! (path: `#{info[:path]}`)"
      end
    end
    file = Dir.glob(File.join(info[:path], build_info[:output_directory], "*.#{build_config[:asset_extension]}")).first
    if(file)
      info[:path] = file
      true
    else
      debug "Glob pattern used for build asset detection: `#{File.join(info[:path], build_info[:output_directory], "*.#{build_config[:asset_extension]}")}`"
      raise "Failed to locate generated build asset for storage! (path: `#{info[:path]}`)"
    end
  else
    false
  end
end

#bucketMiasma::Models::Storage::Bucket

Returns:

  • (Miasma::Models::Storage::Bucket)


138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/sfn-lambda/control.rb', line 138

def bucket
  storage_bucket = callback.config.fetch(:lambda, :upload, :bucket, callback.config[:nesting_bucket])
  if(storage_bucket)
    s3 = api.connection.api_for(:storage)
    l_bucket = s3.buckets.get(storage_bucket)
  end
  unless(l_bucket)
    raise "Failed to locate configured bucket for lambda storage (#{storage_bucket})"
  else
    l_bucket
  end
end

#can_inline?(info) ⇒ TrueClass, FalseClass

Determine if function can be defined inline within template

Parameters:

  • info (Hash)

Returns:

  • (TrueClass, FalseClass)


189
190
191
# File 'lib/sfn-lambda/control.rb', line 189

def can_inline?(info)
  !self[:inline_restricted].include?(info[:runtime]) && File.size(info[:path]) <= self[:inline_max_size]
end

#discover_functions!NilClass

Discover all defined lambda functions available in directories provided via configuration

Returns:

  • (NilClass)


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

def discover_functions!
  core_paths = lambda_directories
  core_paths.each do |path|
    Dir.new(path).each do |dir_item|
      next if dir_item.start_with?('.')
      next unless File.directory?(File.join(path, dir_item))
      if(self[:build_required].keys.include?(dir_item))
        Dir.new(File.join(path, dir_item)).each do |item|
          next if item.start_with?('.')
          full_item = File.join(path, dir_item, item)
          next unless File.directory?(full_item)
          functions.set(dir_item, item, full_item)
        end
      else
        items = Dir.glob(File.join(path, dir_item, '**', '**', '*'))
        items.each do |full_item|
          next unless File.file?(full_item)
          item = full_item.sub(File.join(path, dir_item, ''), '').gsub(File::SEPARATOR, '')
          item = item.sub(/#{Regexp.escape(File.extname(item))}$/, '')
          functions.set(dir_item, item, full_item)
        end
      end
    end
  end
  nil
end

#format_content(info) ⇒ Smash

Format lambda function content to use within template. Will provide raw source when function can be inlined within the template. If inline is not available, it will store within S3

Parameters:

  • info (Hash)

    content information

Options Hash (info):

  • :path (String)

    path to lambda function

  • :runtime (String)

    runtime of lambda function

  • :name (String)

    name of lambda function

Returns:

  • (Smash)

    content information



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/sfn-lambda/control.rb', line 84

def format_content(info)
  if(can_inline?(info))
    Smash.new(:raw => File.read(info[:path]))
  else
    apply_build!(info)
    key_name = generate_key_name(info)
    io = File.open(info[:path], 'rb')
    file = bucket.files.build
    file.name = key_name
    file.body = io
    file.save
    io.close
    if(versioning_enabled?)
      result = s3.request(
        :path => s3.file_path(file),
        :endpoint => s3.bucket_endpoint(file.bucket),
        :method => :head
      )
      version = result[:headers][:x_amz_version_id]
    end
    Smash(:bucket => storage_bucket, :key => key_name, :version => version)
  end
end

#generate_key_name(info) ⇒ String

Generate key name based on state

Parameters:

  • info (Hash)

Returns:

  • (String)

    key name



171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/sfn-lambda/control.rb', line 171

def generate_key_name(info)
  if(versioning_enabled?)
    "sfn.lambda/#{info[:runtime]}/#{File.basename(info[:path])}"
  else
    checksum = Digest::SHA256.new
    File.open(info[:path], 'rb') do |file|
      while(content = file.read(2048))
        checksum << content
      end
    end
    "sfn.lambda/#{info[:runtime]}/#{File.basename(info[:path])}-#{checksum.base64digest}"
  end
end

#get(name, runtime = nil) ⇒ Hash

Get path to lambda function

Parameters:

  • name (String)

    name of lambda function

  • runtime (String) (defaults to: nil)

    runtime of lambda function

Returns:

  • (Hash)

    :runtime



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/sfn-lambda/control.rb', line 55

def get(name, runtime=nil)
  unless(runtime)
    runtime = functions.keys.find_all do |r_name|
      functions[r_name].keys.include?(name.to_s)
    end
    if(runtime.empty?)
      raise "Failed to locate requested lambda function `#{name}`"
    elsif(runtime.size > 1)
      raise "Multiple lambda function matches for `#{name}`. (In runtimes: `#{runtime.sort.join('`, `')}`)"
    end
    runtime = runtime.first
  end
  result = functions.get(runtime, name)
  if(result.nil?)
    raise "Failed to locate requested lambda function `#{name}`"
  else
    Smash.new(:path => result, :runtime => runtime, :name => name)
  end
end

#lambda_directoriesArray<String>

Returns paths to lambda storage directories.

Returns:

  • (Array<String>)

    paths to lambda storage directories



37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/sfn-lambda/control.rb', line 37

def lambda_directories
  paths = [callback.config.fetch(:lambda, :directory, 'lambda')].flatten.compact.uniq.map do |path|
    File.expand_path(path)
  end
  invalids = paths.find_all do |path|
    !File.directory?(path)
  end
  unless(invalids.empty?)
    raise "Invalid lambda directory paths provided: #{invalids.join(', ')}"
  end
  paths
end

#versioning_enabled?TrueClass, FalseClass

Returns bucket has versioning enabled.

Returns:

  • (TrueClass, FalseClass)

    bucket has versioning enabled



152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/sfn-lambda/control.rb', line 152

def versioning_enabled?
  unless(@versioned.nil?)
    s3 = api.connection.api_for(:storage)
    result = s3.request(
      :path => '/',
      :params => {
        :versioning => true
      },
      :endpoint => s3.bucket_endpoint(bucket)
    )
    @versioned = result.get(:body, 'VersioningConfiguration', 'Status') == 'Enabled'
  end
  @versioned
end