aws_pocketknife 0.1.7
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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +1 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +115 -0
- data/Rakefile +22 -0
- data/aws_pocketknife.gemspec +40 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/cert/ca-bundle.crt +3988 -0
- data/exe/pocketknife +5 -0
- data/lib/aws_pocketknife.rb +88 -0
- data/lib/aws_pocketknife/admin/policies/developer_dev_acc.json +10 -0
- data/lib/aws_pocketknife/admin/policies/developer_prd_acc.json +15 -0
- data/lib/aws_pocketknife/admin/policies/tc_devops.json.erb +207 -0
- data/lib/aws_pocketknife/admin/policies/tester_dev_acc.json +176 -0
- data/lib/aws_pocketknife/admin/policies/tester_prd_acc.json +176 -0
- data/lib/aws_pocketknife/admin/policies/web_front_end.json.erb +59 -0
- data/lib/aws_pocketknife/admin/trust_relationships/ec2.json +13 -0
- data/lib/aws_pocketknife/asg.rb +56 -0
- data/lib/aws_pocketknife/cli/ami.rb +24 -0
- data/lib/aws_pocketknife/cli/asg.rb +40 -0
- data/lib/aws_pocketknife/cli/eb.rb +49 -0
- data/lib/aws_pocketknife/cli/ec2.rb +61 -0
- data/lib/aws_pocketknife/cli/elb.rb +20 -0
- data/lib/aws_pocketknife/cli/iam.rb +31 -0
- data/lib/aws_pocketknife/cli/main.rb +34 -0
- data/lib/aws_pocketknife/cli/rds.rb +13 -0
- data/lib/aws_pocketknife/cli/rds_snapshot.rb +44 -0
- data/lib/aws_pocketknife/cli/route53.rb +56 -0
- data/lib/aws_pocketknife/cloudwatch_logs.rb +25 -0
- data/lib/aws_pocketknife/common/logging.rb +31 -0
- data/lib/aws_pocketknife/common/utils.rb +63 -0
- data/lib/aws_pocketknife/ec2.rb +308 -0
- data/lib/aws_pocketknife/elastic_beanstalk.rb +62 -0
- data/lib/aws_pocketknife/elb.rb +25 -0
- data/lib/aws_pocketknife/iam.rb +135 -0
- data/lib/aws_pocketknife/rds.rb +84 -0
- data/lib/aws_pocketknife/route53.rb +234 -0
- data/lib/aws_pocketknife/tasks/asg.rake +18 -0
- data/lib/aws_pocketknife/tasks/cloudwatch.rake +12 -0
- data/lib/aws_pocketknife/tasks/ec2.rake +57 -0
- data/lib/aws_pocketknife/tasks/elastic_beanstalk.rake +25 -0
- data/lib/aws_pocketknife/tasks/elb.rake +13 -0
- data/lib/aws_pocketknife/tasks/iam.rake +57 -0
- data/lib/aws_pocketknife/tasks/route53.rake +64 -0
- data/lib/aws_pocketknife/version.rb +3 -0
- metadata +284 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "aws_pocketknife"
|
3
|
+
|
4
|
+
module AwsPocketknife
|
5
|
+
module Cli
|
6
|
+
class Elb < Thor
|
7
|
+
|
8
|
+
desc "desc ELB_NAME", "describe elastic load balancer"
|
9
|
+
def desc(elb_name)
|
10
|
+
elb = AwsPocketknife::Elb.describe_elb_by_name(name: elb_name)
|
11
|
+
if elb.nil?
|
12
|
+
puts "ELB #{elb_name} not found"
|
13
|
+
else
|
14
|
+
AwsPocketknife::Ec2.nice_print(object: elb.to_h)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "aws_pocketknife"
|
3
|
+
|
4
|
+
module AwsPocketknife
|
5
|
+
module Cli
|
6
|
+
class Iam < Thor
|
7
|
+
|
8
|
+
desc "list_ssl_certs", "list ssl certs"
|
9
|
+
def list_ssl_certs
|
10
|
+
certs = AwsPocketknife::Iam.list_ssl_certificates
|
11
|
+
AwsPocketknife::Iam.nice_print(object: certs.to_h)
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "create_user USERNAME", "create user"
|
15
|
+
def create_user(username)
|
16
|
+
AwsPocketknife::Iam.create_iam_user username
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "create_group GROUP_NAME", "create group"
|
20
|
+
def create_group(group_name)
|
21
|
+
AwsPocketknife::Iam.create_group group_name
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "add_user_to_group USERNAME GROUP_NAME", "add user to group"
|
25
|
+
def add_user_to_group(username, group_name)
|
26
|
+
AwsPocketknife::Iam.add_user_to_group username, group_name
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "aws_pocketknife"
|
3
|
+
|
4
|
+
module AwsPocketknife
|
5
|
+
module Cli
|
6
|
+
class Main < Thor
|
7
|
+
|
8
|
+
desc "ec2 SUBCOMMAND ...ARGS", "ec2 command lines"
|
9
|
+
subcommand "ec2", AwsPocketknife::Cli::Ec2
|
10
|
+
|
11
|
+
desc "ami SUBCOMMAND ...ARGS", "ami command lines"
|
12
|
+
subcommand "ami", AwsPocketknife::Cli::Ami
|
13
|
+
|
14
|
+
desc "eb SUBCOMMAND ...ARGS", "elastic beanstalk command lines"
|
15
|
+
subcommand "eb", AwsPocketknife::Cli::Eb
|
16
|
+
|
17
|
+
desc "route53 SUBCOMMAND ...ARGS", "route53 command lines"
|
18
|
+
subcommand "route53", AwsPocketknife::Cli::Route53
|
19
|
+
|
20
|
+
desc "iam SUBCOMMAND ...ARGS", "iam command lines"
|
21
|
+
subcommand "iam", AwsPocketknife::Cli::Iam
|
22
|
+
|
23
|
+
desc "rds SUBCOMMAND ...ARGS", "rds command lines"
|
24
|
+
subcommand "rds", AwsPocketknife::Cli::Rds
|
25
|
+
|
26
|
+
desc "asg SUBCOMMAND ...ARGS", "asg command lines"
|
27
|
+
subcommand "asg", AwsPocketknife::Cli::Asg
|
28
|
+
|
29
|
+
desc "elb SUBCOMMAND ...ARGS", "elb command lines"
|
30
|
+
subcommand "elb", AwsPocketknife::Cli::Elb
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "aws_pocketknife"
|
3
|
+
|
4
|
+
module AwsPocketknife
|
5
|
+
module Cli
|
6
|
+
class RdsSnapshot < Thor
|
7
|
+
|
8
|
+
desc "list_snapshots DB_NAME", "list snapshots"
|
9
|
+
def list(db_name)
|
10
|
+
snapshots = AwsPocketknife::Rds.describe_snapshots(db_name: db_name)
|
11
|
+
headers = [ 'Name', 'Creation Time', 'Snapshot Type', 'Status','Port', 'Engine', 'Version', 'Storage (Gb)', 'IOPS']
|
12
|
+
data = []
|
13
|
+
snapshots.each do |h|
|
14
|
+
data << [h.db_snapshot_identifier,
|
15
|
+
h.snapshot_create_time,
|
16
|
+
h.snapshot_type,
|
17
|
+
h.status,
|
18
|
+
h.port,
|
19
|
+
h.engine,
|
20
|
+
h.engine_version,
|
21
|
+
h.allocated_storage,
|
22
|
+
h.iops
|
23
|
+
]
|
24
|
+
end
|
25
|
+
AwsPocketknife::Rds.pretty_table(headers: headers, data: data)
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "clean DB_NAME DAYS --dry_run", "Remove manual snapshots with creation time lower than DAYS for database_name."
|
29
|
+
option :dry_run, :type => :boolean, :default => true, :desc => 'just show images that would be deleted'
|
30
|
+
def clean(db_name, days)
|
31
|
+
dry_run = options.fetch("dry_run", true)
|
32
|
+
AwsPocketknife::Rds.clean_snapshots db_name: db_name,
|
33
|
+
days: days,
|
34
|
+
dry_run: dry_run
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "create DB_NAME", "Creates a snapshot for database_name."
|
38
|
+
def create(db_name)
|
39
|
+
AwsPocketknife::Rds.create_snapshot db_name: db_name
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "aws_pocketknife"
|
3
|
+
|
4
|
+
module AwsPocketknife
|
5
|
+
module Cli
|
6
|
+
class Route53 < Thor
|
7
|
+
|
8
|
+
desc "describe_hosted_zone HOSTED_ZONE", "describe hosted zone"
|
9
|
+
def describe_hosted_zone(hosted_zone)
|
10
|
+
hosted_zone = AwsPocketknife::Route53.describe_hosted_zone(hosted_zone: hosted_zone)
|
11
|
+
unless hosted_zone.nil?
|
12
|
+
AwsPocketknife::Route53.nice_print(object: hosted_zone.to_h)
|
13
|
+
else
|
14
|
+
puts "#{hosted_zone} not found"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "list", "list hosted zones"
|
19
|
+
def list
|
20
|
+
hosted_zones = AwsPocketknife::Route53.list_hosted_zones
|
21
|
+
headers = [ 'Name', 'Zone ID', 'Comment']
|
22
|
+
data = []
|
23
|
+
hosted_zones.each do |h|
|
24
|
+
data << [h.name,
|
25
|
+
AwsPocketknife::Route53.get_hosted_zone_id(hosted_zone: h.id),
|
26
|
+
h.config.comment]
|
27
|
+
end
|
28
|
+
AwsPocketknife::Route53.pretty_table(headers: headers, data: data)
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "list_records HOSTED_ZONE", "list records for hosted zone"
|
32
|
+
def list_records(hosted_zone)
|
33
|
+
records = AwsPocketknife::Route53.list_records_for_zone_name(hosted_zone_name: hosted_zone)
|
34
|
+
headers = ["Name", "Type", "DNS Name"]
|
35
|
+
data = []
|
36
|
+
if records.length > 0
|
37
|
+
records.each do |record|
|
38
|
+
if record.type == 'CNAME'
|
39
|
+
data << [record.name, record.type, record.resource_records[0].value]
|
40
|
+
else
|
41
|
+
if record.alias_target.nil?
|
42
|
+
data << [record.name, record.type, "N/A"]
|
43
|
+
else
|
44
|
+
data << [record.name, record.type, record.alias_target.dns_name]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
AwsPocketknife::Route53.pretty_table(headers: headers, data: data)
|
49
|
+
else
|
50
|
+
puts "No records found hosted zone #{hosted_zone}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'aws_pocketknife'
|
2
|
+
require 'base64'
|
3
|
+
require 'openssl'
|
4
|
+
require 'recursive-open-struct'
|
5
|
+
|
6
|
+
module AwsPocketknife
|
7
|
+
module CloudwatchLogs
|
8
|
+
|
9
|
+
class << self
|
10
|
+
include AwsPocketknife::Common::Utils
|
11
|
+
|
12
|
+
def create_log_group(log_group_name: "")
|
13
|
+
|
14
|
+
unless log_group_name.empty?
|
15
|
+
cloudwatch_logs_client.create_log_group({
|
16
|
+
log_group_name: log_group_name, # required
|
17
|
+
})
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'log4r'
|
2
|
+
|
3
|
+
module AwsPocketknife
|
4
|
+
module Common
|
5
|
+
module Logging
|
6
|
+
|
7
|
+
include Log4r
|
8
|
+
Logger = Log4r::Logger
|
9
|
+
Log4r::Logger.root.level = Log4r::INFO
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
def logger
|
14
|
+
@log ||= initialize_log
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize_log(name: "aws_pocketknife", pattern: "[%l] %d %m")
|
18
|
+
log = Logger.new(name)
|
19
|
+
|
20
|
+
log_format = Log4r::PatternFormatter.new(:pattern => pattern)
|
21
|
+
log_output = Log4r::StdoutOutputter.new 'console'
|
22
|
+
log_output.formatter = log_format
|
23
|
+
log.add(log_output)
|
24
|
+
|
25
|
+
return log
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "pretty_table"
|
2
|
+
require "awesome_print"
|
3
|
+
require_relative "logging"
|
4
|
+
|
5
|
+
module AwsPocketknife
|
6
|
+
module Common
|
7
|
+
module Utils
|
8
|
+
#include AwsPocketknife::Common::Logging
|
9
|
+
|
10
|
+
def ec2_client
|
11
|
+
@ec2_client ||= AwsPocketknife.ec2_client
|
12
|
+
end
|
13
|
+
|
14
|
+
def iam_client
|
15
|
+
@iam_client ||= AwsPocketknife.iam_client
|
16
|
+
end
|
17
|
+
|
18
|
+
def route53_client
|
19
|
+
@route53_client ||= AwsPocketknife.route53_client
|
20
|
+
end
|
21
|
+
|
22
|
+
def rds_client
|
23
|
+
@rds_client ||= AwsPocketknife.rds_client
|
24
|
+
end
|
25
|
+
|
26
|
+
def elb_client
|
27
|
+
@elb_client ||= AwsPocketknife.elb_client
|
28
|
+
end
|
29
|
+
|
30
|
+
def asg_client
|
31
|
+
@asg_client ||= AwsPocketknife.asg_client
|
32
|
+
end
|
33
|
+
|
34
|
+
def cloudwatch_logs_client
|
35
|
+
@cloudwatch_logs_client ||= AwsPocketknife.cloudwatch_logs_client
|
36
|
+
end
|
37
|
+
|
38
|
+
def elastic_beanstalk_client
|
39
|
+
@elastic_beanstalk_client ||= AwsPocketknife.elastic_beanstalk_client
|
40
|
+
end
|
41
|
+
|
42
|
+
def pretty_table(headers: [], data: [])
|
43
|
+
puts PrettyTable.new(data, headers).to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
# https://github.com/michaeldv/awesome_print
|
47
|
+
def nice_print(object: nil)
|
48
|
+
ap object
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_tag_value(tags: [], tag_key: "")
|
52
|
+
unless tags.empty? or tag_key.length == 0
|
53
|
+
tag = tags.select { |tag| tag.key == tag_key }
|
54
|
+
return tag[0].value if tag.length == 1
|
55
|
+
return "" if tag.length == 0
|
56
|
+
else
|
57
|
+
return ""
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,308 @@
|
|
1
|
+
require 'aws_pocketknife'
|
2
|
+
require 'base64'
|
3
|
+
require 'openssl'
|
4
|
+
require 'retryable'
|
5
|
+
require 'recursive-open-struct'
|
6
|
+
|
7
|
+
module AwsPocketknife
|
8
|
+
module Ec2
|
9
|
+
|
10
|
+
MAX_ATTEMPTS = 15
|
11
|
+
DELAY_SECONDS = 10
|
12
|
+
|
13
|
+
STATE_PENDING = 'pending'
|
14
|
+
STATE_AVAILABLE = 'available'
|
15
|
+
STATE_DEREGISTERED = 'deregistered'
|
16
|
+
STATE_INVALID = 'invalid'
|
17
|
+
STATE_FAILED = 'failed'
|
18
|
+
STATE_ERROR = 'error'
|
19
|
+
|
20
|
+
class << self
|
21
|
+
include AwsPocketknife::Common::Utils
|
22
|
+
#include AwsPocketknife::Common::Logging
|
23
|
+
|
24
|
+
Logging = Common::Logging.logger
|
25
|
+
|
26
|
+
def find_ami_by_name(name: '')
|
27
|
+
ec2_client.describe_images({dry_run: false,
|
28
|
+
filters: [
|
29
|
+
{
|
30
|
+
name: "tag:Name",
|
31
|
+
values: [name]
|
32
|
+
}
|
33
|
+
]}).images
|
34
|
+
end
|
35
|
+
|
36
|
+
def find_ami_by_id(id: '')
|
37
|
+
ec2_client.describe_images({dry_run: false,
|
38
|
+
image_ids: [id]}).images.first
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete_ami_by_id(id: '')
|
42
|
+
image = find_ami_by_id(id: id)
|
43
|
+
snapshot_ids = snapshot_ids(image)
|
44
|
+
ec2_client.deregister_image(image_id: id)
|
45
|
+
|
46
|
+
Retryable.retryable(:tries => 20, :sleep => lambda { |n| 2**n }, :on => StandardError) do |retries, exception|
|
47
|
+
image = find_ami_by_id(id: id)
|
48
|
+
message = "retry #{retries} - Deleting image #{id}"
|
49
|
+
message << " State: #{image.state}" if image
|
50
|
+
Logging.info message
|
51
|
+
raise StandardError unless image.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
delete_snapshots(snapshot_ids: snapshot_ids)
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete_snapshots(snapshot_ids: [])
|
58
|
+
snapshot_ids.each do |snapshot_id|
|
59
|
+
Logging.info "Deleting Snapshot: #{snapshot_id}"
|
60
|
+
ec2_client.delete_snapshot(snapshot_id: snapshot_id)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def snapshot_ids(image)
|
65
|
+
snapshot_ids = []
|
66
|
+
image.block_device_mappings.each do |device_mapping|
|
67
|
+
ebs = device_mapping.ebs
|
68
|
+
snapshot_ids << ebs.snapshot_id if ebs && !ebs.snapshot_id.to_s.empty?
|
69
|
+
end
|
70
|
+
snapshot_ids
|
71
|
+
end
|
72
|
+
|
73
|
+
def clean_ami(options)
|
74
|
+
Logging.info "options: #{options}"
|
75
|
+
|
76
|
+
dry_run = options.fetch(:dry_run, true)
|
77
|
+
image_ids = find_ami_by_creation_time(options)
|
78
|
+
images_to_delete = find_unused_ami(image_ids: image_ids)
|
79
|
+
|
80
|
+
Logging.info "images (#{image_ids.length}): #{image_ids}"
|
81
|
+
Logging.info "images to delete (#{images_to_delete.length}): #{images_to_delete}"
|
82
|
+
|
83
|
+
unless dry_run
|
84
|
+
images_to_delete.each do |image_id|
|
85
|
+
Logging.info "deleting image #{image_id}"
|
86
|
+
delete_ami_by_id(id: image_id)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
def find_unused_ami(image_ids: [])
|
93
|
+
images_to_delete = []
|
94
|
+
image_ids.each do |image_id|
|
95
|
+
# check if there is any instance using the image id
|
96
|
+
instances = describe_instances_by_image_id(image_id_list: [image_id])
|
97
|
+
if instances.empty?
|
98
|
+
images_to_delete << image_id
|
99
|
+
else
|
100
|
+
Logging.info "#{image_id} is used by instance #{instances.map { |instance| instance.instance_id }}"
|
101
|
+
end
|
102
|
+
Kernel.sleep 2
|
103
|
+
end
|
104
|
+
return images_to_delete
|
105
|
+
end
|
106
|
+
|
107
|
+
def find_ami_by_creation_time(options)
|
108
|
+
|
109
|
+
days = options.fetch(:days, '30').to_i * 24 * 3600
|
110
|
+
creation_time = Time.now-days
|
111
|
+
Logging.info "Cleaning up images older than #{days} days, i.e, with creation_time < #{creation_time})"
|
112
|
+
|
113
|
+
image_ids = []
|
114
|
+
images = find_ami_by_name(name: options.fetch(:ami_name_pattern, ''))
|
115
|
+
images.each do |image|
|
116
|
+
image_creation_time = Time.parse(image.creation_date)
|
117
|
+
msg = "image #{image.image_id} (#{creation_time}) < (image_creation_time: #{image_creation_time})? "
|
118
|
+
if creation_time <= image_creation_time
|
119
|
+
image_ids << image.image_id
|
120
|
+
msg << "YES, marking to be deleted"
|
121
|
+
else
|
122
|
+
msg << "NO"
|
123
|
+
end
|
124
|
+
Logging.info msg
|
125
|
+
end
|
126
|
+
return image_ids
|
127
|
+
end
|
128
|
+
|
129
|
+
def share_ami(image_id: '', user_id: '', options: {})
|
130
|
+
begin
|
131
|
+
options = {}
|
132
|
+
options[:image_id] = image_id
|
133
|
+
options[:launch_permission] = create_launch_permission(user_id)
|
134
|
+
Logging.info "Sharing Image #{image_id} with #{user_id} with options #{options}"
|
135
|
+
response = @ec2_client.modify_image_attribute(options=options)
|
136
|
+
return response
|
137
|
+
rescue Exception => e
|
138
|
+
Logging.error "## Got an error when sharing the image... #{e.cause} -> #{e.message}"
|
139
|
+
raise
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
def create_image(instance_id: "", name: "", description: "Created at #{Time.now}",
|
145
|
+
timeout: 1800, publish_to_account: "",
|
146
|
+
volume_type: "gp2",
|
147
|
+
iops: 3,
|
148
|
+
encrypted: false,
|
149
|
+
volume_size: 60
|
150
|
+
)
|
151
|
+
|
152
|
+
begin
|
153
|
+
Logging.info "creating image"
|
154
|
+
instance = find_by_id(instance_id: instance_id)
|
155
|
+
instance = ec2.instances[instance_id]
|
156
|
+
image = instance.create_image(name, :description => description)
|
157
|
+
sleep 2 until image.exists?
|
158
|
+
Logging.info "image #{image.id} state: #{image.state}"
|
159
|
+
sleep 10 until image.state != :pending
|
160
|
+
if image.state == :failed
|
161
|
+
raise "Create image failed"
|
162
|
+
end
|
163
|
+
Logging.info "image created"
|
164
|
+
rescue => e
|
165
|
+
Logging.error "Creating AMI failed #{e.message}"
|
166
|
+
Logging.error e.backtrace.join("\n")
|
167
|
+
raise e
|
168
|
+
end
|
169
|
+
if publish_to_account.length != 0
|
170
|
+
Logging.info "add permissions for #{publish_to_account}"
|
171
|
+
image.permissions.add(publish_to_account.gsub(/-/, ''))
|
172
|
+
end
|
173
|
+
image.id.tap do |image_id|
|
174
|
+
Logging.info "Image #{@name}[#{image_id}] created"
|
175
|
+
return image_id
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def stop_instance_by_id(instance_ids)
|
180
|
+
instance_id_list = get_instance_id_list(instance_ids: instance_ids)
|
181
|
+
Logging.info "Stoping instance id: #{instance_id_list}"
|
182
|
+
resp = ec2_client.stop_instances({ instance_ids: instance_id_list })
|
183
|
+
wait_till_instance_is_stopped(instance_id_list, max_attempts: MAX_ATTEMPTS, delay_seconds: DELAY_SECONDS)
|
184
|
+
Logging.info "Stopped ec2 instance #{instance_id_list}"
|
185
|
+
end
|
186
|
+
|
187
|
+
def start_instance_by_id(instance_ids)
|
188
|
+
instance_id_list = get_instance_id_list(instance_ids: instance_ids)
|
189
|
+
Logging.info "Start instance id: #{instance_id_list}"
|
190
|
+
ec2_client.start_instances({ instance_ids: instance_id_list })
|
191
|
+
end
|
192
|
+
|
193
|
+
# http://serverfault.com/questions/560337/search-ec2-instance-by-its-name-from-aws-command-line-tool
|
194
|
+
def find_by_name(name: "")
|
195
|
+
instances = []
|
196
|
+
resp = ec2_client.describe_instances({dry_run: false,
|
197
|
+
filters: [
|
198
|
+
{
|
199
|
+
name: "tag:Name",
|
200
|
+
values: [name]
|
201
|
+
}
|
202
|
+
]})
|
203
|
+
resp.reservations.each do |reservation|
|
204
|
+
reservation.instances.each do |instance|
|
205
|
+
instances << instance
|
206
|
+
end
|
207
|
+
end
|
208
|
+
instances
|
209
|
+
end
|
210
|
+
|
211
|
+
def describe_instances_by_image_id(image_id_list: [])
|
212
|
+
instances = []
|
213
|
+
resp = ec2_client.describe_instances({dry_run: false,
|
214
|
+
filters: [
|
215
|
+
{
|
216
|
+
name: "image-id",
|
217
|
+
values: image_id_list
|
218
|
+
}
|
219
|
+
]})
|
220
|
+
resp.reservations.each do |reservation|
|
221
|
+
reservation.instances.each do |instance|
|
222
|
+
instances << instance
|
223
|
+
end
|
224
|
+
end
|
225
|
+
instances
|
226
|
+
end
|
227
|
+
|
228
|
+
def find_by_id(instance_id: "")
|
229
|
+
resp = ec2_client.describe_instances({dry_run: false, instance_ids: [instance_id.to_s]})
|
230
|
+
if resp.nil? or resp.reservations.length == 0 or resp.reservations[0].instances.length == 0
|
231
|
+
return nil
|
232
|
+
else
|
233
|
+
return resp.reservations.first.instances.first
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def get_windows_password(instance_id: "")
|
238
|
+
|
239
|
+
private_keyfile_dir = ENV["AWS_POCKETKNIFE_KEYFILE_DIR"] || ""
|
240
|
+
raise "Environment variable AWS_POCKETKNIFE_KEYFILE_DIR is not defined" if private_keyfile_dir.length == 0
|
241
|
+
|
242
|
+
instance = find_by_id(instance_id: instance_id)
|
243
|
+
key_name = instance.key_name
|
244
|
+
private_keyfile = File.join(private_keyfile_dir, "#{key_name}.pem")
|
245
|
+
raise "File #{private_keyfile} not found" unless File.exist?(private_keyfile)
|
246
|
+
|
247
|
+
resp = ec2_client.get_password_data({dry_run: false,
|
248
|
+
instance_id: instance_id})
|
249
|
+
encrypted_password = resp.password_data
|
250
|
+
decrypted_password = decrypt_windows_password(encrypted_password, private_keyfile)
|
251
|
+
|
252
|
+
RecursiveOpenStruct.new({password: decrypted_password,
|
253
|
+
instance_id: instance.instance_id,
|
254
|
+
private_ip_address: instance.private_ip_address,
|
255
|
+
public_ip_address: instance.public_ip_address}, recurse_over_arrays: true)
|
256
|
+
end
|
257
|
+
|
258
|
+
# def ec2
|
259
|
+
# @ec2 ||= Aws::EC2.new(:ec2_endpoint => "ec2.#{AwsPocketknife::AWS_REGION}.amazonaws.com")
|
260
|
+
# end
|
261
|
+
|
262
|
+
private
|
263
|
+
|
264
|
+
def create_launch_permission(user_id)
|
265
|
+
{
|
266
|
+
add: [
|
267
|
+
{
|
268
|
+
user_id: user_id
|
269
|
+
},
|
270
|
+
]
|
271
|
+
}
|
272
|
+
end
|
273
|
+
|
274
|
+
# Decrypts an encrypted password using a provided RSA
|
275
|
+
# private key file (PEM-format).
|
276
|
+
def decrypt_windows_password(encrypted_password, private_keyfile)
|
277
|
+
encrypted_password_bytes = Base64.decode64(encrypted_password)
|
278
|
+
private_keydata = File.open(private_keyfile, "r").read
|
279
|
+
private_key = OpenSSL::PKey::RSA.new(private_keydata)
|
280
|
+
private_key.private_decrypt(encrypted_password_bytes)
|
281
|
+
end
|
282
|
+
|
283
|
+
def get_instance_id_list(instance_ids: "")
|
284
|
+
instance_ids.strip.split(";")
|
285
|
+
end
|
286
|
+
|
287
|
+
def wait_till_instance_is_stopped(instance_ids, max_attempts: 12, delay_seconds: 10)
|
288
|
+
total_wait_seconds = max_attempts * delay_seconds;
|
289
|
+
Logging.info "Waiting up to #{total_wait_seconds} seconds with #{delay_seconds} seconds delay for ec2 instance #{instance_ids} to be stopped"
|
290
|
+
ec2_client.wait_until(:instance_stopped, { instance_ids: instance_ids }) do |w|
|
291
|
+
w.max_attempts = max_attempts
|
292
|
+
w.delay = delay_seconds
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
def wait_till_instance_is_terminated(instance_ids, max_attempts: 12, delay_seconds: 10)
|
297
|
+
total_wait_seconds = max_attempts * delay_seconds;
|
298
|
+
Logging.info "Waiting up to #{total_wait_seconds} seconds with #{delay_seconds} seconds delay for ec2 instance #{instance_ids} to be terminated"
|
299
|
+
ec2_client.wait_until(:instance_terminated, { instance_ids: instance_ids }) do |w|
|
300
|
+
w.max_attempts = max_attempts
|
301
|
+
w.delay = delay_seconds
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
end
|
306
|
+
|
307
|
+
end
|
308
|
+
end
|