hashicorptools 0.2.3 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d40948d480fbe605a1cdd463dd25f016b9a8caab0bd7b1ee3c85a1f72065b97f
4
- data.tar.gz: 2aff29bee1f1f7bd76b7e0685295b3d5acb173f4a01f362bc843825ac08a6230
3
+ metadata.gz: 1dfcc2c084abef69cad0b5b5860982b1389e399e23e539a979eb7fd32dd245e9
4
+ data.tar.gz: b5d51aa37497b9d61f6d7c0b7e1bcfc0e76fb6bee9857eb51e02ba2ddf8df350
5
5
  SHA512:
6
- metadata.gz: 9db81bb211352e0a183432e031b2b7e596e5ba1005d9e662ab18c46476ce96430b258af6950a2dc8b92d03b87f83d3bf18a550b3ba8c4e908669a1fa98ef20da
7
- data.tar.gz: f7442712eced0611f8695b9acc7186abf4950485f3f48c430d779fa6a1f9980cb9dc552029e74c583726783b8b848df72c020205c94b80c76a03ae0cefeef567
6
+ metadata.gz: bad4f2253923af3acadf6c59846834a07bf8905dddbd8ccf1ca41920c98544dde39592c936b22f07d77763c86382e82a3983502a15c515a4b29b4b76bc90b4b6
7
+ data.tar.gz: 467388e24b4ea0f583dd1536834f21ed5e9c820feaf19f3347d0002b3106f91481886d6bd44549978c5bf652ceebf1bf116f5193304b4881a5c95abb6fb39252
@@ -1 +1 @@
1
- ruby-2.5.3
1
+ ruby-2.6.6
@@ -1,3 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
- - "2.2.5"
3
+ - "2.6.6"
4
+ # TravisCI uses Bundler 1 by default, but we want Bundler 2
5
+ # https://docs.travis-ci.com/user/languages/ruby/#bundler-20
6
+ before_install:
7
+ - gem install bundler
data/Gemfile CHANGED
@@ -1,21 +1,16 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- # try to slowly migrate to v2 of the aws api
4
3
  gem 'aws-sdk', '~> 2'
5
-
6
- gem 'dynect_rest', '= 0.4.6'
7
-
8
- gem 'aws-sdk-v1', '~> 1.67'
9
4
  gem 'dotenv', '~> 2.2', '>= 2.2.1'
10
- gem 'thor', '= 0.20.0'
11
- gem 'activesupport', '~> 5.1', '>= 5.1.4'
12
- gem 'byebug', '~> 10.0', '>= 10.0.2'
13
- gem 'git', '~> 1.3'
5
+ gem 'thor', '>= 0.20.0'
6
+ gem 'activesupport', '>= 5.1.4'
7
+ gem 'byebug', '>= 10.0.2'
8
+ gem 'git', '> 1.3'
14
9
 
15
10
  group :development do
16
- gem 'rspec', '~> 3.7'
17
- gem 'rdoc', '~> 3.12'
18
- gem 'bundler', '~> 1.0'
19
- gem 'juwelier', '~> 2.4', '>= 2.4.7'
11
+ gem 'rspec', '> 3.7'
12
+ gem 'rdoc', '> 3.12'
13
+ gem 'bundler', '> 2.0'
14
+ gem 'juwelier', git: 'https://github.com/flajann2/juwelier.git'
20
15
  gem 'simplecov', '>= 0'
21
16
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.3
1
+ 1.0.0
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: hashicorptools 0.2.3 ruby lib
5
+ # stub: hashicorptools 1.0.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "hashicorptools".freeze
9
- s.version = "0.2.3"
9
+ s.version = "1.0.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Nathan Woodhull".freeze]
14
- s.date = "2018-12-03"
14
+ s.date = "2020-07-30"
15
15
  s.description = "Wrappers for terraform and packer".freeze
16
16
  s.email = "systems@controlshiftlabs.com".freeze
17
17
  s.executables = ["ec2_host".freeze]
@@ -37,62 +37,43 @@ Gem::Specification.new do |s|
37
37
  "lib/hashicorptools/ec2_utilities.rb",
38
38
  "lib/hashicorptools/host.rb",
39
39
  "lib/hashicorptools/packer.rb",
40
- "lib/hashicorptools/terraform.rb",
41
40
  "lib/hashicorptools/update_launch_configuration.rb",
42
41
  "lib/hashicorptools/variables.rb",
43
- "spec/hashicorptools_spec.rb",
42
+ "spec/ec2_utilities_spec.rb",
44
43
  "spec/spec_helper.rb"
45
44
  ]
46
45
  s.homepage = "http://github.com/woodhull/hashicorptools".freeze
47
46
  s.licenses = ["MIT".freeze]
48
- s.rubygems_version = "2.7.6".freeze
47
+ s.rubygems_version = "3.1.2".freeze
49
48
  s.summary = "Wrappers for terraform and packer".freeze
50
49
 
51
50
  if s.respond_to? :specification_version then
52
51
  s.specification_version = 4
52
+ end
53
53
 
