Class: Swaggerless::Deployer

Inherits:
Object
  • Object
show all
Defined in:
lib/swaggerless/deployer.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(account, region, env) ⇒ Deployer

Returns a new instance of Deployer.



8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/swaggerless/deployer.rb', line 8

def initialize(, region, env)
  @env = env
  @region = region
  @account = 
  @api_gateway_client = Aws::APIGateway::Client.new(region: @region)
  @output_path = 'output'
  @verbose = false;
  @function_alias = get_current_package_alias
  @swaggerExtractor = Swaggerless::SwaggerExtractor.new()
  @cloudwatch_client = Aws::CloudWatchLogs::Client.new(region: @region)
  @lambda_client = Aws::Lambda::Client.new(region: @region)
end

Class Method Details

.get_service_prefix(swagger) ⇒ Object



25
26
27
# File 'lib/swaggerless/deployer.rb', line 25

def self.get_service_prefix(swagger)
  return swagger["info"]["title"].gsub(/\s+/, '_')
end

Instance Method Details

#attach_log_forwarder(lambda_name, log_forwarder) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/swaggerless/deployer.rb', line 95

def attach_log_forwarder(lambda_name, log_forwarder)
  permissionStatementId = Digest::SHA1.hexdigest "logs_#{lambda_name}_to_#{log_forwarder}"
  begin
    @lambda_client.remove_permission({function_name: "arn:aws:lambda:#{@region}:#{@account}:function:#{log_forwarder}",
      statement_id: permissionStatementId})
  rescue Aws::Lambda::Errors::ResourceNotFoundException
  end

  @lambda_client.add_permission({function_name: "arn:aws:lambda:#{@region}:#{@account}:function:#{log_forwarder}",
    statement_id: permissionStatementId,
    action: "lambda:InvokeFunction",
    principal: "logs.#{@region}.amazonaws.com", source_arn: "arn:aws:logs:#{@region}:#{@account}:log-group:/aws/lambda/#{lambda_name}:*",
    source_account: "#{@account}" })

  begin
    resp = @cloudwatch_client.describe_log_groups({ log_group_name_prefix: "/aws/lambda/#{lambda_name}", limit: 1 })
    if resp.log_groups.length == 0
      @cloudwatch_client.create_log_group({log_group_name: "/aws/lambda/#{lambda_name}"})
    end
    resp = @cloudwatch_client.describe_subscription_filters({log_group_name: "/aws/lambda/#{lambda_name}"})
    if resp.subscription_filters.length > 0
      resp.subscription_filters.each do |filter|
      @cloudwatch_client.delete_subscription_filter({log_group_name: "/aws/lambda/#{lambda_name}", filter_name: filter.filter_name})
      end
    end
    @cloudwatch_client.put_subscription_filter({ log_group_name: "/aws/lambda/#{lambda_name}",
      filter_name: log_forwarder, filter_pattern: '',
      destination_arn: "arn:aws:lambda:#{@region}:#{@account}:function:#{log_forwarder}"})
  end
end

#create_api_gateway(swagger) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/swaggerless/deployer.rb', line 161

def create_api_gateway(swagger)
  puts "Creating API Gateway"
  apis = @api_gateway_client.get_rest_apis(limit: 500).data
  api = apis.items.select { |a| a.name == swagger['info']['title'] }.first

  if swagger['basePath']
    swagger['paths'] = Hash[swagger['paths'].map {|k, v| [ swagger['basePath'] + k, v ] }]
  end

  if (swagger.key?("definitions")) then
    swagger["definitions"].each do |key, value|
      if (value.key?("example")) then
        value.delete("example")
      end
    end
  end

  if api
    resp = @api_gateway_client.put_rest_api({rest_api_id: api.id, mode: "overwrite", fail_on_warnings: true, body: swagger.to_yaml})
  else
    resp = @api_gateway_client.import_rest_api({fail_on_warnings: true, body: swagger.to_yaml})
  end

  if resp.warnings then
    resp.warnings.each do |warning|
      STDERR.puts "WARNING: " + warning
    end
  end

  return resp.id
end

#create_api_gateway_deployment(lambda_role_arn, swagger) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/swaggerless/deployer.rb', line 193

