minimal_pipeline 0.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c77dd539e2d2d4c1a1639ed735bb8ef0d1e0802e59a1240e16af9e2baa03954d
4
+ data.tar.gz: 22fc7cc213e129f5ceada0fde056f61cfaa3722078b65b1dbc99b77d8fa48bcf
5
+ SHA512:
6
+ metadata.gz: fd61094908f6d9407dbbf1b3e7b8218013125a71d0fe9582887547b297138acfc3952cab760661ab1856a4b80b1d99c12f5771128d6b5b31f27272ad93cd29c8
7
+ data.tar.gz: cf8b54cdedd4b8bccbbe5539cf6ff15742fc3968bc2ff74aa37f7db0bb9e77d6fd25905af81c635e5250b0d7120df3c8ef0f8cbc1c8399c91cef7c5de7891879
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk'
4
+ require 'crossing'
5
+ require 'keystore'
6
+ require 'open3'
7
+ require 'docker'
8
+ require 'json'
9
+
10
+ # Top level namespace for automation pipeline classes
11
+ class MinimalPipeline
12
+ # autoload libraries
13
+ autoload(:Cloudformation, 'minimal_pipeline/cloudformation')
14
+ autoload(:Crossing, 'minimal_pipeline/crossing')
15
+ autoload(:Docker, 'minimal_pipeline/docker')
16
+ autoload(:Keystore, 'minimal_pipeline/keystore')
17
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk'
4
+
5
+ class MinimalPipeline
6
+ # Here is an example of how to use this class to deploy CloudFormation Stacks.
7
+ #
8
+ # ```
9
+ # cloudformation = MinimalPipeline::Cloudformation.new
10
+ #
11
+ # cloudformation_parameters = {
12
+ # 'Vpc' => 'vpc-123456',
13
+ # 'AsgSubnets' => %w[sg-one sg-two sg-three],
14
+ # 'ElbSecurityGroup' => 'sg-123456',
15
+ # 'CertName' => 'example'
16
+ # }
17
+ #
18
+ # stack_parameters = {
19
+ # stack_name: stack_name,
20
+ # template_body: File.read('provisioning/elb.json'),
21
+ # capabilities: ['CAPABILITY_IAM'],
22
+ # parameters: cloudformation.params(cloudformation_parameters)
23
+ # }
24
+ #
25
+ # cloudformation.deploy_stack('EXAMPLE_ELB', stack_parameters)
26
+ # name = cloudformation.stack_output(stack_name, 'LoadBalancerName')
27
+ # ```
28
+ #
29
+ # You will need the following environment variables to be present:
30
+ # * `AWS_REGION` or `region`
31
+ class Cloudformation
32
+ # Instance of `Aws::CloudFormation::Client`
33
+ attr_reader(:client)
34
+
35
+ # Sets up `Aws::CloudFormation::Client`
36
+ # Requires environment variable `AWS_REGION` or `region` to be set.
37
+ def initialize
38
+ raise 'You must set env variable AWS_REGION.' if ENV['AWS_REGION'].nil?
39
+
40
+ region = ENV['AWS_REGION']
41
+ @client = Aws::CloudFormation::Client.new(region: region)
42
+ end
43
+
44
+ # Converts a parameter Hash into a CloudFormation friendly structure
45
+ #
46
+ # @param parameters [Hash] Key value pair of parameters for a CFN stack.
47
+ # @return [Hash] CloudFormation friendly data structure of parameter
48
+ def params(parameters)
49
+ parameter_list = []
50
+ parameters.each do |k, v|
51
+ parameter_list.push(parameter_key: k, parameter_value: v)
52
+ end
53
+ parameter_list
54
+ end
55
+
56
+ # Retrieves the CloudFormation stack output of a single value
57
+ #
58
+ # @param stack [String] The name of the CloudFormation stack
59
+ # @param output [String] The name of the output to fetch the value of
60
+ # @return [String] The value of the output for the CloudFormation stack
61
+ def stack_output(stack, output)
62
+ resp = @client.describe_stacks(stack_name: stack)
63
+
64
+ raise "#{stack.upcase} stack does not exist!" if resp.stacks.empty?
65
+
66
+ resp.stacks.first.outputs.each do |stack_output|
67
+ zero_output = stack_output.output_key.casecmp(output).zero?
68
+ return stack_output.output_value if zero_output
69
+ end
70
+
71
+ raise "#{stack.upcase} stack does not have a(n) '#{output}' output!"
72
+ end
73
+
74
+ # Creates or Updates a CloudFormation stack. Checks to see if the stack
75
+ # already exists and takes the appropriate action. Pauses until a final
76
+ # stack state is reached.
77
+
78
+ # @param stack_name [String] The name of the CloudFormation stack
79
+ # @param stack_parameters [Hash] Parameters to be passed into the stack
80
+ def deploy_stack(stack_name, stack_parameters)
81
+ unless @client.describe_stacks(stack_name: stack_name).stacks.empty?
82
+ puts 'Updating the existing stack'
83
+ @client.update_stack(stack_parameters)
84
+ @client.wait_until(:stack_update_complete, stack_name: stack_name)
85
+ end
86
+ rescue Aws::CloudFormation::Errors::ValidationError => error
87
+ if error.to_s.include? 'No updates are to be performed.'
88
+ puts "Nothing to do."
89
+ elsif error.to_s.include? 'Template error'
90
+ raise error
91
+ else
92
+ puts 'Creating a new stack'
93
+ @client.create_stack(stack_parameters)
94
+ @client.wait_until(:stack_create_complete, stack_name: stack_name)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk'
4
+ require 'crossing'
5
+
6
+ class MinimalPipeline
7
+ # Here is an example of how to use this class to interact with Crossing.
8
+ #
9
+ # ```
10
+ # crossing = MinimalPipeline::Crossing.new
11
+ #
12
+ # # Upload
13
+ # crossing.upload_content('my-config-bucket', 'example.txt', 'foo')
14
+ #
15
+ # # Download
16
+ # content = crossing.download_file('my-config-bucket', 'example.txt')
17
+ # puts content # Outputs 'foo'
18
+ # ```
19
+ #
20
+ # You will need the following environment variables to be present:
21
+ # * `AWS_REGION` or `region`
22
+ # * `keystore_kms_id`
23
+ #
24
+ # For more information on Crossing see https://github.com/stelligent/crossing
25
+ class Crossing
26
+ def initialize
27
+ raise 'You must set env variable AWS_REGION.' if ENV['AWS_REGION'].nil?
28
+ raise '`keystore_kms_id` in environment!' \
29
+ if ENV['keystore_kms_id'].nil?
30
+
31
+ region = ENV['AWS_REGION']
32
+ kms = Aws::KMS::Client.new(region: region)
33
+ s3 = Aws::S3::Encryption::Client.new(kms_key_id: ENV['keystore_kms_id'],
34
+ kms_client: kms,
35
+ region: region)
36
+ @crossing = ::Crossing.new(s3)
37
+ end
38
+
39
+ # Securely uploads a file to an S3 bucket
40
+ #
41
+ # @param config_bucket [String] The name of the S3 bucket
42
+ # @param filename [String] The name of the file to save content to in the
43
+ # bucket
44
+ # @param content [String] The content to store in the file
45
+ def upload_content(config_bucket, filename, content)
46
+ @crossing.put_content(config_bucket, filename, content)
47
+ end
48
+
49
+ # Securely downloads a file from an S3 bucket
50
+ #
51
+ # @param config_bucket [String] The name of the S3 bucket
52
+ # @param filename [String] The name of the file that contains the desired
53
+ # content
54
+ # @return [String] The content that was stored in the file
55
+ def download_file(config_bucket, filename)
56
+ @crossing.get_content(config_bucket, filename)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'docker'
5
+ require 'json'
6
+ require 'securerandom'
7
+
8
+ class MinimalPipeline
9
+ # Here is an example of how to use this class to manage Docker containers.
10
+ #
11
+ # ```
12
+ # docker = MinimalPipeline::Docker.new
13
+ # keystore = MinimalPipeline::Keystore.new
14
+ #
15
+ # deploy_env = ENV['DEPLOY_ENV']
16
+ # docker_repo = keystore.retrieve("#{deploy_env}_EXAMPLE_ECR_REPO")
17
+ # docker_image = "#{docker_repo}/example:latest"
18
+ # docker.build_docker_image(docker_image, 'containers/example')
19
+ # docker.push_docker_image(docker_image)
20
+ # ```
21
+ class Docker
22
+ include Rake::DSL
23
+
24
+ def initialize
25
+ end
26
+
27
+ # Finds the absolute path to a given executable
28
+ #
29
+ # @param cmd [String] The name of the executable to locate
30
+ # @return [String] The absolute path to the executable
31
+ def which(cmd)
32
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
33
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
34
+ exts.each do |ext|
35
+ exe = File.join(path, "#{cmd}#{ext}")
36
+ return exe if File.executable?(exe) && !File.directory?(exe)
37
+ end
38
+ end
39
+ nil
40
+ end
41
+
42
+ # Outputs JSON build output lines as human readible text
43
+ #
44
+ # @param build_output [String] Raw JSON build output line
45
+ def build_output(build_output)
46
+ # Each line of the response is its own JSON structure
47
+ build_output.each_line do |l|
48
+ if (log = JSON.parse(l)) && log.key?('stream')
49
+ $stdout.puts log['stream']
50
+ end
51
+ end
52
+ rescue JSON::ParserError
53
+ $stdout.puts "Bad JSON parse\n"
54
+ $stdout.puts build_output
55
+ end
56
+
57
+ # Logs in to AWS ECR
58
+ def ecr_login
59
+ region = ENV['AWS_REGION'] || ENV['region']
60
+ `$(aws ecr get-login --region #{region})`
61
+ end
62
+
63
+ # Cleans up docker images
64
+ #
65
+ # @param image_id [String] The Docker container ID to delete
66
+ def clean_up_image(image_id)
67
+ image = ::Docker::Image.get(image_id)
68
+ image.remove(force: true)
69
+ end
70
+
71
+ # Builds a docker image from a Dockerfile
72
+ #
73
+ # @param image_id [String] The name of the docker image
74
+ # @param dir [String] The path to the Dockerfile
75
+ # @param build_args [Hash] Additional build args to pass to Docker
76
+ # @param timeout [Integer] The Docker build timeout
77
+ def build_docker_image(image_id, dir = '.', build_args: {}, timeout: 600)
78
+ %w[HTTP_PROXY HTTPS_PROXY NO_PROXY http_proxy https_proxy
79
+ no_proxy].each do |arg|
80
+ build_args[arg] = ENV[arg] if ENV[arg]
81
+ end
82
+
83
+ args = {
84
+ 'nocache' => 'true',
85
+ 'pull' => 'true',
86
+ 't' => image_id,
87
+ 'buildargs' => JSON.dump(build_args)
88
+ }
89
+ puts "Build args: #{args.inspect}"
90
+ ::Docker.options[:read_timeout] = timeout
91
+ ::Docker::Image.build_from_dir(dir, args) { |v| build_output(v) }
92
+ end
93
+
94
+ # Pushes a docker image from local to AWS ECR.
95
+ # This handles login, the upload, and local cleanup of the container
96
+ #
97
+ # @param image_id [String] The name of the docker image
98
+ def push_docker_image(image_id)
99
+ ecr_login
100
+ docker_bin = which('docker')
101
+ raise "docker_push: no docker binary: #{image_id}" unless docker_bin
102
+ stdout, stderr, status = Open3.capture3(docker_bin, 'push', image_id)
103
+ raise "stdout: #{stdout}\nstderr: #{stderr}\nstatus: #{status}" \
104
+ unless status.exitstatus.zero?
105
+ clean_up_image(image_id)
106
+ end
107
+
108
+ # List all containers in the `containers` directory
109
+ # @return [Array] List of container names
110
+ def list_containers
111
+ containers = Dir.glob('containers/*')
112
+ # Grab container name from containers/name/Dockerfile
113
+ containers.map{ |container| container.split('/')[1] }
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'keystore'
4
+ require 'aws-sdk'
5
+
6
+ class MinimalPipeline
7
+ # Here is an example of how to use this class to interact with the Keystore.
8
+ #
9
+ # ```
10
+ # keystore = MinimalPipeline::Keystore.new
11
+ #
12
+ # # Store
13
+ # keystore.store('EXAMPLE_KEY', 'foo')
14
+ #
15
+ # # Retrieve
16
+ # content = keystore.retrieve('EXAMPLE_KEY')
17
+ # puts content # Outputs 'foo'
18
+ # ```
19
+ #
20
+ # You will need the following environment variables to be present:
21
+ # * `AWS_REGION` or `region`
22
+ # * `keystore_table`
23
+ # * `keystore_kms_id`
24
+ #
25
+ # For more information on the Keystore please see
26
+ # https://github.com/stelligent/keystore
27
+ class Keystore
28
+ # Initializes a `Keystore` client
29
+ # Requires environment variables `AWS_REGION` or `region` to be set.
30
+ # Also requires `keystore_kms_id` and `keystore_table`
31
+ def initialize
32
+ raise 'You must set env variable AWS_REGION.' if ENV['AWS_REGION'].nil?
33
+ raise 'Missing `keystore_table` or `keystore_kms_id` in environment!' \
34
+ if ENV['keystore_table'].nil? || ENV['keystore_kms_id'].nil?
35
+
36
+ region = ENV['AWS_REGION']
37
+ kms = Aws::KMS::Client.new(region: region)
38
+ dynamo = Aws::DynamoDB::Client.new(region: region)
39
+ @keystore = ::Keystore.new(dynamo: dynamo,
40
+ table_name: ENV['keystore_table'],
41
+ kms: kms,
42
+ key_id: ENV['keystore_kms_id'])
43
+ end
44
+
45
+ # Retrieves a value from the Keystore
46
+ #
47
+ # @param keyname [String] The name of the Keystore key
48
+ # @return [String] The value stored in the Keystore for the given key
49
+ def retrieve(keyname)
50
+ @keystore.retrieve(key: keyname)
51
+ end
52
+
53
+ # Stores a value in the Keystore
54
+ #
55
+ # @param keyname [String] The name of the Keystore key
56
+ # @param value [String] The value to store for the given key
57
+ def store(keyname, value)
58
+ @keystore.store(key: keyname, value: value)
59
+ end
60
+ end
61
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: minimal_pipeline
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Mayowa Aladeojebi
8
+ - Jesse Adams
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2018-08-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: aws-sdk
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '2'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '2'
28
+ - !ruby/object:Gem::Dependency
29
+ name: crossing
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - '='
33
+ - !ruby/object:Gem::Version
34
+ version: 0.1.8
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - '='
40
+ - !ruby/object:Gem::Version
41
+ version: 0.1.8
42
+ - !ruby/object:Gem::Dependency
43
+ name: docker-api
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - '='
47
+ - !ruby/object:Gem::Version
48
+ version: 1.34.2
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - '='
54
+ - !ruby/object:Gem::Version
55
+ version: 1.34.2
56
+ - !ruby/object:Gem::Dependency
57
+ name: keystore
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '='
61
+ - !ruby/object:Gem::Version
62
+ version: 0.1.7
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '='
68
+ - !ruby/object:Gem::Version
69
+ version: 0.1.7
70
+ - !ruby/object:Gem::Dependency
71
+ name: rake
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rubocop
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - '='
89
+ - !ruby/object:Gem::Version
90
+ version: 0.55.0
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - '='
96
+ - !ruby/object:Gem::Version
97
+ version: 0.55.0
98
+ description: Helper gem to orchestrate pipeline tasks
99
+ email:
100
+ - mayowa.aladeojebi@stelligent.com
101
+ - jesse.adams@stelligent.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - lib/minimal_pipeline.rb
107
+ - lib/minimal_pipeline/cloudformation.rb
108
+ - lib/minimal_pipeline/crossing.rb
109
+ - lib/minimal_pipeline/docker.rb
110
+ - lib/minimal_pipeline/keystore.rb
111
+ homepage: https://github.com/stelligent/minimal-pipeline-gem
112
+ licenses:
113
+ - 0BSD
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.7.6
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: Helper gem to manage pipeline tasks
135
+ test_files: []