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.
@@ -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
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -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
+
@@ -0,0 +1,3 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task :default => :spec
@@ -0,0 +1,6 @@
1
+ Dir["#{File.expand_path(File.dirname(__FILE__))}/**/*.rb"].each { |f| require f }
2
+
3
+ STDOUT.sync = true
4
+ STDERR.sync = true
5
+
6
+ load 'tasks/swaggerless.rake'
@@ -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,3 @@
1
+ module Swaggerless
2
+ VERSION = "0.1.0"
3
+ 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
@@ -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: []