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,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/TODO.md ADDED
@@ -0,0 +1 @@
1
+ * "parameters" as template arguments and "parameters" as secrets in the parameter store is confusing?
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "openstax_aws"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "openstax_aws"
5
+
6
+ REGION = 'us-east-2'
7
+
8
+ OpenStax::Aws.configure do |config|
9
+ config.cfn_template_bucket_name = "openstax-sandbox-cfn-templates"
10
+ config.cfn_template_bucket_region = "us-west-2"
11
+ config.stack_waiter_delay = 10
12
+ config.fixed_s3_template_folder = "spec-templates"
13
+ end
14
+
15
+ stack = OpenStax::Aws::Stack.new({
16
+ name: "aws-ruby-development",
17
+ region: REGION,
18
+ absolute_template_path: File.join(__dir__, "templates/aws_ruby_development.yml"),
19
+ dry_run: false,
20
+ })
21
+
22
+ stack.create(wait: true, params: {
23
+ unique_name: "aws-ruby-dev-env",
24
+ image_id: "ami-0510c89f1a2691cf2", # See bin/get_latest_ubuntu_ami
25
+ key_name: "openstax-sandbox-kp"
26
+ })
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Returns the latest Ubuntu AMI from Canonical. Change the AMI name
4
+ # below to select a different Ubuntu version. Example run:
5
+ #
6
+ # $> ./bin/get_latest_ubuntu_ami us-east-2
7
+
8
+ require "bundler/setup"
9
+ require "aws-sdk-ec2"
10
+
11
+ client = Aws::EC2::Client.new(region: ARGV[0])
12
+
13
+ result = client.describe_images({
14
+ owners: ['099720109477'],
15
+ filters: [
16
+ {
17
+ name: "name",
18
+ values: ["ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*"]
19
+ },
20
+ {
21
+ name: "virtualization-type",
22
+ values: ["hvm"]
23
+ },
24
+ {
25
+ name: "root-device-type",
26
+ values: ["ebs"]
27
+ }
28
+ ]
29
+ })
30
+
31
+ puts result.images.sort_by(&:name).last.image_id
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,221 @@
1
+ AWSTemplateFormatVersion: '2010-09-09'
2
+
3
+ Description: aws-ruby development
4
+
5
+ Parameters:
6
+
7
+ UniqueName:
8
+ Type: String
9
+
10
+ ImageId:
11
+ Description: AMI to use in the web server LaunchConfiguration
12
+ Type: AWS::EC2::Image::Id
13
+ ConstraintDescription: must be the id of a valid AMI
14
+
15
+ KeyName:
16
+ Description: Name of an existing EC2 KeyPair to enable SSH access to the instances
17
+ Type: AWS::EC2::KeyPair::KeyName
18
+ ConstraintDescription: must be the name of an existing EC2 KeyPair
19
+
20
+ Resources:
21
+
22
+ Vpc:
23
+ Type: AWS::EC2::VPC
24
+ Properties:
25
+ CidrBlock: 10.0.0.0/16
26
+ EnableDnsSupport: 'true'
27
+ EnableDnsHostnames: 'true'
28
+ Tags:
29
+ - Key: Name
30
+ Value: !Sub '${AWS::StackName}-vpc'
31
+ - Key: Project
32
+ Value: Unified
33
+ - Key: Application
34
+ Value: Search
35
+
36
+ Subnet1:
37
+ Type: AWS::EC2::Subnet
38
+ Properties:
39
+ VpcId: !Ref 'Vpc'
40
+ CidrBlock: 10.0.1.0/24
41
+ MapPublicIpOnLaunch: 'true'
42
+ AvailabilityZone: !Select
43
+ - '0'
44
+ - !GetAZs
45
+ Ref: AWS::Region
46
+ Tags:
47
+ - Key: Name
48
+ Value: !Sub '${AWS::StackName}-sn1'
49
+
50
+ Subnet2:
51
+ Type: AWS::EC2::Subnet
52
+ Properties:
53
+ VpcId: !Ref 'Vpc'
54
+ CidrBlock: 10.0.2.0/24
55
+ MapPublicIpOnLaunch: 'true'
56
+ AvailabilityZone: !Select
57
+ - '1'
58
+ - !GetAZs
59
+ Ref: AWS::Region
60
+ Tags:
61
+ - Key: Name
62
+ Value: !Sub '${AWS::StackName}-sn2'
63
+
64
+ Subnet3:
65
+ Type: AWS::EC2::Subnet
66
+ Properties:
67
+ VpcId: !Ref 'Vpc'
68
+ CidrBlock: 10.0.3.0/24
69
+ MapPublicIpOnLaunch: 'true'
70
+ AvailabilityZone: !Select
71
+ - '2'
72
+ - !GetAZs
73
+ Ref: AWS::Region
74
+ Tags:
75
+ - Key: Name
76
+ Value: !Sub '${AWS::StackName}-sn3'
77
+
78
+ Gateway:
79
+ Type: AWS::EC2::InternetGateway
80
+ Properties:
81
+ Tags:
82
+ - Key: Name
83
+ Value: !Sub '${AWS::StackName}-igw'
84
+
85
+ GatewayAttachment:
86
+ Type: AWS::EC2::VPCGatewayAttachment
87
+ Properties:
88
+ InternetGatewayId: !Ref 'Gateway'
89
+ VpcId: !Ref 'Vpc'
90
+
91
+ RouteTable:
92
+ Type: AWS::EC2::RouteTable
93
+ Properties:
94
+ VpcId: !Ref 'Vpc'
95
+ Tags:
96
+ - Key: Name
97
+ Value: !Sub '${AWS::StackName}-rt'
98
+
99
+ InetTrafficRoute:
100
+ Type: AWS::EC2::Route
101
+ Properties:
102
+ DestinationCidrBlock: '0.0.0.0/0'
103
+ GatewayId: !Ref 'Gateway'
104
+ RouteTableId: !Ref 'RouteTable'
105
+
106
+ Subnet1RtAssoc:
107
+ Type: AWS::EC2::SubnetRouteTableAssociation
108
+ Properties:
109
+ RouteTableId: !Ref 'RouteTable'
110
+ SubnetId: !Ref 'Subnet1'
111
+
112
+ Subnet2RtAssoc:
113
+ Type: AWS::EC2::SubnetRouteTableAssociation
114
+ Properties:
115
+ RouteTableId: !Ref 'RouteTable'
116
+ SubnetId: !Ref 'Subnet2'
117
+
118
+ Subnet3RtAssoc:
119
+ Type: AWS::EC2::SubnetRouteTableAssociation
120
+ Properties:
121
+ RouteTableId: !Ref 'RouteTable'
122
+ SubnetId: !Ref 'Subnet3'
123
+
124
+ ServerSecGrp:
125
+ Type: AWS::EC2::SecurityGroup
126
+ Properties:
127
+ GroupDescription: aws-ruby development server
128
+ VpcId: !Ref 'Vpc'
129
+ Tags:
130
+ - Key: Name
131
+ Value: !Sub '${AWS::StackName}-dev-sg'
132
+ SecurityGroupEgress:
133
+ - Description: all traffic allowed on all ports to anywhere
134
+ IpProtocol: '-1'
135
+ FromPort: '-1'
136
+ ToPort: '-1'
137
+ CidrIp: '0.0.0.0/0'
138
+ SecurityGroupIngress:
139
+ - Description: SSH on port 22 from bastion2 (IPv4)
140
+ IpProtocol: '6'
141
+ FromPort: '22'
142
+ ToPort: '22'
143
+ CidrIp: '128.42.169.79/32'
144
+
145
+ AsgInstanceRole:
146
+ Type: AWS::IAM::Role
147
+ Properties:
148
+ AssumeRolePolicyDocument:
149
+ Version: '2012-10-17'
150
+ Statement:
151
+ - Effect: Allow
152
+ Principal:
153
+ Service:
154
+ - ec2.amazonaws.com
155
+ Action: sts:AssumeRole
156
+ Policies:
157
+ - PolicyName: !Sub '${UniqueName}-autoscaling'
158
+ PolicyDocument:
159
+ Version: '2012-10-17'
160
+ Statement:
161
+ - Effect: Allow
162
+ Action:
163
+ - autoscaling:DescribeAutoScalingInstances
164
+ Resource: "*"
165
+
166
+ AsgInstanceProfile:
167
+ Type: AWS::IAM::InstanceProfile
168
+ Properties:
169
+ Roles:
170
+ - !Ref 'AsgInstanceRole'
171
+
172
+ Lc:
173
+ Type: AWS::AutoScaling::LaunchConfiguration
174
+ Properties:
175
+ ImageId: !Ref 'ImageId'
176
+ InstanceType: t3.nano
177
+ KeyName: !Ref 'KeyName'
178
+ SecurityGroups:
179
+ - !Ref 'ServerSecGrp'
180
+ SpotPrice: 0.0052 # set max at on-demand pricing
181
+ IamInstanceProfile: !Ref 'AsgInstanceProfile'
182
+ UserData:
183
+ Fn::Base64:
184
+ !Sub |
185
+ #!/bin/bash -x
186
+
187
+ cat >/home/ubuntu/install.sh <<CONTENT
188
+ #!/bin/bash
189
+
190
+ sudo apt update
191
+ sudo apt install ruby-full -y
192
+ sudo apt install git-core -y
193
+ sudo apt install build-essential -y
194
+ sudo apt install emacs-nox -y
195
+ sudo gem install bundler
196
+ git config --global core.editor "vim"
197
+
198
+ CONTENT
199
+
200
+ /usr/local/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource \
201
+ Asg --region ${AWS::Region}
202
+
203
+ Asg:
204
+ Type: AWS::AutoScaling::AutoScalingGroup
205
+ Properties:
206
+ AutoScalingGroupName: !Sub "${UniqueName}-asg"
207
+ LaunchConfigurationName: !Ref 'Lc'
208
+ VPCZoneIdentifier:
209
+ - !Ref 'Subnet1'
210
+ - !Ref 'Subnet2'
211
+ - !Ref 'Subnet3'
212
+ DesiredCapacity: 1
213
+ MinSize: 0
214
+ MaxSize: 2
215
+ HealthCheckType: EC2
216
+ HealthCheckGracePeriod: 30
217
+ CreationPolicy:
218
+ ResourceSignal:
219
+ Count: 0
220
+ Timeout: PT5M
221
+
@@ -0,0 +1,90 @@
1
+ require 'byebug'
2
+
3
+ module OpenStax::Aws::SomeApp
4
+ class Deployment < OpenStax::Aws::DeploymentBase
5
+
6
+ stack :network
7
+ stack :redis
8
+ stack :app do
9
+ parameter_defaults do
10
+ web_server_desired_capacity '1'
11
+ secrets_namespace { secrets_namespace }
12
+ end
13
+ volatile_parameters do
14
+ web_server_desired_capacity { resource("Asg").desired_capacity }
15
+ end
16
+ end
17
+
18
+ def initialize(env_name:, region:, dry_run: true)
19
+ super(env_name: env_name, region: region, name: "some_name", dry_run: dry_run)
20
+ end
21
+
22
+ def create(app_image_id:, sha: nil)
23
+ network_stack.create
24
+ redis_stack.create
25
+
26
+ network_stack.wait_for_stack_creation
27
+ redis_stack.wait_for_stack_creation
28
+
29
+ create_parameters(deployed_app_sha: sha || image_sha(app_image_id))
30
+
31
+ app_stack.create(params: {
32
+ branch_name_or_sha: sha || "",
33
+ web_server_image_id: app_image_id,
34
+ })
35
+ end
36
+
37
+ def create_parameters(deployed_app_sha:)
38
+ redis_host = redis_stack.output_value(key: "ElastiCacheAddress")
39
+
40
+ parameters.create(
41
+ specifications: OpenStax::Aws::ParametersSpecification.from_git(
42
+ org_slash_repo: "openstax/some_app_repo",
43
+ sha: deployed_app_sha,
44
+ path: 'config/secrets.yml.example',
45
+ format: :yml,
46
+ top_key: :production
47
+ ),
48
+ substitutions: {
49
+ env_name: env_name,
50
+ redis_url: "redis://#{redis_host}:6379/0"
51
+ }
52
+ )
53
+ end
54
+
55
+ def delete
56
+ app_stack.delete
57
+ redis_stack.delete
58
+
59
+ app_stack.wait_for_stack_deletion
60
+ redis_stack.wait_for_stack_deletion
61
+
62
+ parameters.delete
63
+
64
+ network_stack.delete
65
+ end
66
+
67
+ def update_app_image(new_app_image_id:, dry_run: true)
68
+ app_stack.apply_change_set(params: {
69
+ web_server_image_id: new_app_image_id
70
+ })
71
+ end
72
+
73
+ protected
74
+
75
+ # A good way to handle deployment-wide stack parameter defaults
76
+ def parameter_default(parameter_name)
77
+ case parameter_name
78
+ when "HostedZoneName"
79
+ "something.domain.com"
80
+ when "KeyName"
81
+ "my-key-pair-on-aws"
82
+ end
83
+ end
84
+
85
+ def secrets_namespace
86
+ 'some_name'
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,15 @@
1
+ ### Better declaration of stacks
2
+
3
+ ```ruby
4
+ class MyDeployment < OpenStax::Aws::DeploymentBase
5
+ template_directory __dir__, "../../../cfn/search/"
6
+
7
+ # makes an attr_reader `elasticsearch_stack`
8
+ stack :elasticsearch, # can infer stack name
9
+ template_path: "elasticsearch.yml",
10
+ capabilities: [:iam],
11
+ default_parameters: -> {
12
+ env_name: env_name # could even have multiple passes of defaults, some set from DeploymentBase (e.g. env_name)
13
+ }
14
+ end
15
+ ```
@@ -0,0 +1,28 @@
1
+ module OpenStax::Aws
2
+ class AutoScalingGroup
3
+ attr_reader :raw_asg
4
+
5
+ delegate_missing_to :@raw_asg
6
+
7
+ def initialize(name:, region:)
8
+ @raw_asg = Aws::AutoScaling::AutoScalingGroup.new(
9
+ name: name,
10
+ client: Aws::AutoScaling::Client.new(region: region)
11
+ )
12
+ end
13
+
14
+ def increase_desired_capacity(by:)
15
+ # take the smaller of max size or desired+by (or this call raises an exception)
16
+ increase_to = [raw_asg.max_size, raw_asg.desired_capacity + by].min
17
+
18
+ raw_asg.set_desired_capacity(
19
+ {
20
+ desired_capacity: increase_to
21
+ })
22
+ end
23
+
24
+ def desired_capacity
25
+ raw_asg.desired_capacity
26
+ end
27
+ end
28
+ end