aws_pocketknife 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|