def create_api_gateway_deployment(lambda_role_arn, swagger)
  deploy_lambdas_and_update_uris(lambda_role_arn, swagger)
  deploy_authoirizers_and_update_authorizers_uri(lambda_role_arn, swagger)
  api_id = self.create_api_gateway(swagger);
  while true do
    begin
      puts "Creating API Gateway Deployment"
      @api_gateway_client.create_deployment({rest_api_id: api_id, stage_name: @env, description: "Automated deployment of #{@env} using lambda: #{@function_alias}", variables: {"env" => @env }});
      url = "https://#{api_id}.execute-api.#{@region}.amazonaws.com/#{@env}/"
      puts "API available at #{url}"
      return url;
    rescue Aws::APIGateway::Errors::TooManyRequestsException
      STDERR.puts 'WARNING: Got TooManyRequests response from API Gateway. Waiting...'
      sleep(5)
    end
  end

end

#create_lambda_package(directory, outputName) ⇒ Object



21
22
23
# File 'lib/swaggerless/deployer.rb', line 21

def create_lambda_package(directory, outputName)
  Swaggerless::Packager.new(directory, "#{outputName}.zip")
end

#deploy_authoirizers_and_update_authorizers_uri(lambda_role_arn, swagger) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/swaggerless/deployer.rb', line 29

def deploy_authoirizers_and_update_authorizers_uri(lambda_role_arn, swagger)
  lambdas_configs = @swaggerExtractor.get_lambda_map(swagger)
  if swagger.key?("securityDefinitions") then
    swagger["securityDefinitions"].each do |securityDefinitionName, securityDefinition|
      if securityDefinition[AMZ_APIGATEWAY_AUTHORIZER] != nil then
        authorizer = securityDefinition[EXT_LAMBDA_NAME]
        if securityDefinition[EXT_LAMBDA_NAME] and securityDefinition[EXT_LAMBDA_HANDLER] then
          config = lambdas_configs[securityDefinition[EXT_LAMBDA_NAME]]
          securityDefinition[AMZ_APIGATEWAY_AUTHORIZER]["authorizerUri"] =
              deploy_lambda_and_attach_log_forwarder(lambda_role_arn, securityDefinition, config)
        elsif securityDefinition[EXT_LAMBDA_NAME]
          securityDefinition[AMZ_APIGATEWAY_AUTHORIZER]["authorizerUri"] = "arn:aws:apigateway:#{@region}:lambda:path/2015-03-31/functions/arn:aws:lambda:#{@region}:#{@account}:function:#{authorizer}/invocations"
        end
        securityDefinition[SWGR_AUTH_TYPE] = 'apiKey'
        policy_exists = false
        policy_name = "API_2_#{authorizer}".gsub(":","_")
        begin
          existing_policies = @lambda_client.get_policy(function_name: authorizer).data
          existing_policy = JSON.parse(existing_policies.policy)
          policy_exists = existing_policy['Statement'].select { |s| s['Sid'] == policy_name }.any?
        rescue Aws::Lambda::Errors::ResourceNotFoundException
          policy_exists = false
        end
        unless policy_exists
          @lambda_client.add_permission({function_name: "arn:aws:lambda:#{@region}:#{@account}:function:#{authorizer}",
            statement_id: policy_name, action: "lambda:*", principal: 'apigateway.amazonaws.com'})
        end
      end
    end
  end
end

#deploy_lambda(lambda_role_arn, function_name, summary, runtime, handler, timeout) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/swaggerless/deployer.rb', line 126

