openstax_aws 1.0.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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.travis.yml +12 -0
  4. data/CHANGELOG.md +11 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +120 -0
  7. data/LICENSE.txt +1 -0
  8. data/README.md +927 -0
  9. data/Rakefile +6 -0
  10. data/TODO.md +1 -0
  11. data/assets/secrets_sequence_diagram.png +0 -0
  12. data/bin/console +14 -0
  13. data/bin/create_development_environment +26 -0
  14. data/bin/get_latest_ubuntu_ami +31 -0
  15. data/bin/setup +8 -0
  16. data/bin/templates/aws_ruby_development.yml +221 -0
  17. data/examples/deployment.rb +90 -0
  18. data/ideas.md +15 -0
  19. data/lib/openstax/aws/auto_scaling_group.rb +28 -0
  20. data/lib/openstax/aws/auto_scaling_instance.rb +96 -0
  21. data/lib/openstax/aws/build_image_command_1.rb +53 -0
  22. data/lib/openstax/aws/change_set.rb +100 -0
  23. data/lib/openstax/aws/deployment_base.rb +372 -0
  24. data/lib/openstax/aws/distribution.rb +56 -0
  25. data/lib/openstax/aws/ec2_instance_data.rb +18 -0
  26. data/lib/openstax/aws/extensions.rb +19 -0
  27. data/lib/openstax/aws/git_helper.rb +18 -0
  28. data/lib/openstax/aws/image.rb +34 -0
  29. data/lib/openstax/aws/msk_cluster.rb +19 -0
  30. data/lib/openstax/aws/packer_1_2_5.rb +63 -0
  31. data/lib/openstax/aws/packer_1_4_1.rb +72 -0
  32. data/lib/openstax/aws/packer_factory.rb +25 -0
  33. data/lib/openstax/aws/rds_instance.rb +25 -0
  34. data/lib/openstax/aws/s3_text_file.rb +50 -0
  35. data/lib/openstax/aws/sam_stack.rb +85 -0
  36. data/lib/openstax/aws/secrets.rb +302 -0
  37. data/lib/openstax/aws/secrets_factory.rb +126 -0
  38. data/lib/openstax/aws/secrets_set.rb +21 -0
  39. data/lib/openstax/aws/secrets_specification.rb +68 -0
  40. data/lib/openstax/aws/stack.rb +465 -0
  41. data/lib/openstax/aws/stack_event.rb +28 -0
  42. data/lib/openstax/aws/stack_factory.rb +153 -0
  43. data/lib/openstax/aws/stack_parameters.rb +19 -0
  44. data/lib/openstax/aws/stack_status.rb +125 -0
  45. data/lib/openstax/aws/system.rb +21 -0
  46. data/lib/openstax/aws/tag.rb +31 -0
  47. data/lib/openstax/aws/template.rb +129 -0
  48. data/lib/openstax/aws/version.rb +5 -0
  49. data/lib/openstax/aws/wait_message.rb +20 -0
  50. data/lib/openstax_aws.rb +154 -0
  51. data/openstax_aws.gemspec +58 -0
  52. metadata +350 -0