54
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
55
- s.add_runtime_dependency(%q<aws-sdk>.freeze, ["~> 2"])
56
- s.add_runtime_dependency(%q<dynect_rest>.freeze, ["= 0.4.6"])
57
- s.add_runtime_dependency(%q<aws-sdk-v1>.freeze, ["~> 1.67"])
58
- s.add_runtime_dependency(%q<dotenv>.freeze, [">= 2.2.1", "~> 2.2"])
59
- s.add_runtime_dependency(%q<thor>.freeze, ["= 0.20.0"])
60
- s.add_runtime_dependency(%q<activesupport>.freeze, [">= 5.1.4", "~> 5.1"])
61
- s.add_runtime_dependency(%q<byebug>.freeze, [">= 10.0.2", "~> 10.0"])
62
- s.add_runtime_dependency(%q<git>.freeze, ["~> 1.3"])
63
- s.add_development_dependency(%q<rspec>.freeze, ["~> 3.7"])
64
- s.add_development_dependency(%q<rdoc>.freeze, ["~> 3.12"])
65
- s.add_development_dependency(%q<bundler>.freeze, ["~> 1.0"])
66
- s.add_development_dependency(%q<juwelier>.freeze, [">= 2.4.7", "~> 2.4"])
67
- s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
68
- else
69
- s.add_dependency(%q<aws-sdk>.freeze, ["~> 2"])
70
- s.add_dependency(%q<dynect_rest>.freeze, ["= 0.4.6"])
71
- s.add_dependency(%q<aws-sdk-v1>.freeze, ["~> 1.67"])
72
- s.add_dependency(%q<dotenv>.freeze, [">= 2.2.1", "~> 2.2"])
73
- s.add_dependency(%q<thor>.freeze, ["= 0.20.0"])
74
- s.add_dependency(%q<activesupport>.freeze, [">= 5.1.4", "~> 5.1"])
75
- s.add_dependency(%q<byebug>.freeze, [">= 10.0.2", "~> 10.0"])
76
- s.add_dependency(%q<git>.freeze, ["~> 1.3"])
77
- s.add_dependency(%q<rspec>.freeze, ["~> 3.7"])
78
- s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
79
- s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
80
- s.add_dependency(%q<juwelier>.freeze, [">= 2.4.7", "~> 2.4"])
81
- s.add_dependency(%q<simplecov>.freeze, [">= 0"])
82
- end
54
+ if s.respond_to? :add_runtime_dependency then
55
+ s.add_runtime_dependency(%q<aws-sdk>.freeze, ["~> 2"])
56
+ s.add_runtime_dependency(%q<dotenv>.freeze, ["~> 2.2", ">= 2.2.1"])
57
+ s.add_runtime_dependency(%q<thor>.freeze, [">= 0.20.0"])
58
+ s.add_runtime_dependency(%q<activesupport>.freeze, [">= 5.1.4"])
59
+ s.add_runtime_dependency(%q<byebug>.freeze, [">= 10.0.2"])
60
+ s.add_runtime_dependency(%q<git>.freeze, ["> 1.3"])
61
+ s.add_development_dependency(%q<rspec>.freeze, ["> 3.7"])
62
+ s.add_development_dependency(%q<rdoc>.freeze, ["> 3.12"])
63
+ s.add_development_dependency(%q<bundler>.freeze, ["> 2.0"])
64
+ s.add_development_dependency(%q<juwelier>.freeze, [">= 0"])
65
+ s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
83
66
  else
84
67
  s.add_dependency(%q<aws-sdk>.freeze, ["~> 2"])
85
- s.add_dependency(%q<dynect_rest>.freeze, ["= 0.4.6"])
86
- s.add_dependency(%q<aws-sdk-v1>.freeze, ["~> 1.67"])
87
- s.add_dependency(%q<dotenv>.freeze, [">= 2.2.1", "~> 2.2"])
88
- s.add_dependency(%q<thor>.freeze, ["= 0.20.0"])
89
- s.add_dependency(%q<activesupport>.freeze, [">= 5.1.4", "~> 5.1"])
90
- s.add_dependency(%q<byebug>.freeze, [">= 10.0.2", "~> 10.0"])
91
- s.add_dependency(%q<git>.freeze, ["~> 1.3"])
92
- s.add_dependency(%q<rspec>.freeze, ["~> 3.7"])
93
- s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
94
- s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
95
- s.add_dependency(%q<juwelier>.freeze, [">= 2.4.7", "~> 2.4"])
68
+ s.add_dependency(%q<dotenv>.freeze, ["~> 2.2", ">= 2.2.1"])
69
+ s.add_dependency(%q<thor>.freeze, [">= 0.20.0"])
70
+ s.add_dependency(%q<activesupport>.freeze, [">= 5.1.4"])
71
+ s.add_dependency(%q<byebug>.freeze, [">= 10.0.2"])
72
+ s.add_dependency(%q<git>.freeze, ["> 1.3"])
73
+ s.add_dependency(%q<rspec>.freeze, ["> 3.7"])
74
+ s.add_dependency(%q<rdoc>.freeze, ["> 3.12"])
75
+ s.add_dependency(%q<bundler>.freeze, ["> 2.0"])
76
+ s.add_dependency(%q<juwelier>.freeze, [">= 0"])
96
77
  s.add_dependency(%q<simplecov>.freeze, [">= 0"])
97
78
  end
98
79
  end
@@ -2,9 +2,7 @@ require 'bundler/setup'
2
2
  require 'dotenv'
3
3
  require 'thor'
4
4
  require 'active_support/all'
5
- require 'aws-sdk-v1'
6
5
  require 'aws-sdk'
7
- require 'dynect_rest'
8
6
 
9
7
  module Hashicorptools
10
8
  end
@@ -13,7 +11,6 @@ require_relative 'hashicorptools/variables'
13
11
  require_relative 'hashicorptools/ec2_utilities'
14
12
  require_relative 'hashicorptools/auto_scaling_group'
15
13
  require_relative 'hashicorptools/packer'
16
- require_relative 'hashicorptools/terraform'
17
14
  require_relative 'hashicorptools/host'
18
15
  require_relative 'hashicorptools/update_launch_configuration'
19
16
  require_relative 'hashicorptools/code_deploy'
@@ -15,6 +15,7 @@
15
15
  }
16
16
  ],
17
17
  "region": "us-east-1",