def deploy_lambda(lambda_role_arn, function_name, summary, runtime, handler, timeout)
  puts "Deploying #{function_name}"
  runtime ||= 'nodejs4.3'
  timeout ||= 5
  permissionStatementId = Digest::SHA1.hexdigest "API_2_#{function_name}_#{@function_alias}"
  begin
    @lambda_client.get_alias({function_name: function_name, name: @function_alias})
  rescue Aws::Lambda::Errors::ResourceNotFoundException
    lambda_response = nil
    zip_file_content = File.read(File.join(@output_path, "#{@function_alias}.zip"))
    begin
      @lambda_client.get_function({function_name: function_name})
      lambda_response = @lambda_client.update_function_code({function_name: function_name, zip_file: zip_file_content, publish: true})
      @lambda_client.update_function_configuration({function_name: function_name, runtime: runtime, role: lambda_role_arn, handler: handler, description: summary, timeout: timeout})
    rescue Aws::Lambda::Errors::ResourceNotFoundException
      puts "Creating new function #{function_name}"
      lambda_response = @lambda_client.create_function({function_name: function_name, runtime: runtime, role: lambda_role_arn, handler: handler, code: {zip_file: zip_file_content }, description: summary, publish: true, timeout: timeout})
    end
    puts "Creating alias #{@function_alias}"
    alias_resp = @lambda_client.create_alias({function_name: function_name, name: @function_alias, function_version: lambda_response.version, description: "Deployment of new version on " +  Time.now.inspect})
    @lambda_client.add_permission({function_name: alias_resp.alias_arn, statement_id: permissionStatementId, action: "lambda:*", principal: 'apigateway.amazonaws.com'})
  end
  return "arn:aws:apigateway:#{@region}:lambda:path/2015-03-31/functions/arn:aws:lambda:#{@region}:#{@account}:function:#{function_name}:#{@function_alias}/invocations"
end

#deploy_lambda_and_attach_log_forwarder(lambda_role_arn, lambda_obj, config) ⇒ Object



87
88
89
90
91
92
93
# File 'lib/swaggerless/deployer.rb', line 87

def deploy_lambda_and_attach_log_forwarder(lambda_role_arn, lambda_obj, config)
  lambda_arn = deploy_lambda(lambda_role_arn, lambda_obj[EXT_LAMBDA_NAME], config[:description], config[:runtime], config[:handler], config[:timeout])
  if config[:log_forwarder]
    attach_log_forwarder(lambda_obj[EXT_LAMBDA_NAME], config[:log_forwarder])
  end
  return lambda_arn
end

#deploy_lambdas_and_update_uris(lambda_role_arn, swagger) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/swaggerless/deployer.rb', line 61

def deploy_lambdas_and_update_uris(lambda_role_arn, swagger)
  lambdas_configs = @swaggerExtractor.get_lambda_map(swagger)
  deployed_operations = Hash.new
  swagger["paths"].each do |path, path_config|
    path_config.each do |method, method_config|
      if method_config[EXT_LAMBDA_HANDLER] then
        if deployed_operations[method_config[EXT_LAMBDA_NAME]] == nil
          config = lambdas_configs[method_config[EXT_LAMBDA_NAME]]
          deployed_operations[method_config[EXT_LAMBDA_NAME]] =
              deploy_lambda_and_attach_log_forwarder(lambda_role_arn, method_config, config)
        end
        puts "Updating swagger with integration uri for #{method} #{path}: #{deployed_operations[method_config[EXT_LAMBDA_NAME]]}" unless not @verbose
        method_config['x-amazon-apigateway-integration']['uri'] = deployed_operations[method_config[EXT_LAMBDA_NAME]]
      elsif method_config[EXT_LAMBDA_NAME] then
        if lambdas_configs[method_config[EXT_LAMBDA_NAME] == nil]
          external_lambda_arn = "arn:aws:apigateway:#{@region}:lambda:path/2015-03-31/functions/arn:aws:lambda:#{@region}:#{@account}:function:#{method_config[EXT_LAMBDA_NAME]}/invocations"
          method_config['x-amazon-apigateway-integration']['uri'] = external_lambda_arn
          puts "Adding integration to lambda that is external (#{external_lambda_arn}) to the project. Make sure to grant permissions so that API Gateway can call it."
        else
          method_config['x-amazon-apigateway-integration']['uri'] = "arn:aws:apigateway:#{@region}:lambda:path/2015-03-31/functions/arn:aws:lambda:#{@region}:#{@account}:function:#{method_config[EXT_LAMBDA_NAME]}:#{@function_alias}/invocations"
        end
      end
    end
  end
end

#get_current_package_aliasObject



151
152
153
154
155
156
157
158
159
# File 'lib/swaggerless/deployer.rb', line 151

def get_current_package_alias
  zipFiles = Dir["#{@output_path}/*.zip"]
  if zipFiles.length == 0 then
    raise 'No package in the output folder. Unable to continue.'
  elsif zipFiles.length == 0
    raise 'Multiple package in the output folder. Unable to continue.'
  end
  File.basename(zipFiles.first, '.zip')
end