@@ -0,0 +1,56 @@
1
+ module OpenStax::Aws
2
+ class Distribution
3
+
4
+ attr_reader :id, :region, :logger
5
+
6
+ def initialize(id:, region:, logger: nil)
7
+ @id = id
8
+ @region = region
9
+ @logger ||= Logger.new(IO::NULL)
10
+ end
11
+
12
+ def invalidate(paths:, wait: false)
13
+ paths = [paths].flatten.compact
14
+
15
+ resp = client.create_invalidation({
16
+ distribution_id: id,
17
+ invalidation_batch: {
18
+ paths: {
19
+ quantity: paths.length,
20
+ items: paths,
21
+ },
22
+ caller_reference: "#{paths.join(' ')} #{Time.now.utc.strftime("%Y%m%d-%H%M%S")}"
23
+ },
24
+ })
25
+
26
+ invalidation_id = resp.invalidation.id
27
+
28
+ logger.info("Created invalidation #{invalidation_id} for paths #{paths.join(', ')}.")
29
+
30
+ wait_message = OpenStax::Aws::WaitMessage.new(
31
+ message: "Waiting for invalidation #{invalidation_id} to be completed"
32
+ )
33
+
34
+ begin
35
+ Aws::CloudFront::Waiters::InvalidationCompleted.new(
36
+ client: client,
37
+ before_attempt: ->(*) { wait_message.say_it }
38
+ ).wait(
39
+ distribution_id: id,
40
+ id: invalidation_id
41
+ )
42
+ rescue Aws::Waiters::Errors::WaiterFailed => error
43
+ logger.error "Waiting failed: #{error.message}"
44
+ raise
45
+ end
46
+ logger.info "Invalidation #{invalidation_id} has been completed!"
47
+ end
48
+
49
+ protected
50
+
51
+ def client
52
+ @cloudfront_client ||= ::Aws::CloudFront::Client.new(region: region)
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,18 @@
1
+ module OpenStax::Aws
2
+ class Ec2InstanceData
3
+
4
+ def self.get(key)
5
+ endpoint = "http://169.254.169.254/latest/#{key}"
6
+ Net::HTTP.get(URI.parse(endpoint))
7
+ end
8
+
9
+ def self.instance_id
10
+ get("meta-data/instance-id")
11
+ end
12
+
13
+ def self.region
14
+ get("meta-data/placement/availability-zone")[0..-2]
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ class String
2
+ def blank?
3
+ respond_to?(:empty?) ? !!empty? : !self
4
+ end
5
+
6
+ def present?
7
+ !blank?
8
+ end
9
+ end
10
+
11
+ class NilClass
12
+ def blank?
13
+ true
14
+ end
15
+
16
+ def present?
17
+ false
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ require 'git'
2
+ require 'open-uri'
3
+
4
+ module OpenStax::Aws
5
+ module GitHelper
6
+
7
+ def self.sha_for_branch_name(org_slash_repo:, branch:)
8
+ ::Git.ls_remote("https://github.com/#{org_slash_repo}")["branches"][branch][:sha]
9
+ end
10
+
11
+ def self.file_content_at_sha(org_slash_repo:, sha:, path:)
12
+ location = "https://raw.githubusercontent.com/#{org_slash_repo}/#{sha}/#{path}"
13
+ file = open(location)
14
+ file.read
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,34 @@
1
+ module OpenStax::Aws
2
+ class Image
3
+
4
+ attr_reader :aws_image
5
+
6
+ def initialize(id: nil, region: nil, aws_image: nil)
7
+ if aws_image
8
+ @aws_image = aws_image
9
+ else
10
+ if id.nil? || region.nil?
11
+ raise ArgumentError, "`id` and `region` must be provided"
12
+ end
13
+
14
+ @aws_image = Aws::EC2::Image.new(id, region: region)
15
+ end
16
+ end
17
+
18
+ def get_tag(key:)
19
+ tag = aws_image.tags.find{|tag| tag.key == key}
20
+ raise "No tag with key #{key} on AMI #{aws_image.image_id}" if tag.nil?
21
+ tag.value
22
+ end
23
+
24
+ def sha
25
+ get_tag(key: "sha")
26
+ end
27
+
28
+ def self.find_by_sha(sha:, region:)
29
+ Aws::EC2::Client.new(region: region).describe_images({
30
+ filters: [{name: "tag:sha", values: [sha]}]
31
+ }).images.map{|aws_image| new(aws_image: aws_image)}
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ module OpenStax::Aws
2
+ class MskCluster
3
+
4
+ attr_reader :client, :cluster_arn
5
+
6
+ delegate_missing_to :@client
7
+
8
+ def initialize(cluster_arn:, region:)
9
+ @cluster_arn = cluster_arn
10
+ @client = ::Aws::Kafka::Client.new(region: region)
11
+ end
12
+
13
+ def bootstrap_broker_string
14
+ client.get_bootstrap_brokers(cluster_arn: cluster_arn).bootstrap_broker_string
15
+ end
16
+
17
+ end
18
+ end
19
+
@@ -0,0 +1,63 @@
1
+ require 'open3'
2
+
3
+ module OpenStax::Aws
4
+ class Packer_1_2_5
5
+
6
+ def initialize(absolute_file_path:, dry_run: true)
7
+ @logger = OpenStax::Aws.configuration.logger
8
+ @only = []
9
+ @vars = {}
10
+ @dry_run = dry_run
11
+ @verbose = false
12
+ @debug = false
13
+ @absolute_file_path = absolute_file_path
14
+ end
15
+
16
+ def only(builders)
17
+ @only = [builders].flatten
18
+ end
19
+
20
+ def var(key, value)
21
+ @vars[key.to_s] = value.to_s
22
+ end
23
+
24
+ def verbose!
25
+ @verbose = true
26
+ end
27
+
28
+ def debug!
29
+ @debug = true
30
+ end
31
+
32
+ def command
33
+ cmd = "packer build --only=amazon-ebs"
34
+
35
+ cmd = "#{cmd} --only=#{@only.join(',')}" if !@only.empty?
36
+
37
+ @vars.each do |key, value|
38
+ cmd = "#{cmd} --var '#{key}=#{value}'"
39
+ end
40
+
41
+ cmd = "PACKER_LOG=1 #{cmd}" if @verbose
42
+ cmd = "#{cmd} --debug" if @debug
43
+
44
+ cmd = "#{cmd} #{@absolute_file_path}"
45
+ end
46
+
47
+ def run
48
+ @logger.info("**** DRY RUN ****") if @dry_run
49
+ @logger.info("Running: #{command}")
50
+
51
+ if !@dry_run
52
+ @logger.info("Printing stderr for desired verbosity")
53
+
54
+ Open3.popen2e(command) do |stdin, stdout_err, wait_thr|
55
+ while line=stdout_err.gets do
56
+ puts(line)
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,72 @@
1
+ require 'open3'
2
+
3
+ module OpenStax::Aws
4
+ class Packer_1_4_1
5
+
6
+ def initialize(absolute_file_path:, dry_run: true)
7
+ @logger = OpenStax::Aws.configuration.logger
8
+ @only = []
9
+ @vars = {}
10
+ @dry_run = dry_run
11
+ @verbose = false
12
+ @debug = false
13
+ @absolute_file_path = absolute_file_path
14
+ end
15
+
16
+ def only(builders)
17
+ @only = [builders].flatten
18
+ end
19
+
20
+ def var(key, value)
21
+ @vars[key.to_s] = value.to_s
22
+ end
23
+
24
+ def verbose!
25
+ @verbose = true
26
+ end
27
+
28
+ def debug!
29
+ @debug = true
30
+ end
31
+
32
+ def command
33
+ cmd = "packer build"
34
+
35
+ cmd = "#{cmd} --only=#{@only.join(',')}" if !@only.empty?
36
+
37
+ @vars.each do |key, value|
38
+ cmd = "#{cmd} --var '#{key}=#{value}'"
39
+ end
40
+
41
+ cmd = "PACKER_LOG=1 #{cmd}" if @verbose
42
+ cmd = "#{cmd} --debug" if @debug
43
+
44
+ cmd = "#{cmd} #{@absolute_file_path}"
45
+ end
46
+
47
+ def run
48
+ @logger.info("**** DRY RUN ****") if @dry_run
49
+ @logger.info("Running: #{command}")
50
+
51
+ if !@dry_run
52
+ @logger.info("Printing stderr for desired verbosity")
53
+ ami = ""
54
+
55
+ Open3.popen2e(command) do |stdin, stdout_err, wait_thr|
56
+ while line=stdout_err.gets do
57
+ STDERR.puts(line)
58
+ matchami = line.match(/AMI: (ami-[0-9\-a-z]*)/i)
59
+ ami = matchami.captures[0] if matchami
60
+ end
61
+ end
62
+
63
+ puts ami
64
+ end
65
+ end
66
+
67
+ def to_s
68
+ command.to_s
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,25 @@
1
+ module OpenStax::Aws
2
+ class PackerFactory
3
+
4
+ # There are differences between Packer 1.2 and 1.4. This factory attempts to
5
+ # provide the right version of packer class.
6
+
7
+ def self.new_packer(absolute_file_path:, dry_run: true)
8
+ packer_version = `packer --version`
9
+
10
+ raise "packer is not installed" if packer_version.nil?
11
+
12
+ packer_class =
13
+ case packer_version
14
+ when /^1.2/
15
+ Packer_1_2_5
16
+ else
17
+ Packer_1_4_1
18
+ end
19
+
20
+ @packer = packer_class.new(absolute_file_path: absolute_file_path,
21
+ dry_run: dry_run)
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ module OpenStax::Aws
2
+ class RdsInstance
3
+
4
+ attr_reader :raw
5
+
6
+ delegate_missing_to :@raw
7
+
8
+ def initialize(db_instance_identifier:, region:)
9
+ @raw = Aws::RDS::DBInstance.new(
10
+ db_instance_identifier,
11
+ client: Aws::RDS::Client.new(region: region)
12
+ )
13
+ end
14
+
15
+ def set_master_password(password:)
16
+ raw.modify({
17
+ apply_immediately: true,
18
+ master_user_password: password
19
+ })
20
+
21
+ end
22
+
23
+ end
24
+ end
25
+
@@ -0,0 +1,50 @@
1
+ module OpenStax::Aws
2
+ class S3TextFile
3
+
4
+ attr_reader :bucket_name, :bucket_region, :key
5
+
6
+ def initialize(bucket_name:, bucket_region:, key:)
7
+ raise ArgumentError, "bucket_name cannot be nil" if bucket_name.nil?
8
+ raise ArgumentError, "bucket_region cannot be nil" if bucket_region.nil?
9
+ raise ArgumentError, "key cannot be nil" if key.nil?
10
+
11
+ @bucket_name = bucket_name
12
+ @bucket_region = bucket_region
13
+ @key = key
14
+ end
15
+
16
+ def read
17
+ object.load
18
+ object.get.body.read
19
+ end
20
+
21
+ def get
22
+ object.load
23
+ object.get
24
+ end
25
+
26
+ def write(string_contents:, content_type:'text/plain', cache_control: nil)
27
+ args = {
28
+ body: StringIO.new(string_contents)
29
+ }
30
+
31
+ args[:content_type] = content_type if !content_type.nil?
32
+ args[:cache_control] = cache_control if !cache_control.nil?
33
+
34
+ object.put(**args)
35
+ end
36
+
37
+ def delete
38
+ object.delete
39
+ end
40
+
41
+ def object
42
+ @object ||= Aws::S3::Object.new(
43
+ bucket_name,
44
+ key,
45
+ client: Aws::S3::Client.new(region: bucket_region)
46
+ )
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,85 @@
1
+ module OpenStax::Aws
2
+ class SamStack < Stack
3
+
4
+ attr_reader :build_directory
5
+
6
+ def initialize(id: nil, name:, tags: {},
7
+ region:, enable_termination_protection: false,
8
+ absolute_template_path: nil,
9
+ capabilities: nil, parameter_defaults: {},
10
+ volatile_parameters_block: nil,
11
+ secrets_blocks: [], secrets_context: nil, secrets_namespace: nil,
12
+ shared_secrets_substitutions_block: nil,
13
+ cycle_if_different_parameter: nil,
14
+ build_directory:,
15
+ dry_run: true)
16
+ @build_directory = build_directory
17
+
18
+ super(id: id,
19
+ name: name,
20
+ tags: tags,
21
+ region: region,
22
+ enable_termination_protection: enable_termination_protection,
23
+ absolute_template_path: absolute_template_path,
24
+ capabilities: capabilities,
25
+ parameter_defaults: parameter_defaults,
26
+ volatile_parameters_block: volatile_parameters_block,
27
+ secrets_blocks: secrets_blocks,
28
+ secrets_context: secrets_context,
29
+ secrets_namespace: secrets_namespace,
30
+ shared_secrets_substitutions_block: shared_secrets_substitutions_block,
31
+ cycle_if_different_parameter: cycle_if_different_parameter,
32
+ dry_run: dry_run)
33
+ end
34
+
35
+ def build
36
+ # SAM doesn't have an API or SDK - we have to make calls to its CLI
37
+ command = "sam build -t #{absolute_template_path} -b #{build_directory}"
38
+ System.call(command, logger: logger, dry_run: dry_run)
39
+ end
40
+
41
+ def deploy(bucket_name:, params: {})
42
+ # SAM doesn't have an API or SDK - we have to make calls to its CLI
43
+
44
+ check_for_required_tags
45
+
46
+ params = parameter_defaults.merge(params)
47
+
48
+ command = "sam deploy" \
49
+ " --template-file #{build_directory}/template.yaml" \
50
+ " --capabilities CAPABILITY_IAM" \
51
+ " --s3-bucket #{bucket_name}" \
52
+ " --s3-prefix #{name}" \
53
+ " --stack-name #{name}"
54
+
55
+ if params.any?
56
+ command += " --parameter-overrides #{self.class.format_hash_as_cli_stack_parameters(params)}"
57
+ end
58
+
59
+ if tags.any?
60
+ command += " --tags #{self.class.format_tags_as_cli_tags(tags)}"
61
+ end
62
+
63
+ System.call(command, logger: logger, dry_run: dry_run)
64
+
65
+ if enable_termination_protection
66
+ client.update_termination_protection({
67
+ enable_termination_protection: true,
68
+ stack_name: name
69
+ })
70
+ end
71
+ end
72
+
73
+ def self.format_hash_as_cli_stack_parameters(params={})
74
+ params.map{|key, value| "ParameterKey=#{key.to_s.camelcase},ParameterValue=#{value}"}
75
+ .map{|item| "'" + item + "'"}
76
+ .join(" ")
77
+ end
78
+
79
+ def self.format_tags_as_cli_tags(tags=[])
80
+ tags.map{|tag| "'#{tag.key}=#{tag.value}'"}
81
+ .join(" ")
82
+ end
83
+
84
+ end
85
+ end