18
+ "ami_regions": ["us-east-1", "eu-central-1"],
18
19
  "run_tags": {
19
20
  "kind": "packer",
20
21
  "role": "AMI builder"
@@ -2,7 +2,7 @@ require "timeout"
2
2
 
3
3
  module Hashicorptools
4
4
  class AutoScalingGroup
5
- attr_accessor :name
5
+ attr_accessor :name, :region
6
6
 
7
7
  def initialize(attrs = {})
8
8
  attrs.each do |key,value|
@@ -115,15 +115,15 @@ module Hashicorptools
115
115
  end
116
116
 
117
117
  def autoscaling
118
- @autoscaling ||= Aws::AutoScaling::Client.new(region: 'us-east-1')
118
+ @autoscaling ||= Aws::AutoScaling::Client.new(region: region)
119
119
  end
120
120
 
121
121
  def ec2
122
- @ec2 ||= Aws::EC2::Client.new(region: 'us-east-1')
122
+ @ec2 ||= Aws::EC2::Client.new(region: region)
123
123
  end
124
124
 
125
125
  def elb
126
- @elb ||= Aws::ElasticLoadBalancing::Client.new(region: 'us-east-1')
126
+ @elb ||= Aws::ElasticLoadBalancing::Client.new(region: region)
127
127
  end
128
128
 
129
129
  def groups
@@ -7,52 +7,98 @@ require 'logger'
7
7
  require 'thor'
8
8
 
9
9
  module Hashicorptools
10
+ class RegionDeployment
11
+ attr_accessor :aws_region, :environment
12
+
13
+ def initialize(aws_region:, environment:)
14
+ @aws_region = aws_region
15
+ @environment = environment
16
+ end
17
+
18
+ def create_deployment(commit_id, commit_message)
19
+ client = Aws::CodeDeploy::Client.new(region: aws_region)
20
+ response = client.create_deployment({
21
+ application_name: application_name,
22
+ deployment_group_name: "#{application_name}-#{@environment}",
23
+ revision: {
24
+ revision_type: 'GitHub',
25
+ git_hub_location: {
26
+ repository: "controlshift/#{application_name}",
27
+ commit_id: commit_id
28
+ }
29
+ },
30
+ description: (commit_message || "commit #{commit_id}").slice(0,99)
31
+ })
32
+ output "created deployment #{response.deployment_id}"
33
+ output "https://console.aws.amazon.com/codedeploy/home?region=#{aws_region}#/deployments/#{response.deployment_id}"
34
+ end
35
+
36
+ private
37
+
38
+ def application_name
39
+ raise "implement me"
40
+ end
41
+
42
+ def output(text)
43
+ puts "[#{aws_region}] #{text}"
44
+ end
45
+ end
46
+
10
47
  class CodeDeploy < Thor
48
+ AWS_REGION_US_EAST_1 = 'us-east-1'
11
49
 
12
50
  desc 'deploy', 'deploy latest code to environment'
13
- option :environment, :required => true
14
- option :branch, default: 'master'
51
+ option :environment, required: true
52
+ option :branch
53
+ option :aws_regions, type: :array
15
54
  option :commit
16
55
  def deploy
17
56
  g = Git.open('..')
18
57
 
58
+ # We set defaults (depending on environment) for aws_regions if not passed in
59
+ aws_regions = options[:aws_regions] || default_regions
60
+
19
61
  commit = if options[:commit].present?
20
62
  g.gcommit(options[:commit])
21
63
  else
22
- g.checkout(options[:branch].to_sym)
64
+ branch = options[:branch].nil? ? :main : options[:branch].to_sym
65
+ g.checkout(branch)
23
66
  g.log.first
24
67
  end
25
68
 
69
+ puts "Deploying to environment #{options[:environment]} - regions: #{aws_regions.join(', ')}
70
+ commit: #{commit.sha}
71
+ message: #{commit.message}"
26
72
 
27
- puts "deploying commit: #{commit.sha} #{commit.message}"
73
+ puts "Deploying for regions: #{aws_regions}"
28
74
 
29
- create_deployment(commit.sha, commit.message)
75
+ threads = []
76
+ aws_regions.each_slice(2) do |aws_regions_batch|
77
+ puts "Deploying for batch of regions: #{aws_regions_batch}"
78
+ aws_regions_batch.each do |aws_region|
79
+ thread = Thread.new{ region_deployment(aws_region).create_deployment(commit.sha, commit.message) }
80
+ threads.push(thread)
81
+ end
82
+
83
+ threads.each_with_index do |thread, index|
84
+ begin
85
+ thread.join
86
+ rescue Exception => e
87
+ # Don't quit whole program on exception in thread, just print exception and exit thread
88
+ puts "[#{aws_regions[index]}] EXCEPTION: #{e}"
89
+ end
90
+ end
91
+ end
30
92
  end
31
93
 
32
94
  private
33
95
 
34
- def create_deployment(commit_id, commit_message = nil)
35
- Dotenv.load
36
-
37
- client = Aws::CodeDeploy::Client.new
38
- response = client.create_deployment({
39
- application_name: application_name,
40
- deployment_group_name: "#{application_name}-#{options[:environment]}",
41
- revision: {
42
- revision_type: 'GitHub',
43
- git_hub_location: {
44
- repository: "controlshift/#{application_name}",
45
- commit_id: commit_id
46
- }
47
- },
48
- description: (commit_message || "commit #{commit_id}").slice(0,99)
49
- })
50
- puts "created deployment #{response.deployment_id}"
51
- puts "https://console.aws.amazon.com/codedeploy/home?region=#{ENV['AWS_REGION']}#/deployments/#{response.deployment_id}"
96
+ def region_deployment(aws_region)
97
+ RegionDeployment.new(aws_region: aws_region, environment: options[:environment])
52
98
  end
53
99
 
54
- def application_name
55
- raise "implement me"
100
+ def default_regions
101
+ [AWS_REGION_US_EAST_1]
56
102
  end
57
103
  end
58
104
  end
@@ -5,7 +5,8 @@ module Hashicorptools
5
5
  end
6
6
 
7
7
  def amis(tag = tag_name)
8
- sort_by_created_at( ec2.images.with_owner('self').with_tag('Name', tag).to_a )
8
+ images = ec2.describe_images({owners: ['self'], filters: [{name: 'tag:Name', values: [tag]}]}).images
9
+ sort_by_created_at(images)
9
10
  end
10
11
 
11
12
  def ec2
@@ -17,17 +18,15 @@ module Hashicorptools
17
18
  'us-east-1'
18
19
  end
19
20
 
20
- @_ec2 = AWS::EC2.new(region: reg)
21
+ @_ec2 = Aws::EC2::Client.new(region: reg)
21
22
  end
22
23
 
23
24
  def vpc_with_name(name)
24
- vpcs = ec2.client.describe_vpcs({filters: [{name: 'tag:Name', values: [name]}]}).vpc_set
25
- vpcs.first
25
+ ec2.describe_vpcs({filters: [{name: 'tag:Name', values: [name]}]}).vpcs.first
26
26
  end
27
27
 
28
28
  def internet_gateway_for_vpc(vpc_id)
29
- igs = ec2.client.describe_internet_gateways({filters: [{name: 'attachment.vpc-id', values: [vpc_id]}]}).internet_gateway_set
30
- igs.first
29
+ ec2.describe_internet_gateways({filters: [{name: 'attachment.vpc-id', values: [vpc_id]}]}).internet_gateways.first
31
30
  end
32
31
 
33
32
  def sort_by_created_at(collection)
@@ -33,7 +33,7 @@ module Hashicorptools
33
33
 
34
34
  desc "list", "list all available amis"
35
35
  def list
36
- amis.each do |ami|
36
+ amis_in_region(region).each do |ami|
37
37
  puts ami.image_id
38
38
  end
39
39
  end
@@ -45,7 +45,7 @@ module Hashicorptools
45
45
 
46
46
  desc "clean_snapshots", "clean obsolete EBS snapshots not associated with any AMI"
47
47
  def clean_snapshots
48
- snapshots = ec2.snapshots.with_owner('self')
48
+ snapshots = ec2.describe_snapshots({owner_ids: ['self']}).snapshots
49
49
  snapshots.each do |snapshot|
50
50
  match = snapshot.description.match(/Created by CreateImage\(.+\) for (ami-[0-9a-f]+) from vol-.+/)
51
51
  if match.nil?
@@ -54,23 +54,28 @@ module Hashicorptools
54
54
  end
55
55
 
56
56
  ami_id = match[1]
57
- unless ec2.images[ami_id].exists?
58
- puts "Removing obsolete snapshot #{snapshot.id} - #{snapshot.description}"
59
- snapshot = AWS::EC2::Snapshot.new(snapshot.id)
60
- snapshot.delete
57
+ unless Aws::EC2::Image.new(ami_id, region: region).exists?
58
+ puts "Removing obsolete snapshot #{snapshot.snapshot_id} - #{snapshot.description}"
59
+ ec2.delete_snapshot({snapshot_id: snapshot.snapshot_id})
61
60
  end
62
61
  end
63
62
  end
64
63
 
65
64
  desc "boot", "start up an instance of the latest version of AMI"
66
65
  def boot
67
- run_instances_resp = ec2.run_instances(image_id: current_ami('base-image').image_id,
66
+ run_instances_resp = ec2.run_instances({
67
+ image_id: current_ami('base-image').image_id,
68
68
  min_count: 1,
69
69
  max_count: 1,
70
- instance_type: "t2.micro")
70
+ instance_type: "t2.micro"
71
+ })
71
72
 
72
- ec2.create_tags( resources: run_instances_resp.instances.collect{|i| i.instance_id },
73
- tags: [ {key: 'Name', value: "packer test boot #{tag_name}"}, {key: 'environment', value: 'packer-development'}, {key: 'temporary', value: 'kill me'}])
73
+ ec2.create_tags({
74
+ resources: run_instances_resp.instances.collect(&:instance_id),
75
+ tags: [ {key: 'Name', value: "packer test boot #{tag_name}"},
76
+ {key: 'environment', value: 'packer-development'},
77
+ {key: 'temporary', value: 'kill me'}]
78
+ })
74
79
 
75
80
  require 'byebug'
76
81
  byebug
@@ -104,7 +109,7 @@ module Hashicorptools
104
109
  end
105
110
 
106
111
  def ami_building_subnet_id
107
- ec2.client.describe_subnets({filters: [{name: "vpc-id", values: [ami_building_vpc_id]}]}).subnet_set.first.subnet_id
112
+ ec2.describe_subnets({filters: [{name: "vpc-id", values: [ami_building_vpc_id]}]}).subnets.first.subnet_id
108
113
  end
109
114
 
110
115
  def format_variable(key, value)
@@ -127,39 +132,52 @@ module Hashicorptools
127
132
  'us-east-1'
128
133
  end
129
134
 
130
- def auto_scaling
131
- @auto_scaling ||= Aws::AutoScaling::Client.new(region: region)
135
+ def auto_scaling(client_region=region)
136
+ @auto_scaling ||= Aws::AutoScaling::Client.new(region: client_region)
132
137
  end
133
138
 
134
- def ec2_v2
135
- @ec2 ||= Aws::EC2::Client.new(region: region)
139
+ def regional_ec2_client(client_region=region)
140
+ @_regional_ec2_clients = {} if @_regional_ec2_clients.nil?
141
+ @_regional_ec2_clients[client_region] ||= Aws::EC2::Client.new(region: client_region)
136
142
  end
137
143
 
138
- def amis_in_use
139
- launch_configs = auto_scaling.describe_launch_configurations
144
+ def amis_in_use(client_region)
145
+ launch_configs = auto_scaling(client_region).describe_launch_configurations
140
146
  image_ids = launch_configs.data['launch_configurations'].collect{|lc| lc.image_id}.flatten
141
147
 
142
- ec2_reservations = ec2_v2.describe_instances
148
+ ec2_reservations = regional_ec2_client(client_region).describe_instances
143
149
  image_ids << ec2_reservations.reservations.collect{|res| res.instances.collect{|r| r.image_id}}.flatten
144
150
  image_ids.flatten
145
151
  end
146
152
 
153
+ def ami_regions
154
+ ['us-east-1', 'eu-central-1']
155
+ end
156
+
147
157
  def clean_amis
148
- ami_ids = amis.collect{|a| a.image_id}
149
- ami_ids_to_remove = ami_ids - amis_in_use
150
- potential_amis_to_remove = amis
158
+ ami_regions.each do |ami_region|
159
+ clean_amis_for_region(ami_region)
160
+ end
161
+ end
162
+
163
+ def clean_amis_for_region(region_to_clean)
164
+ ami_ids = amis_in_region(region_to_clean).collect{|a| a.image_id}
165
+ ami_ids_to_remove = ami_ids - amis_in_use(region_to_clean)
166
+ potential_amis_to_remove = amis_in_region(region_to_clean)
151
167
  potential_amis_to_remove.keep_if {|a| ami_ids_to_remove.include?(a.image_id) }
152
168
 
153
169
  if potential_amis_to_remove.size > NUMBER_OF_AMIS_TO_KEEP
154
170
  amis_to_remove = potential_amis_to_remove[NUMBER_OF_AMIS_TO_KEEP..-1]
155
171
  amis_to_keep = potential_amis_to_remove[0..(NUMBER_OF_AMIS_TO_KEEP-1)]
156
172
 
157
- puts "Deregistering old AMIs..."
173
+ puts "Deregistering old AMIs in #{region_to_clean}..."
158
174
  amis_to_remove.each do |ami|
159
175
  ebs_mappings = ami.block_device_mappings
160
176
  puts "Deregistering #{ami.image_id}"
161
- ami.deregister
162
- delete_ami_snapshots(ebs_mappings)
177
+ regional_ec2_client(region_to_clean).deregister_image({
178
+ image_id: ami.image_id
179
+ })
180
+ delete_ami_snapshots(ebs_mappings, snapshot_region: region_to_clean)
163
181
  end
164
182
 
165
183
  puts "Currently active AMIs..."
@@ -167,15 +185,26 @@ module Hashicorptools
167
185
  puts ami.image_id
168
186
  end
169
187
  else
170
- puts "no AMIs to clean."
188
+ puts "no AMIs to clean in #{region_to_clean}."
171
189
  end
172
190
  end
173
191
 
174
- def delete_ami_snapshots(ebs_mappings)
175
- ebs_mappings.each do |volume, attributes|
176
- puts "Deleting snapshot #{attributes[:snapshot_id]}"
177
- snapshot = AWS::EC2::Snapshot.new(attributes[:snapshot_id])
178
- snapshot.delete
192
+ def amis_in_region(ami_region)
193
+ images = regional_ec2_client(ami_region).describe_images({
194
+ owners: ['self'],
195
+ filters: [{name: 'tag:Name', values: [tag_name]}]
196
+ }).images
197
+ sort_by_created_at(images)
198
+ end
199
+
200
+ def delete_ami_snapshots(ebs_mappings, snapshot_region:)
201
+ ec2_client = regional_ec2_client(snapshot_region)
202
+
203
+ ebs_mappings.each do |ebs_mapping|
204
+ unless ebs_mapping.ebs.nil?
205
+ puts "Deleting snapshot #{ebs_mapping.ebs.snapshot_id}"
206
+ ec2_client.delete_snapshot({snapshot_id: ebs_mapping.ebs.snapshot_id})
207
+ end
179
208
  end
180
209
  end
181
210
  end
@@ -1,8 +1,9 @@
1
1
  module Hashicorptools
2
2
  class UpdateLaunchConfiguration < Thor
3
3
  desc 'deploy ASG_NAME', 'recycle instances in the ASG with no downtime'
4
+ option :aws_region, default: 'us-east-1'
4
5
  def deploy(asg_name)
5
- asg = AutoScalingGroup.new(name: asg_name)
6
+ asg = AutoScalingGroup.new(name: asg_name, region: options[:aws_region])
6
7
  if asg.group.nil?
7
8
  raise "could not find asg #{asg_name}"
8
9
  end
@@ -1,12 +1,8 @@
1
1
  module Hashicorptools
2
2
  module Variables
3
- def aws_credentials_settings(settings_overrides = {})
4
- {aws_access_key: ENV['AWS_ACCESS_KEY_ID'],
5
- aws_secret_key: ENV['AWS_SECRET_ACCESS_KEY']}.merge(settings_overrides)
6
- end
7
3
 
8
4
  def variables(settings_overrides = {})
9
- aws_credentials_settings(settings_overrides).collect{|key,value| format_variable(key, value)}.join(' ')
5
+ settings_overrides.collect{|key,value| format_variable(key, value)}.join(' ')
10
6
  end
11
7
 
12
8
  protected
@@ -0,0 +1,14 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Hashicorptools::Ec2Utilities, type: :helper do
4
+ let(:including_class) { Class.new { include Hashicorptools::Ec2Utilities } }
5
+
6
+ subject { including_class.new }
7
+
8
+ describe '#ec2' do
9
+ it 'should return a client' do
10
+ client = subject.ec2
11
+ expect(client).to be_a Aws::EC2::Client
12
+ end
13
+ end
14
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hashicorptools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Woodhull
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-03 00:00:00.000000000 Z
11
+ date: 2020-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk
@@ -25,65 +25,37 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2'
27
27
  - !ruby/object:Gem::Dependency
28
- name: dynect_rest
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - '='
32
- - !ruby/object:Gem::Version
33
- version: 0.4.6
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - '='
39
- - !ruby/object:Gem::Version
40
- version: 0.4.6
41
- - !ruby/object:Gem::Dependency
42
- name: aws-sdk-v1
28
+ name: dotenv
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
31
  - - "~>"
46
32
  - !ruby/object:Gem::Version
47
- version: '1.67'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '1.67'
55
- - !ruby/object:Gem::Dependency
56
- name: dotenv
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
33
+ version: '2.2'
59
34
  - - ">="
60
35
  - !ruby/object:Gem::Version
61
36
  version: 2.2.1
62
- - - "~>"
63
- - !ruby/object:Gem::Version
64
- version: '2.2'
65
37
  type: :runtime
66
38
  prerelease: false
67
39
  version_requirements: !ruby/object:Gem::Requirement
68
40
  requirements:
69
- - - ">="
70
- - !ruby/object:Gem::Version
71
- version: 2.2.1
72
41
  - - "~>"
73
42
  - !ruby/object:Gem::Version
74
43
  version: '2.2'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 2.2.1
75
47
  - !ruby/object:Gem::Dependency
76
48
  name: thor
77
49
  requirement: !ruby/object:Gem::Requirement
78
50
  requirements:
79
- - - '='
51
+ - - ">="
80
52
  - !ruby/object:Gem::Version
81
53
  version: 0.20.0
82
54
  type: :runtime
83
55
  prerelease: false
84
56
  version_requirements: !ruby/object:Gem::Requirement
85
57
  requirements:
86
- - - '='
58
+ - - ">="
87
59
  - !ruby/object:Gem::Version
88
60
  version: 0.20.0
89
61
  - !ruby/object:Gem::Dependency
@@ -93,9 +65,6 @@ dependencies:
93
65
  - - ">="
94
66
  - !ruby/object:Gem::Version
95
67
  version: 5.1.4
96
- - - "~>"
97
- - !ruby/object:Gem::Version
98
- version: '5.1'
99
68
  type: :runtime
100
69
  prerelease: false
101
70
  version_requirements: !ruby/object:Gem::Requirement
@@ -103,9 +72,6 @@ dependencies:
103
72
  - - ">="
104
73
  - !ruby/object:Gem::Version
105
74
  version: 5.1.4
106
- - - "~>"
107
- - !ruby/object:Gem::Version
108
- version: '5.1'
109
75
  - !ruby/object:Gem::Dependency
110
76
  name: byebug
111
77
  requirement: !ruby/object:Gem::Requirement
@@ -113,9 +79,6 @@ dependencies:
113
79
  - - ">="
114
80
  - !ruby/object:Gem::Version
115
81
  version: 10.0.2
116
- - - "~>"
117
- - !ruby/object:Gem::Version
118
- version: '10.0'
119
82
  type: :runtime
120
83
  prerelease: false
121
84
  version_requirements: !ruby/object:Gem::Requirement
@@ -123,85 +86,76 @@ dependencies:
123
86
  - - ">="
124
87
  - !ruby/object:Gem::Version
125
88
  version: 10.0.2
126
- - - "~>"
127
- - !ruby/object:Gem::Version
128
- version: '10.0'
129
89
  - !ruby/object:Gem::Dependency
130
90
  name: git
131
91
  requirement: !ruby/object:Gem::Requirement
132
92
  requirements:
133
- - - "~>"
93
+ - - ">"
134
94
  - !ruby/object:Gem::Version
135
95
  version: '1.3'
136
96
  type: :runtime
137
97
  prerelease: false
138
98
  version_requirements: !ruby/object:Gem::Requirement
139
99
  requirements:
140
- - - "~>"
100
+ - - ">"
141
101
  - !ruby/object:Gem::Version
142
102
  version: '1.3'
143
103
  - !ruby/object:Gem::Dependency
144
104
  name: rspec
145
105
  requirement: !ruby/object:Gem::Requirement
146
106
  requirements:
147
- - - "~>"
107
+ - - ">"
148
108
  - !ruby/object:Gem::Version
149
109
  version: '3.7'
150
110
  type: :development
151
111
  prerelease: false
152
112
  version_requirements: !ruby/object:Gem::Requirement
153
113
  requirements:
154
- - - "~>"
114
+ - - ">"
155
115
  - !ruby/object:Gem::Version
156
116
  version: '3.7'
157
117
  - !ruby/object:Gem::Dependency
158
118
  name: rdoc
159
119
  requirement: !ruby/object:Gem::Requirement
160
120
  requirements:
161
- - - "~>"
121
+ - - ">"
162
122
  - !ruby/object:Gem::Version
163
123
  version: '3.12'
164
124
  type: :development
165
125
  prerelease: false
166
126
  version_requirements: !ruby/object:Gem::Requirement
167
127
  requirements:
168
- - - "~>"
128
+ - - ">"
169
129
  - !ruby/object:Gem::Version
170
130
  version: '3.12'
171
131
  - !ruby/object:Gem::Dependency
172
132
  name: bundler
173
133
  requirement: !ruby/object:Gem::Requirement
174
134
  requirements:
175
- - - "~>"
135
+ - - ">"
176
136
  - !ruby/object:Gem::Version
177
- version: '1.0'
137
+ version: '2.0'
178
138
  type: :development
179
139
  prerelease: false
180
140
  version_requirements: !ruby/object:Gem::Requirement
181
141
  requirements:
182
- - - "~>"
142
+ - - ">"
183
143
  - !ruby/object:Gem::Version
184
- version: '1.0'
144
+ version: '2.0'
185
145
  - !ruby/object:Gem::Dependency
186
146
  name: juwelier
187
147
  requirement: !ruby/object:Gem::Requirement
188
148
  requirements:
189
149
  - - ">="
190
150
  - !ruby/object:Gem::Version
191
- version: 2.4.7
192
- - - "~>"
193
- - !ruby/object:Gem::Version
194
- version: '2.4'
151
+ version: '0'
195
152
  type: :development
196
153
  prerelease: false
197
154
  version_requirements: !ruby/object:Gem::Requirement
198
155
  requirements:
199
156
  - - ">="
200
157
  - !ruby/object:Gem::Version
201
- version: 2.4.7
202
- - - "~>"
203
- - !ruby/object:Gem::Version
204
- version: '2.4'
158
+ version: '0'
205
159
  - !ruby/object:Gem::Dependency
206
160
  name: simplecov
207
161
  requirement: !ruby/object:Gem::Requirement
@@ -242,10 +196,9 @@ files:
242
196
  - lib/hashicorptools/ec2_utilities.rb
243
197
  - lib/hashicorptools/host.rb
244
198
  - lib/hashicorptools/packer.rb
245
- - lib/hashicorptools/terraform.rb
246
199
  - lib/hashicorptools/update_launch_configuration.rb
247
200
  - lib/hashicorptools/variables.rb
248
- - spec/hashicorptools_spec.rb
201
+ - spec/ec2_utilities_spec.rb
249
202
  - spec/spec_helper.rb
250
203
  homepage: http://github.com/woodhull/hashicorptools
251
204
  licenses:
@@ -266,8 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
266
219
  - !ruby/object:Gem::Version
267
220
  version: '0'
268
221
  requirements: []
269
- rubyforge_project:
270
- rubygems_version: 2.7.6
222
+ rubygems_version: 3.1.2
271
223
  signing_key:
272
224
  specification_version: 4
273
225
  summary: Wrappers for terraform and packer
@@ -1,299 +0,0 @@
1
- module Hashicorptools
2
- class Terraform < Thor
3
- TERRAFORM_VERSION = '0.11.0'
4
-
5
- include Ec2Utilities
6
- include Variables
7
-
8
- desc 'bootstrap', 'terraform a new infrastructure from scratch'
9
- option :environment, :required => true
10
- def bootstrap
11
- apply
12
- end
13
-
14
- desc 'init', 'install providers into local terraform'
15
- option :environment, :required => true
16
- def init
17
-
18
- end
19
-
20
- [:apply, :plan, :destroy, :pull, :refresh].each do |cmd|
21
- desc cmd, "terraform #{cmd}"
22
- option :environment, :required => true
23
- option :debug, :required => false
24
-
25
- define_method cmd do
26
- send("_#{cmd}")
27
- end
28
-
29
- no_commands do
30
- define_method "_#{cmd}" do |settings_overrides = {}|
31
- enforce_version!
32
- raise 'invalid environment' unless ['staging', 'production'].include?(options[:environment])
33
-
34
- execute(state_path, var_file_path) do
35
-
36
- settings_overrides
37
- .merge!({ app_environment: options[:environment] }
38
- .merge(env_variable_keys)
39
- .merge(settings)
40
- .merge(shared_plan_variables))
41
-
42
- send("before_#{cmd}")
43
-
44
- terraform_command = "terraform #{cmd} #{variables(settings_overrides)} -state #{state_path} #{var_file_param} #{config_directory}"
45
-
46
- if (options[:debug])
47
- puts "[DEBUG] running command: '#{terraform_command}"
48
- end
49
-
50
- result = system terraform_command
51
-
52
- if result
53
- send("after_#{cmd}")
54
- end
55
- end
56
- end
57
-
58
- define_method "before_#{cmd}" do
59
- # no-op
60
- end
61
- end
62
-
63
- no_commands do
64
- define_method "before_shared_#{cmd}" do
65
- # no-op
66
- end
67
-
68
- define_method "after_#{cmd}" do
69
- # no-op
70
- end
71
-
72
- define_method "after_shared_#{cmd}" do
73
- # no-op
74
- end
75
- end
76
-
77
- desc cmd, "terraform #{cmd} for shared plan"
78
- option :debug, :required => false
79
- define_method "shared_#{cmd}" do
80
- enforce_version!
81
-
82
- execute(shared_state_path) do
83
- send("before_shared_#{cmd}")
84
-
85
- terraform_command = "terraform #{cmd} #{variables(env_variable_keys.merge(settings))} -state #{shared_state_path} #{shared_config_directory}"
86
- if (options[:debug])
87
- puts "[DEBUG] running command: '#{terraform_command}"
88
- end
89
- result = system terraform_command
90
-
91
- if result
92
- send("after_shared_#{cmd}")
93
- end
94
- end
95
- end
96
- end
97
-
98
- desc 'output', 'terraform output'
99
- option :environment, :required => true
100
- option :name, :required => true
101
- def output
102
- execute(state_path) do
103
- system output_cmd(state_path, options[:name])
104
- end
105
- end
106
-
107
- desc 'shared_output', 'terraform output for shared plan'
108
- option :name, :required => true
109
- def shared_output
110
- execute(shared_state_path) do
111
- system output_cmd(shared_state_path, options[:name])
112
- end
113
- end
114
-
115
- desc 'taint', 'terraform taint'
116
- option :environment, :required => true
117
- option :name, :required => true
118
- option :module, :required => false
119
- def taint
120
- execute(state_path) do
121
- if options[:module].present?
122
- system "terraform taint -module #{options[:module]} -state #{state_path} #{options[:name]}"
123
- else
124
- system "terraform taint -state #{state_path} #{options[:name]}"
125
- end
126
- end
127
- end
128
-
129
- desc 'shared_taint', 'terraform taint for shared plan'
130
- option :name, :required => true
131
- option :module, :required => false
132
- def shared_taint
133
- execute(shared_state_path) do
134
- if options[:module].present?
135
- system "terraform taint -module #{options[:module]} -state #{shared_state_path} #{options[:name]}"
136
- else
137
- system "terraform taint -state #{shared_state_path} #{options[:name]}"
138
- end
139
- end
140
- end
141
-
142
- desc 'show', 'terraform show'
143
- option :environment, :required => true
144
- def show
145
- execute(state_path) do
146
- system "terraform show #{state_path}"
147
- end
148
- end
149
-
150
- desc 'shared_show', 'terraform show for shared plan'
151
- def shared_show
152
- execute(shared_state_path) do
153
- system "terraform show #{shared_state_path}"
154
- end
155
- end
156
-
157
- desc "console", "interactive session"
158
- def console
159
- require 'pry-byebug'
160
- binding.pry
161
- end
162
-
163
- protected
164
-
165
- def var_file_param
166
- File.exist?(var_file_path) ?
167
- "-var-file #{var_file_path}" :
168
- ""
169
- end
170
-
171
- def execute(state_file_path, var_file_path=nil)
172
- begin
173
- yield
174
- rescue StandardError => e
175
- puts e.message
176
- puts e.backtrace
177
- end
178
- end
179
-
180
- def state_path
181
- "#{config_environment_path}/#{options[:environment]}.tfstate"
182
- end
183
-
184
- def shared_state_path
185
- "#{shared_config_directory}/shared.tfstate"
186
- end
187
-
188
- def var_file_path
189
- "#{config_environment_path}/variables.tfvars"
190
- end
191
-
192
- def config_directory
193
- "config/infrastructure/#{infrastructure}"
194
- end
195
-
196
- def shared_config_directory
197
- "config/infrastructure/#{infrastructure}/shared"
198
- end
199
-
200
- def config_environment_path
201
- "#{config_directory}/environments/#{options[:environment]}"
202
- end
203
-
204
- def infrastructure
205
- raise 'implement me'
206
- end
207
-
208
- def output_cmd(state_file_path, name=nil)
209
- "terraform output -state=#{state_file_path} #{name}"
210
- end
211
-
212
- def output_variable(state_file_path, name)
213
- `#{output_cmd(state_file_path, name)}`.chomp
214
- end
215
-
216
- def output_variables(state_file_path)
217
- raw_plan_output = `#{output_cmd(state_file_path)}`
218
- parse_key_value_variables(raw_plan_output)
219
- end
220
-
221
- def var_file_variables
222
- raise "Vars file #{var_file_path} does not exist" unless File.exist?(var_file_path)
223
-
224
- raw_var_file_variables = File.read(var_file_path)
225
- parse_key_value_variables(raw_var_file_variables)
226
- end
227
-
228
- def terraform_version
229
- version_string = `terraform version`.chomp
230
- version = /(\d+.\d+.\d+)/.match(version_string)
231
- version[0]
232
- end
233
-
234
- def enforce_version!
235
- if Gem::Version.new(terraform_version) < Gem::Version.new(TERRAFORM_VERSION)
236
- raise "Terraform #{terraform_version} is out of date, please upgrade"
237
- end
238
- end
239
-
240
- def settings
241
- {} # override me to pass more variables into the terraform plan.
242
- end
243
-
244
- def asg_launch_config_name(asg_name)
245
- asg_client = Aws::AutoScaling::Client.new(region: 'us-east-1')
246
- group = asg_client.describe_auto_scaling_groups(auto_scaling_group_names: [asg_name]).auto_scaling_groups.first
247
- group.try(:launch_configuration_name)
248
- end
249
-
250
- def env_variable_keys
251
- {} # override me to pass environmental variables into the terraform plan
252
- end
253
-
254
- def shared_plan_variables
255
- if File.exist?(shared_state_path)
256
- output_variables(shared_state_path)
257
- else
258
- {}
259
- end
260
- end
261
-
262
- def fetch_terraform_modules
263
- system "terraform get -update=true #{config_directory}"
264
- end
265
-
266
- def current_tfstate
267
- return @current_tfstate if defined?(@current_tfstate)
268
- raw_conf = File.read(state_path)
269
- @current_tfstate = JSON.parse(raw_conf)
270
- end
271
-
272
- def read_config_file(path)
273
- File.new('config/' + path).read
274
- template = ERB.new File.new("config/#{path}").read, nil, "%"
275
- template.result(OpenStruct.new(options).instance_eval { binding })
276
- end
277
-
278
- def dynect
279
- @dynect ||= DynectRest.new("controlshiftlabs", ENV['DYNECT_USERNAME'], ENV['DYNECT_PASSWORD'], "controlshiftlabs.com")
280
- end
281
-
282
- def dns_record_exists?(parent_node_fqdn, record)
283
- dynect.node_list(nil, parent_node_fqdn).include?(record.fqdn)
284
- end
285
-
286
- private
287
-
288
- def parse_key_value_variables(vars_string)
289
- vars = {}
290
- vars_string.split("\n").each do |string_var|
291
- next if string_var.blank?
292
- key, value = string_var.split("=")
293
- vars[key.strip] = value.strip.gsub('"', '')
294
- end
295
-
296
- vars
297
- end
298
- end
299
- end
@@ -1,8 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
-
3
- describe "Hashicorptools" do
4
- it "fails" do
5
- pending
6
- fail "hey buddy, you should probably rename this file and start specing for real"
7
- end
8
- end