swaggerless 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +3 -0
- data/README.md +30 -0
- data/Rakefile +3 -0
- data/lib/swaggerless.rb +6 -0
- data/lib/swaggerless/deployer.rb +135 -0
- data/lib/swaggerless/packager.rb +57 -0
- data/lib/swaggerless/version.rb +3 -0
- data/lib/tasks/swaggerless.rake +52 -0
- data/swaggerless.gemspec +30 -0
- metadata +125 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9674dbf5858fbd49c52d4618342cef130958ce2b
|
4
|
+
data.tar.gz: 117d7804b5871e64b30fcc80dcb7e75786c3ebbf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 39003efb6aa3728827e9d48ff8e7fffabf8d6a6bb7f752a43d94355e8aa480d39733f3d2dbc6162b3cdff4068f102742c4fba1ab3bab1ee379f3300ed5fb78e9
|
7
|
+
data.tar.gz: 5f1e63e1565cca994550547068f279c27fdf24ab6ad3b771213243649613d1c8a68e61b1ce25dff0541b5cc7ba6d6ec1b1b35545174f4d667da7b518792123a1
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Swaggerless
|
2
|
+
|
3
|
+
This simple gem is supposed to speed up work on deploying serverless applications into the AWS.
|
4
|
+
The idea behind this particualar development workflow is simple:
|
5
|
+
|
6
|
+
1. Design you API using [OpenAPI Specification](https://github.com/OAI/OpenAPI-Specification)
|
7
|
+
2. Add extension entries to the specification accoding to [AWS Guidelines](http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions.html)
|
8
|
+
3. Add couple more extension entries to taste, that help the gem do the right thing
|
9
|
+
4. Inlcude the Swaggerless in your Rakefile and see your service stub deployed to AWS Lambda, fronted by API Gateway
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'swaggerless'
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install swaggerless
|
26
|
+
|
27
|
+
## Contributing
|
28
|
+
|
29
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/swaggerless.
|
30
|
+
|
data/Rakefile
ADDED
data/lib/swaggerless.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
require "aws-sdk"
|
2
|
+
|
3
|
+
module Swaggerless
|
4
|
+
|
5
|
+
class Deployer
|
6
|
+
|
7
|
+
def initialize(account, region, env)
|
8
|
+
@env = env
|
9
|
+
@region = region
|
10
|
+
@account = account
|
11
|
+
@apiGatewayClient = Aws::APIGateway::Client.new(region: @region)
|
12
|
+
@outputPath = 'output'
|
13
|
+
@function_alias = get_current_package_alias
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_lambda_package(directory, outputName)
|
17
|
+
Swaggerless::Packager.new(directory, "#{outputName}.zip")
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.get_service_prefix(swagger)
|
21
|
+
return swagger["info"]["title"].gsub(/\s+/, '_')
|
22
|
+
end
|
23
|
+
|
24
|
+
def update_authorizer_uri(swagger)
|
25
|
+
swagger["securityDefinitions"].each do |securityDefinitionName, securityDefinition|
|
26
|
+
if securityDefinition['x-amazon-apigateway-authorizer'] != nil then
|
27
|
+
authorizer = securityDefinition['x-amazon-apigateway-authorizer-lambda']
|
28
|
+
securityDefinition['x-amazon-apigateway-authorizer']["authorizerUri"] = "arn:aws:apigateway:#{@region}:lambda:path/2015-03-31/functions/arn:aws:lambda:#{@region}:#{@account}:function:#{authorizer}/invocations"
|
29
|
+
lambdaClient = Aws::Lambda::Client.new(region: @region)
|
30
|
+
policy_exists = false
|
31
|
+
begin
|
32
|
+
existing_policies = lambdaClient.get_policy(function_name: authorizer).data
|
33
|
+
existing_policy = JSON.parse(existing_policies.policy)
|
34
|
+
policy_exists = existing_policy['Statement'].select { |s| s['Sid'] == "API_2_#{authorizer}" }.any?
|
35
|
+
rescue Aws::Lambda::Errors::ResourceNotFoundException
|
36
|
+
policy_exists = false
|
37
|
+
end
|
38
|
+
unless policy_exists
|
39
|
+
lambdaClient.add_permission({function_name: "arn:aws:lambda:#{@region}:#{@account}:function:#{authorizer}",
|
40
|
+
statement_id: "API_2_#{authorizer}", action: "lambda:*", principal: 'apigateway.amazonaws.com'})
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def deploy_lambdas_and_update_integration_uris(lambda_role_arn, swagger)
|
47
|
+
deployedOperations = Hash.new
|
48
|
+
swagger["paths"].each do |path, path_config|
|
49
|
+
path_config.each do |method, method_config|
|
50
|
+
if method_config['operationId'] then
|
51
|
+
if deployedOperations[method_config['operationId']] == nil then
|
52
|
+
function_name = Deployer.get_service_prefix(swagger) + "_" + method_config['operationId'].split(".").last
|
53
|
+
deployedOperations[method_config['operationId']] =
|
54
|
+
deploy_lambda(lambda_role_arn, function_name, method_config["summary"], method_config['x-amazon-lambda-runtime'] || 'nodejs4.3',
|
55
|
+
method_config['operationId'], method_config['x-amazon-lambda-timeout'] || 5)
|
56
|
+
end
|
57
|
+
method_config['x-amazon-apigateway-integration']['uri'] = deployedOperations[method_config['operationId']]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def deploy_lambda(lambda_role_arn, function_name, summary, runtime, handler, timeout)
|
64
|
+
puts "Deploying #{function_name}"
|
65
|
+
lambdaClient = Aws::Lambda::Client.new(region: @region)
|
66
|
+
begin
|
67
|
+
lambdaClient.get_alias({function_name: function_name, name: @function_alias})
|
68
|
+
rescue Aws::Lambda::Errors::ResourceNotFoundException
|
69
|
+
lambdaResponse = nil
|
70
|
+
zipFileContent = File.read(File.join(@outputPath,"#{@function_alias}.zip"))
|
71
|
+
begin
|
72
|
+
lambdaClient.get_function({function_name: function_name})
|
73
|
+
lambdaResponse = lambdaClient.update_function_code({function_name: function_name, zip_file: zipFileContent, publish: true})
|
74
|
+
lambdaClient.update_function_configuration({function_name: function_name, runtime: runtime, role: lambda_role_arn, handler: handler, description: summary, timeout: timeout})
|
75
|
+
rescue Aws::Lambda::Errors::ResourceNotFoundException
|
76
|
+
puts "Creating new function #{function_name}"
|
77
|
+
lambdaResponse = lambdaClient.create_function({function_name: function_name, runtime: runtime, role: lambda_role_arn, handler: handler, code: { zip_file: zipFileContent }, description: summary, publish: true, timeout: timeout})
|
78
|
+
end
|
79
|
+
puts "Creating alias #{@function_alias}"
|
80
|
+
aliasResp = lambdaClient.create_alias({function_name: function_name, name: @function_alias, function_version: lambdaResponse.version, description: "Deployment of new version on " + Time.now.inspect})
|
81
|
+
lambdaClient.add_permission({function_name: aliasResp.alias_arn, statement_id: "API_2_#{function_name}_#{@function_alias}", action: "lambda:*", principal: 'apigateway.amazonaws.com'})
|
82
|
+
end
|
83
|
+
return "arn:aws:apigateway:#{@region}:lambda:path/2015-03-31/functions/arn:aws:lambda:#{@region}:#{@account}:function:#{function_name}:#{@function_alias}/invocations"
|
84
|
+
end
|
85
|
+
|
86
|
+
def get_current_package_alias
|
87
|
+
zipFiles = Dir["#{@outputPath}/*.zip"]
|
88
|
+
if zipFiles.length == 0 then
|
89
|
+
raise 'No package in the output folder. Unable to continue.'
|
90
|
+
elsif zipFiles.length == 0
|
91
|
+
raise 'Multiple package in the output folder. Unable to continue.'
|
92
|
+
end
|
93
|
+
return File.basename(zipFiles.first, '.zip')
|
94
|
+
end
|
95
|
+
|
96
|
+
def create_api_gateway(swagger)
|
97
|
+
puts "Creating API Gateway"
|
98
|
+
apis = @apiGatewayClient.get_rest_apis(limit: 500).data
|
99
|
+
api = apis.items.select { |a| a.name == swagger['info']['title'] }.first
|
100
|
+
|
101
|
+
if api then
|
102
|
+
resp = @apiGatewayClient.put_rest_api({rest_api_id: api.id, mode: "overwrite", fail_on_warnings: true, body: swagger.to_yaml})
|
103
|
+
else
|
104
|
+
resp = @apiGatewayClient.import_rest_api({fail_on_warnings: true, body: swagger.to_yaml})
|
105
|
+
end
|
106
|
+
|
107
|
+
if resp.warnings then
|
108
|
+
resp.warnings.each do |warning|
|
109
|
+
STDERR.puts "WARNING: " + warning
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
return resp.id
|
114
|
+
end
|
115
|
+
|
116
|
+
def create_api_gateway_deployment(lambda_role_arn, swagger)
|
117
|
+
deploy_lambdas_and_update_integration_uris(lambda_role_arn, swagger)
|
118
|
+
update_authorizer_uri(swagger)
|
119
|
+
apiId = self.create_api_gateway(swagger);
|
120
|
+
while true do
|
121
|
+
begin
|
122
|
+
puts "Creating API Gateway Deployment"
|
123
|
+
@apiGatewayClient.create_deployment({rest_api_id: apiId, stage_name: @env, description: "Automated deployment of #{@env}", variables: { "env" => @env }});
|
124
|
+
break
|
125
|
+
rescue Aws::APIGateway::Errors::TooManyRequestsException => e
|
126
|
+
STDERR.puts 'WARNING: Got TooManyRequests response from API Gateway. Waiting for a second.'
|
127
|
+
sleep(1)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "zip"
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
module Swaggerless
|
5
|
+
|
6
|
+
class Packager
|
7
|
+
|
8
|
+
def initialize(input_dir, output_file)
|
9
|
+
@input_dir = input_dir
|
10
|
+
@output_file = output_file
|
11
|
+
end
|
12
|
+
|
13
|
+
def write
|
14
|
+
version_hash = version_hash()
|
15
|
+
entries = Dir.entries(@input_dir) - %w(. ..)
|
16
|
+
|
17
|
+
::Zip::File.open("#{@output_file}_#{version_hash}.zip", ::Zip::File::CREATE) do |io|
|
18
|
+
write_entries entries, '', io
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def version_hash()
|
25
|
+
files = Dir["#{@input_dir}/**/*"].reject{|f| File.directory?(f)}
|
26
|
+
content = files.map{|f| File.read(f)}.join
|
27
|
+
Digest::SHA1.hexdigest(content).to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def write_entries(entries, path, io)
|
31
|
+
entries.each do |e|
|
32
|
+
zip_file_path = path == '' ? e : File.join(path, e)
|
33
|
+
disk_file_path = File.join(@input_dir, zip_file_path)
|
34
|
+
|
35
|
+
|
36
|
+
if File.directory? disk_file_path
|
37
|
+
recursively_deflate_directory(disk_file_path, io, zip_file_path)
|
38
|
+
else
|
39
|
+
put_into_archive(disk_file_path, io, zip_file_path)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def recursively_deflate_directory(disk_file_path, io, zip_file_path)
|
45
|
+
io.mkdir zip_file_path
|
46
|
+
subdir = Dir.entries(disk_file_path) - %w(. ..)
|
47
|
+
write_entries subdir, zip_file_path, io
|
48
|
+
end
|
49
|
+
|
50
|
+
def put_into_archive(disk_file_path, io, zip_file_path)
|
51
|
+
io.get_output_stream(zip_file_path) do |f|
|
52
|
+
f.write(File.open(disk_file_path, 'rb').read)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "json"
|
3
|
+
require "swaggerless"
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
desc 'Deploys to an environment specified as parameter'
|
7
|
+
task :deploy, [ :environment ] => [ :clean, :package ] do |t, args|
|
8
|
+
puts "Deploying API Gateway"
|
9
|
+
if not @swaggerSpecFile then
|
10
|
+
@swaggerSpecFile = 'swagger.yaml'
|
11
|
+
STDERR.puts("Swagger file not configured. Trying default ('#{@swaggerSpecFile}'). Set @swaggerSpecFile to point to swagger yaml file if you use different file name")
|
12
|
+
end
|
13
|
+
|
14
|
+
if not @lambdaRoleArn then
|
15
|
+
raise "Unable to continue. Please configue @lambdaRoleArn in the Rakefile"
|
16
|
+
end
|
17
|
+
|
18
|
+
if not @awsRegion then
|
19
|
+
@awsRegion = ENV['AWS_REGION'] || 'eu-west-1'
|
20
|
+
STDERR.puts("AWS Region is not configured. Trying default ('#{@awsRegion}'). Set @awsRegion to point to swagger yaml file if you use different file name")
|
21
|
+
end
|
22
|
+
|
23
|
+
if not @awsAccount then
|
24
|
+
raise "Unable to continue. Please configue @awsAccount in the Rakefile"
|
25
|
+
end
|
26
|
+
|
27
|
+
swagger_content = File.read(@swaggerSpecFile)
|
28
|
+
swagger = YAML.load(swagger_content)
|
29
|
+
Swaggerless::Deployer.new(@awsAccount, @awsRegion, args[:environment]).create_api_gateway_deployment(@lambdaRoleArn, swagger)
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Package the project for AWS Lambda'
|
33
|
+
task :package do
|
34
|
+
puts "Packaging"
|
35
|
+
|
36
|
+
FileUtils.mkdir_p 'output'
|
37
|
+
|
38
|
+
if not @packageDir then
|
39
|
+
@packageDir = 'src'
|
40
|
+
STDERR.puts("Package directory not configured. Trying default ('#{@packageDir}'). Set @packageDir to point to the directory that should be packaged for AWS Lambda")
|
41
|
+
end
|
42
|
+
|
43
|
+
swagger_content = File.read(@swaggerSpecFile)
|
44
|
+
swagger = YAML.load(swagger_content)
|
45
|
+
servicePrefix = Swaggerless::Deployer.get_service_prefix(swagger)
|
46
|
+
Swaggerless::Packager.new(@packageDir, "output/#{servicePrefix}").write
|
47
|
+
end
|
48
|
+
|
49
|
+
desc 'Clean'
|
50
|
+
task :clean do
|
51
|
+
FileUtils.rm_rf('output')
|
52
|
+
end
|
data/swaggerless.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'swaggerless/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "swaggerless"
|
8
|
+
spec.version = Swaggerless::VERSION
|
9
|
+
spec.authors = ["Rafal Nowosielski"]
|
10
|
+
spec.email = ["rafal@nowosielski.email"]
|
11
|
+
|
12
|
+
spec.summary = "The gem includes common tasks needed to deploy design first serverless to the AWS"
|
13
|
+
spec.description = "The gem includes common tasks needed to deploy design first Open API spec to AWS using Lambdas and API Gateway"
|
14
|
+
spec.homepage = "https://open.cimpress.io"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
26
|
+
|
27
|
+
spec.add_runtime_dependency "aws-sdk", "~> 2.6"
|
28
|
+
spec.add_runtime_dependency "rubyzip"
|
29
|
+
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: swaggerless
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rafal Nowosielski
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.13'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: aws-sdk
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.6'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.6'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubyzip
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: The gem includes common tasks needed to deploy design first Open API
|
84
|
+
spec to AWS using Lambdas and API Gateway
|
85
|
+
email:
|
86
|
+
- rafal@nowosielski.email
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- .gitignore
|
92
|
+
- Gemfile
|
93
|
+
- README.md
|
94
|
+
- Rakefile
|
95
|
+
- lib/swaggerless.rb
|
96
|
+
- lib/swaggerless/deployer.rb
|
97
|
+
- lib/swaggerless/packager.rb
|
98
|
+
- lib/swaggerless/version.rb
|
99
|
+
- lib/tasks/swaggerless.rake
|
100
|
+
- swaggerless.gemspec
|
101
|
+
homepage: https://open.cimpress.io
|
102
|
+
licenses: []
|
103
|
+
metadata: {}
|
104
|
+
post_install_message:
|
105
|
+
rdoc_options: []
|
106
|
+
require_paths:
|
107
|
+
- lib
|
108
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
requirements: []
|
119
|
+
rubyforge_project:
|
120
|
+
rubygems_version: 2.0.14.1
|
121
|
+
signing_key:
|
122
|
+
specification_version: 4
|
123
|
+
summary: The gem includes common tasks needed to deploy design first serverless to
|
124
|
+
the AWS
|
125
|
+
test_files: []
|