kontena-cli 0.12.0 → 0.12.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/kontena/cli/grids/common.rb +15 -4
- data/lib/kontena/cli/grids/list_command.rb +1 -1
- data/lib/kontena/cli/master/aws/create_command.rb +7 -7
- data/lib/kontena/cli/master/users/remove_command.rb +22 -0
- data/lib/kontena/cli/master/users_command.rb +3 -1
- data/lib/kontena/cli/nodes/aws/create_command.rb +3 -3
- data/lib/kontena/cli/nodes/aws/terminate_command.rb +1 -1
- data/lib/kontena/cli/services/containers_command.rb +1 -1
- data/lib/kontena/cli/vault/list_command.rb +4 -2
- data/lib/kontena/machine/aws.rb +3 -3
- data/lib/kontena/machine/aws/common.rb +17 -1
- data/lib/kontena/machine/aws/master_provisioner.rb +85 -64
- data/lib/kontena/machine/aws/node_destroyer.rb +17 -5
- data/lib/kontena/machine/aws/node_provisioner.rb +103 -63
- data/lib/kontena/machine/cert_helper.rb +39 -0
- data/spec/kontena/cli/master/users/remove_command_spec.rb +28 -0
- data/spec/kontena/cli/services/containers_command_spec.rb +48 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b4c7616444c2dfd31cc66c625d49168d8ab8623d
|
4
|
+
data.tar.gz: f28c78f100bed2695c99b12d5ed757dfd0e4eaa3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b594019efe570386ebbf644888636a28f31ed88c0fe01425322dce0057fd232f1707d70e82899aefac3576a179ebccc87726a4b90e53a5c473bc354320947dcb
|
7
|
+
data.tar.gz: 9202690e4f3cff809576272a11108351266b97c421c189fbfe742c4f7d0ccc1c90e143a182f754c09a586f37a4f1a23a33724ea8f848352f0167fce50045c06f
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.12.
|
1
|
+
0.12.1
|
@@ -21,10 +21,8 @@ module Kontena::Cli::Grids
|
|
21
21
|
puts " load: #{(loads[:'1m'] || 0.0).round(2)} #{(loads[:'5m'] || 0.0).round(2)} #{(loads[:'15m'] || 0.0).round(2)}"
|
22
22
|
|
23
23
|
mem_total = nodes.map{|n| n['mem_total'].to_i}.inject(:+)
|
24
|
-
|
25
|
-
|
26
|
-
}.inject(:+)
|
27
|
-
puts " memory: #{to_gigabytes(mem_wired)} of #{to_gigabytes(mem_total)} GB"
|
24
|
+
mem_used = calculate_mem_used(nodes)
|
25
|
+
puts " memory: #{to_gigabytes(mem_used)} of #{to_gigabytes(mem_total)} GB"
|
28
26
|
|
29
27
|
total_fs = calculate_filesystem_stats(nodes)
|
30
28
|
puts " filesystem: #{to_gigabytes(total_fs['used'])} of #{to_gigabytes(total_fs['total'])} GB"
|
@@ -55,6 +53,19 @@ module Kontena::Cli::Grids
|
|
55
53
|
loads
|
56
54
|
end
|
57
55
|
|
56
|
+
# @param [Array<Hash>] nodes
|
57
|
+
# @return [Float]
|
58
|
+
def calculate_mem_used(nodes)
|
59
|
+
nodes.map{|n|
|
60
|
+
mem = n.dig('resource_usage', 'memory')
|
61
|
+
if mem
|
62
|
+
mem['used'] - (mem['cached'] + mem['buffers'])
|
63
|
+
else
|
64
|
+
0.0
|
65
|
+
end
|
66
|
+
}.inject(:+)
|
67
|
+
end
|
68
|
+
|
58
69
|
# @param [Array<Hash>] nodes
|
59
70
|
# @return [Hash]
|
60
71
|
def calculate_filesystem_stats(nodes)
|
@@ -9,7 +9,7 @@ module Kontena::Cli::Grids
|
|
9
9
|
require_api_url
|
10
10
|
|
11
11
|
if grids['grids'].size == 0
|
12
|
-
puts "You don't have any grids yet. Create first one with 'kontena
|
12
|
+
puts "You don't have any grids yet. Create first one with 'kontena grid create' command".colorize(:yellow)
|
13
13
|
end
|
14
14
|
|
15
15
|
puts '%-30.30s %-8s %-12s %-10s' % ['Name', 'Nodes', 'Services', 'Users']
|
@@ -6,18 +6,18 @@ module Kontena::Cli::Master::Aws
|
|
6
6
|
|
7
7
|
option "--access-key", "ACCESS_KEY", "AWS access key ID", required: true
|
8
8
|
option "--secret-key", "SECRET_KEY", "AWS secret key", required: true
|
9
|
-
option "--key-pair", "KEY_PAIR", "EC2
|
10
|
-
option "--ssl-cert", "SSL CERT", "SSL certificate file"
|
9
|
+
option "--key-pair", "KEY_PAIR", "EC2 key pair name", required: true
|
10
|
+
option "--ssl-cert", "SSL CERT", "SSL certificate file (default: generate self-signed cert)"
|
11
11
|
option "--region", "REGION", "EC2 Region", default: 'eu-west-1'
|
12
12
|
option "--zone", "ZONE", "EC2 Availability Zone", default: 'a'
|
13
|
-
option "--vpc-id", "VPC ID", "Virtual Private Cloud (VPC) ID"
|
14
|
-
option "--subnet-id", "SUBNET ID", "VPC option to specify subnet to launch instance into"
|
13
|
+
option "--vpc-id", "VPC ID", "Virtual Private Cloud (VPC) ID (default: default vpc)"
|
14
|
+
option "--subnet-id", "SUBNET ID", "VPC option to specify subnet to launch instance into (default: first subnet from vpc/az)"
|
15
15
|
option "--type", "SIZE", "Instance type", default: 't2.small'
|
16
16
|
option "--storage", "STORAGE", "Storage size (GiB)", default: '30'
|
17
|
-
option "--vault-secret", "VAULT_SECRET", "Secret key for Vault"
|
18
|
-
option "--vault-iv", "VAULT_IV", "Initialization vector for Vault"
|
17
|
+
option "--vault-secret", "VAULT_SECRET", "Secret key for Vault (default: generate random secret)"
|
18
|
+
option "--vault-iv", "VAULT_IV", "Initialization vector for Vault (default: generate random iv)"
|
19
19
|
option "--version", "VERSION", "Define installed Kontena version", default: 'latest'
|
20
|
-
option "--auth-provider-url", "AUTH_PROVIDER_URL", "Define authentication provider url"
|
20
|
+
option "--auth-provider-url", "AUTH_PROVIDER_URL", "Define authentication provider url (optional)"
|
21
21
|
|
22
22
|
def execute
|
23
23
|
require 'kontena/machine/aws'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative '../../common'
|
2
|
+
|
3
|
+
module Kontena::Cli::Master::Users
|
4
|
+
class RemoveCommand < Clamp::Command
|
5
|
+
include Kontena::Cli::Common
|
6
|
+
|
7
|
+
parameter "EMAIL ...", "List of emails"
|
8
|
+
|
9
|
+
def execute
|
10
|
+
require_api_url
|
11
|
+
token = require_token
|
12
|
+
email_list.each do |email|
|
13
|
+
begin
|
14
|
+
client(token).delete("users/#{email}")
|
15
|
+
rescue => exc
|
16
|
+
STDERR.puts "Failed to remove user #{email}".colorize(:red)
|
17
|
+
STDERR.puts exc.message
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,12 +1,14 @@
|
|
1
1
|
module Kontena::Cli::Master
|
2
2
|
|
3
|
-
require_relative 'users/add_role_command'
|
4
3
|
require_relative 'users/invite_command'
|
4
|
+
require_relative 'users/remove_command'
|
5
5
|
require_relative 'users/list_command'
|
6
|
+
require_relative 'users/add_role_command'
|
6
7
|
require_relative 'users/remove_role_command'
|
7
8
|
|
8
9
|
class UsersCommand < Clamp::Command
|
9
10
|
subcommand "invite", "Invite user to Kontena Master", Users::InviteCommand
|
11
|
+
subcommand ["remove", "rm"], "Remove user from Kontena Master", Users::RemoveCommand
|
10
12
|
subcommand ["list", "ls"], "List users", Users::ListCommand
|
11
13
|
subcommand "add-role", "Add role to user", Users::AddRoleCommand
|
12
14
|
subcommand "remove-role", "Remove role from user", Users::RemoveRoleCommand
|
@@ -6,11 +6,11 @@ module Kontena::Cli::Nodes::Aws
|
|
6
6
|
parameter "[NAME]", "Node name"
|
7
7
|
option "--access-key", "ACCESS_KEY", "AWS access key ID", required: true
|
8
8
|
option "--secret-key", "SECRET_KEY", "AWS secret key", required: true
|
9
|
+
option "--key-pair", "KEY_PAIR", "EC2 Key Pair", required: true
|
9
10
|
option "--region", "REGION", "EC2 Region", default: 'eu-west-1'
|
10
11
|
option "--zone", "ZONE", "EC2 Availability Zone", default: 'a'
|
11
|
-
option "--vpc-id", "VPC ID", "Virtual Private Cloud (VPC) ID"
|
12
|
-
option "--subnet-id", "SUBNET ID", "VPC option to specify subnet to launch instance into"
|
13
|
-
option "--key-pair", "KEY_PAIR", "EC2 Key Pair", required: true
|
12
|
+
option "--vpc-id", "VPC ID", "Virtual Private Cloud (VPC) ID (default: default vpc)"
|
13
|
+
option "--subnet-id", "SUBNET ID", "VPC option to specify subnet to launch instance into (default: first subnet in vpc/az)"
|
14
14
|
option "--type", "SIZE", "Instance type", default: 't2.small'
|
15
15
|
option "--storage", "STORAGE", "Storage size (GiB)", default: '30'
|
16
16
|
option "--version", "VERSION", "Define installed Kontena version", default: 'latest'
|
@@ -6,7 +6,7 @@ module Kontena::Cli::Nodes::Aws
|
|
6
6
|
parameter "NAME", "Node name"
|
7
7
|
option "--access-key", "ACCESS_KEY", "AWS access key ID", required: true
|
8
8
|
option "--secret-key", "SECRET_KEY", "AWS secret key", required: true
|
9
|
-
option "--region", "REGION", "EC2 Region",
|
9
|
+
option "--region", "REGION", "EC2 Region", default: 'eu-west-1'
|
10
10
|
|
11
11
|
def execute
|
12
12
|
require_api_url
|
@@ -18,7 +18,7 @@ module Kontena::Cli::Services
|
|
18
18
|
puts " rev: #{container['deploy_rev']}"
|
19
19
|
puts " node: #{container['node']['name']}"
|
20
20
|
puts " dns: #{container['name']}.#{current_grid}.kontena.local"
|
21
|
-
puts " ip: #{container['overlay_cidr'].split('/')[0]}"
|
21
|
+
puts " ip: #{container['overlay_cidr'].to_s.split('/')[0]}"
|
22
22
|
puts " public ip: #{container['node']['public_ip']}"
|
23
23
|
if container['status'] == 'unknown'
|
24
24
|
puts " status: #{container['status'].colorize(:yellow)}"
|
@@ -7,9 +7,11 @@ module Kontena::Cli::Vault
|
|
7
7
|
require_api_url
|
8
8
|
token = require_token
|
9
9
|
result = client(token).get("grids/#{current_grid}/secrets")
|
10
|
-
|
10
|
+
|
11
|
+
column_width_paddings = '%-54s %-25.25s'
|
12
|
+
puts column_width_paddings % ['NAME', 'CREATED AT']
|
11
13
|
result['secrets'].each do |secret|
|
12
|
-
puts
|
14
|
+
puts column_width_paddings % [secret['name'], secret['created_at']]
|
13
15
|
end
|
14
16
|
end
|
15
17
|
end
|
data/lib/kontena/machine/aws.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
begin
|
2
|
-
require "
|
2
|
+
require "aws-sdk"
|
3
3
|
rescue LoadError
|
4
4
|
puts "It seems that you don't have gem for AWS API installed."
|
5
|
-
puts "Install it using: gem install
|
5
|
+
puts "Install it using: gem install aws-sdk"
|
6
6
|
exit 1
|
7
7
|
end
|
8
8
|
|
9
9
|
require_relative 'random_name'
|
10
|
+
require_relative 'cert_helper'
|
10
11
|
require_relative 'aws/master_provisioner'
|
11
12
|
require_relative 'aws/node_provisioner'
|
12
13
|
require_relative 'aws/node_destroyer'
|
13
|
-
|
@@ -20,7 +20,23 @@ module Kontena
|
|
20
20
|
}
|
21
21
|
images[region]
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
|
+
# @param [String] vpc_id
|
25
|
+
# @param [String] zone
|
26
|
+
# @return [Aws::EC2::Types::Subnet, NilClass]
|
27
|
+
def default_subnet(vpc_id, zone)
|
28
|
+
ec2.subnets({
|
29
|
+
filters: [
|
30
|
+
{name: "vpc-id", values: [vpc_id]},
|
31
|
+
{name: "availability-zone", values: [zone]}
|
32
|
+
]
|
33
|
+
}).first
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Aws::EC2::Types::Vpc, NilClass]
|
37
|
+
def default_vpc
|
38
|
+
ec2.vpcs({filters: [{name: "is-default", values: ["true"]}]}).first
|
39
|
+
end
|
24
40
|
end
|
25
41
|
end
|
26
42
|
end
|
@@ -10,35 +10,37 @@ module Kontena
|
|
10
10
|
class MasterProvisioner
|
11
11
|
include RandomName
|
12
12
|
include Common
|
13
|
-
|
13
|
+
include Machine::CertHelper
|
14
|
+
attr_reader :ec2, :http_client, :region
|
14
15
|
|
15
16
|
# @param [String] access_key_id aws_access_key_id
|
16
17
|
# @param [String] secret_key aws_secret_access_key
|
17
18
|
# @param [String] region
|
18
19
|
def initialize(access_key_id, secret_key, region)
|
19
|
-
@
|
20
|
-
:
|
21
|
-
:aws_access_key_id => access_key_id,
|
22
|
-
:aws_secret_access_key => secret_key,
|
23
|
-
:region => region
|
20
|
+
@ec2 = ::Aws::EC2::Resource.new(
|
21
|
+
region: region, credentials: ::Aws::Credentials.new(access_key_id, secret_key)
|
24
22
|
)
|
25
23
|
end
|
26
24
|
|
27
25
|
# @param [Hash] opts
|
28
26
|
def run!(opts)
|
27
|
+
ssl_cert = nil
|
29
28
|
if opts[:ssl_cert]
|
30
29
|
abort('Invalid ssl cert') unless File.exists?(File.expand_path(opts[:ssl_cert]))
|
31
30
|
ssl_cert = File.read(File.expand_path(opts[:ssl_cert]))
|
31
|
+
else
|
32
|
+
ShellSpinner "Generating self-signed SSL certificate" do
|
33
|
+
ssl_cert = generate_self_signed_cert
|
34
|
+
end
|
32
35
|
end
|
33
36
|
|
34
|
-
ami = resolve_ami(
|
37
|
+
ami = resolve_ami(region)
|
35
38
|
abort('No valid AMI found for region') unless ami
|
36
|
-
opts[:vpc] = default_vpc.
|
39
|
+
opts[:vpc] = default_vpc.vpc_id unless opts[:vpc]
|
37
40
|
if opts[:subnet].nil?
|
38
|
-
subnet = default_subnet(opts[:vpc],
|
39
|
-
opts[:subnet] = subnet.subnet_id
|
41
|
+
subnet = default_subnet(opts[:vpc], region+opts[:zone])
|
40
42
|
else
|
41
|
-
subnet =
|
43
|
+
subnet = ec2.subnet(opts[:subnet])
|
42
44
|
end
|
43
45
|
userdata_vars = {
|
44
46
|
ssl_cert: ssl_cert,
|
@@ -50,41 +52,39 @@ module Kontena
|
|
50
52
|
|
51
53
|
security_group = ensure_security_group(opts[:vpc])
|
52
54
|
name = generate_name
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
55
|
+
ec2_instance = ec2.create_instances({
|
56
|
+
image_id: ami,
|
57
|
+
min_count: 1,
|
58
|
+
max_count: 1,
|
59
|
+
instance_type: opts[:type],
|
60
|
+
security_group_ids: [security_group.group_id],
|
61
|
+
key_name: opts[:key_pair],
|
62
|
+
subnet_id: subnet.subnet_id,
|
63
|
+
user_data: Base64.encode64(user_data(userdata_vars)),
|
64
|
+
block_device_mappings: [
|
65
|
+
{
|
66
|
+
device_name: '/dev/xvda',
|
67
|
+
virtual_name: 'Root',
|
68
|
+
ebs: {
|
69
|
+
volume_size: opts[:storage],
|
70
|
+
volume_type: 'gp2'
|
71
|
+
}
|
72
|
+
}
|
73
|
+
]
|
74
|
+
}).first
|
75
|
+
ec2_instance.create_tags({
|
76
|
+
tags: [
|
77
|
+
{key: 'Name', value: name}
|
78
|
+
]
|
79
|
+
})
|
75
80
|
ShellSpinner "Creating AWS instance #{name.colorize(:cyan)} " do
|
76
|
-
|
77
|
-
end
|
78
|
-
if opts[:ssl_cert]
|
79
|
-
master_url = "https://#{instance.public_ip_address}"
|
80
|
-
else
|
81
|
-
master_url = "http://#{instance.public_ip_address}"
|
81
|
+
sleep 5 until ec2_instance.reload.state.name == 'running'
|
82
82
|
end
|
83
|
+
master_url = "https://#{ec2_instance.public_ip_address}"
|
83
84
|
Excon.defaults[:ssl_verify_peer] = false
|
84
|
-
|
85
|
-
|
85
|
+
http_client = Excon.new(master_url, :connect_timeout => 10)
|
86
86
|
ShellSpinner "Waiting for #{name.colorize(:cyan)} to start" do
|
87
|
-
sleep 5 until master_running?
|
87
|
+
sleep 5 until master_running?(http_client)
|
88
88
|
end
|
89
89
|
|
90
90
|
puts "Kontena Master is now running at #{master_url}"
|
@@ -92,38 +92,57 @@ module Kontena
|
|
92
92
|
end
|
93
93
|
|
94
94
|
##
|
95
|
-
# @param [String]
|
96
|
-
# @return
|
95
|
+
# @param [String] vpc_id
|
96
|
+
# @return [Aws::EC2::SecurityGroup]
|
97
97
|
def ensure_security_group(vpc_id)
|
98
98
|
group_name = "kontena_master"
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
99
|
+
sg = ec2.security_groups({
|
100
|
+
filters: [
|
101
|
+
{name: 'group-name', values: [group_name]},
|
102
|
+
{name: 'vpc-id', values: [vpc_id]}
|
103
|
+
]
|
104
|
+
}).first
|
105
|
+
unless sg
|
106
|
+
ShellSpinner "Creating AWS security group" do
|
107
|
+
sg = create_security_group(group_name, vpc_id)
|
108
|
+
end
|
103
109
|
end
|
110
|
+
sg
|
104
111
|
end
|
105
112
|
|
106
113
|
##
|
107
114
|
# creates security_group and authorizes default port ranges
|
108
115
|
#
|
109
116
|
# @param [String] name
|
110
|
-
# @
|
117
|
+
# @param [String, NilClass] vpc_id
|
118
|
+
# @return Aws::EC2::SecurityGroup
|
111
119
|
def create_security_group(name, vpc_id = nil)
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
120
|
+
sg = ec2.create_security_group({
|
121
|
+
group_name: name,
|
122
|
+
description: "Kontena Master",
|
123
|
+
vpc_id: vpc_id
|
124
|
+
})
|
125
|
+
|
126
|
+
sg.authorize_ingress({
|
127
|
+
ip_protocol: 'tcp',
|
128
|
+
from_port: 443,
|
129
|
+
to_port: 443,
|
130
|
+
cidr_ip: '0.0.0.0/0'
|
131
|
+
})
|
132
|
+
|
133
|
+
sg.authorize_ingress({
|
134
|
+
ip_protocol: 'tcp',
|
135
|
+
from_port: 22,
|
136
|
+
to_port: 22,
|
137
|
+
cidr_ip: '0.0.0.0/0'
|
138
|
+
})
|
139
|
+
|
140
|
+
sg
|
119
141
|
end
|
120
142
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
def default_vpc
|
126
|
-
client.vpcs.all('isDefault' => true).first
|
143
|
+
# @return [String]
|
144
|
+
def region
|
145
|
+
ec2.client.config.region
|
127
146
|
end
|
128
147
|
|
129
148
|
def user_data(vars)
|
@@ -135,14 +154,16 @@ module Kontena
|
|
135
154
|
"kontena-master-#{super}-#{rand(1..99)}"
|
136
155
|
end
|
137
156
|
|
138
|
-
def master_running?
|
157
|
+
def master_running?(http_client)
|
139
158
|
http_client.get(path: '/').status == 200
|
140
159
|
rescue
|
141
160
|
false
|
142
161
|
end
|
143
162
|
|
144
163
|
def erb(template, vars)
|
145
|
-
ERB.new(template).result(
|
164
|
+
ERB.new(template).result(
|
165
|
+
OpenStruct.new(vars).instance_eval { binding }
|
166
|
+
)
|
146
167
|
end
|
147
168
|
end
|
148
169
|
end
|
@@ -5,7 +5,7 @@ module Kontena
|
|
5
5
|
module Aws
|
6
6
|
class NodeDestroyer
|
7
7
|
|
8
|
-
attr_reader :
|
8
|
+
attr_reader :ec2, :api_client
|
9
9
|
|
10
10
|
# @param [Kontena::Client] api_client Kontena api client
|
11
11
|
# @param [String] access_key_id aws_access_key_id
|
@@ -13,15 +13,27 @@ module Kontena
|
|
13
13
|
# @param [String] region
|
14
14
|
def initialize(api_client, access_key_id, secret_key, region = 'eu-west-1')
|
15
15
|
@api_client = api_client
|
16
|
-
@
|
16
|
+
@ec2 = ::Aws::EC2::Resource.new(
|
17
|
+
region: region,
|
18
|
+
credentials: ::Aws::Credentials.new(access_key_id, secret_key)
|
19
|
+
)
|
17
20
|
end
|
18
21
|
|
19
22
|
def run!(grid, name)
|
20
|
-
|
23
|
+
instances = ec2.instances({
|
24
|
+
filters: [
|
25
|
+
{name: 'tag:Name', values: [name]}
|
26
|
+
]
|
27
|
+
})
|
28
|
+
abort("Cannot find AWS instance #{name}") if instances.to_a.size == 0
|
29
|
+
abort("There are multiple instances with name #{name}") if instances.to_a.size > 1
|
30
|
+
instance = instances.first
|
21
31
|
if instance
|
22
32
|
ShellSpinner "Terminating AWS instance #{name.colorize(:cyan)} " do
|
23
|
-
instance.
|
24
|
-
|
33
|
+
instance.terminate
|
34
|
+
until instance.reload.state.name.to_s == 'terminated'
|
35
|
+
sleep 2
|
36
|
+
end
|
25
37
|
end
|
26
38
|
else
|
27
39
|
abort "Cannot find instance #{name.colorize(:cyan)} in AWS"
|
@@ -11,7 +11,7 @@ module Kontena
|
|
11
11
|
include RandomName
|
12
12
|
include Common
|
13
13
|
|
14
|
-
attr_reader :
|
14
|
+
attr_reader :ec2, :api_client
|
15
15
|
|
16
16
|
# @param [Kontena::Client] api_client Kontena api client
|
17
17
|
# @param [String] access_key_id aws_access_key_id
|
@@ -19,106 +19,141 @@ module Kontena
|
|
19
19
|
# @param [String] region
|
20
20
|
def initialize(api_client, access_key_id, secret_key, region)
|
21
21
|
@api_client = api_client
|
22
|
-
@
|
23
|
-
:
|
24
|
-
:aws_secret_access_key => secret_key, :region => region
|
22
|
+
@ec2 = ::Aws::EC2::Resource.new(
|
23
|
+
region: region, credentials: ::Aws::Credentials.new(access_key_id, secret_key)
|
25
24
|
)
|
26
25
|
end
|
27
26
|
|
28
27
|
# @param [Hash] opts
|
29
28
|
def run!(opts)
|
30
|
-
ami = resolve_ami(
|
29
|
+
ami = resolve_ami(region)
|
31
30
|
abort('No valid AMI found for region') unless ami
|
32
31
|
|
32
|
+
opts[:vpc] = default_vpc.vpc_id unless opts[:vpc]
|
33
|
+
|
33
34
|
security_group = ensure_security_group(opts[:grid], opts[:vpc])
|
34
35
|
name = opts[:name ] || generate_name
|
35
36
|
|
36
|
-
|
37
|
+
|
37
38
|
if opts[:subnet].nil?
|
38
|
-
subnet = default_subnet(opts[:vpc],
|
39
|
-
opts[:subnet] = subnet.subnet_id
|
39
|
+
subnet = default_subnet(opts[:vpc], region+opts[:zone])
|
40
40
|
else
|
41
|
-
subnet =
|
41
|
+
subnet = ec2.subnet(opts[:subnet])
|
42
42
|
end
|
43
43
|
dns_server = aws_dns_supported?(opts[:vpc]) ? '169.254.169.253' : '8.8.8.8'
|
44
44
|
userdata_vars = {
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
45
|
+
name: name,
|
46
|
+
version: opts[:version],
|
47
|
+
master_uri: opts[:master_uri],
|
48
|
+
grid_token: opts[:grid_token],
|
49
|
+
dns_server: dns_server
|
50
50
|
}
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
52
|
+
ec2_instance = ec2.create_instances({
|
53
|
+
image_id: ami,
|
54
|
+
min_count: 1,
|
55
|
+
max_count: 1,
|
56
|
+
instance_type: opts[:type],
|
57
|
+
security_group_ids: [security_group.group_id],
|
58
|
+
key_name: opts[:key_pair],
|
59
|
+
subnet_id: subnet.subnet_id,
|
60
|
+
user_data: Base64.encode64(user_data(userdata_vars)),
|
61
|
+
block_device_mappings: [
|
62
|
+
{
|
63
|
+
device_name: '/dev/xvda',
|
64
|
+
virtual_name: 'Root',
|
65
|
+
ebs: {
|
66
|
+
volume_size: opts[:storage],
|
67
|
+
volume_type: 'gp2'
|
68
|
+
}
|
69
|
+
}
|
70
|
+
]
|
71
|
+
}).first
|
72
|
+
ec2_instance.create_tags({
|
73
|
+
tags: [
|
74
|
+
{key: 'Name', value: name},
|
75
|
+
{key: 'kontena_grid', value: opts[:grid]}
|
76
|
+
]
|
77
|
+
})
|
72
78
|
|
73
|
-
instance = client.servers.get(instance_id)
|
74
79
|
ShellSpinner "Creating AWS instance #{name.colorize(:cyan)} " do
|
75
|
-
|
80
|
+
sleep 5 until ec2_instance.reload.state.name == 'running'
|
76
81
|
end
|
77
|
-
client.create_tags(instance.id, {'Name' => name, 'kontena_grid' => opts[:grid]})
|
78
82
|
node = nil
|
79
83
|
ShellSpinner "Waiting for node #{name.colorize(:cyan)} join to grid #{opts[:grid].colorize(:cyan)} " do
|
80
84
|
sleep 2 until node = instance_exists_in_grid?(opts[:grid], name)
|
81
85
|
end
|
82
|
-
labels = ["region=#{
|
86
|
+
labels = ["region=#{region}", "az=#{opts[:zone]}"]
|
83
87
|
set_labels(node, labels)
|
84
88
|
end
|
85
89
|
|
86
90
|
##
|
87
91
|
# @param [String] grid
|
88
|
-
# @return
|
92
|
+
# @return [Aws::EC2::SecurityGroup]
|
89
93
|
def ensure_security_group(grid, vpc_id)
|
90
94
|
group_name = "kontena_grid_#{grid}"
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
+
sg = ec2.security_groups({
|
96
|
+
filters: [
|
97
|
+
{name: 'group-name', values: [group_name]},
|
98
|
+
{name: 'vpc-id', values: [vpc_id]}
|
99
|
+
]
|
100
|
+
}).first
|
101
|
+
unless sg
|
102
|
+
ShellSpinner "Creating AWS security group" do
|
103
|
+
sg = create_security_group(group_name, vpc_id)
|
104
|
+
end
|
95
105
|
end
|
106
|
+
sg
|
96
107
|
end
|
97
108
|
|
98
109
|
##
|
99
110
|
# creates security_group and authorizes default port ranges
|
100
111
|
#
|
101
112
|
# @param [String] name
|
102
|
-
# @
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
113
|
+
# @param [String] vpc_id
|
114
|
+
# @return [Aws::EC2::SecurityGroup]
|
115
|
+
def create_security_group(name, vpc_id)
|
116
|
+
sg = ec2.create_security_group({
|
117
|
+
group_name: name,
|
118
|
+
description: "Kontena Grid",
|
119
|
+
vpc_id: vpc_id
|
120
|
+
})
|
121
|
+
|
122
|
+
sg.authorize_ingress({ # SSH
|
123
|
+
ip_protocol: 'tcp', from_port: 22, to_port: 22, cidr_ip: '0.0.0.0/0'
|
124
|
+
})
|
125
|
+
sg.authorize_ingress({ # HTTP
|
126
|
+
ip_protocol: 'tcp', from_port: 80, to_port: 80, cidr_ip: '0.0.0.0/0'
|
127
|
+
})
|
128
|
+
sg.authorize_ingress({ # HTTPS
|
129
|
+
ip_protocol: 'tcp', from_port: 443, to_port: 443, cidr_ip: '0.0.0.0/0'
|
130
|
+
})
|
131
|
+
sg.authorize_ingress({ # OpenVPN
|
132
|
+
ip_protocol: 'udp', from_port: 1194, to_port: 1194, cidr_ip: '0.0.0.0/0'
|
133
|
+
})
|
134
|
+
sg.authorize_ingress({ # Overlay / Weave network
|
135
|
+
ip_permissions: [
|
136
|
+
{
|
137
|
+
from_port: 6783, to_port: 6783, ip_protocol: 'tcp',
|
138
|
+
user_id_group_pairs: [
|
139
|
+
{ group_id: sg.group_id, vpc_id: vpc_id }
|
140
|
+
]
|
141
|
+
},
|
142
|
+
{
|
143
|
+
from_port: 6783, to_port: 6784, ip_protocol: 'udp',
|
144
|
+
user_id_group_pairs: [
|
145
|
+
{ group_id: sg.group_id, vpc_id: vpc_id }
|
146
|
+
]
|
147
|
+
}
|
148
|
+
]
|
149
|
+
})
|
150
|
+
|
151
|
+
sg
|
118
152
|
end
|
119
153
|
|
120
|
-
|
121
|
-
|
154
|
+
# @return [String]
|
155
|
+
def region
|
156
|
+
ec2.client.config.region
|
122
157
|
end
|
123
158
|
|
124
159
|
def user_data(vars)
|
@@ -138,15 +173,20 @@ module Kontena
|
|
138
173
|
ERB.new(template).result(OpenStruct.new(vars).instance_eval { binding })
|
139
174
|
end
|
140
175
|
|
176
|
+
# @param [Hash] node
|
177
|
+
# @param [Array<String>] labels
|
141
178
|
def set_labels(node, labels)
|
142
179
|
data = {}
|
143
180
|
data[:labels] = labels
|
144
181
|
api_client.put("nodes/#{node['id']}", data, {}, {'Kontena-Grid-Token' => node['grid']['token']})
|
145
182
|
end
|
146
183
|
|
184
|
+
# @param [String] vpc_id
|
185
|
+
# @return [Boolean]
|
147
186
|
def aws_dns_supported?(vpc_id)
|
148
|
-
|
149
|
-
response.
|
187
|
+
vpc = ec2.vpc(vpc_id)
|
188
|
+
response = vpc.describe_attribute({attribute: 'enableDnsSupport'})
|
189
|
+
response.enable_dns_support
|
150
190
|
end
|
151
191
|
end
|
152
192
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Kontena
|
4
|
+
module Machine
|
5
|
+
module CertHelper
|
6
|
+
|
7
|
+
def generate_self_signed_cert
|
8
|
+
key = OpenSSL::PKey::RSA.new(2048)
|
9
|
+
public_key = key.public_key
|
10
|
+
|
11
|
+
subject = "/C=FI/O=Test/OU=Test/CN=Test"
|
12
|
+
|
13
|
+
cert = OpenSSL::X509::Certificate.new
|
14
|
+
cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
|
15
|
+
cert.not_before = Time.now
|
16
|
+
cert.not_after = Time.now + (10 * 365 * 24 * 60 * 60)
|
17
|
+
cert.public_key = public_key
|
18
|
+
cert.serial = 0x0
|
19
|
+
cert.version = 2
|
20
|
+
|
21
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
22
|
+
ef.subject_certificate = cert
|
23
|
+
ef.issuer_certificate = cert
|
24
|
+
cert.extensions = [
|
25
|
+
ef.create_extension("basicConstraints","CA:TRUE", true),
|
26
|
+
ef.create_extension("subjectKeyIdentifier", "hash")
|
27
|
+
]
|
28
|
+
cert.add_extension ef.create_extension("authorityKeyIdentifier",
|
29
|
+
"keyid:always,issuer:always")
|
30
|
+
|
31
|
+
cert.sign key, OpenSSL::Digest::SHA1.new
|
32
|
+
|
33
|
+
pem = cert.to_pem
|
34
|
+
pem << key.to_s
|
35
|
+
pem
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative "../../../../spec_helper"
|
2
|
+
require 'kontena/cli/master/users_command'
|
3
|
+
require "kontena/cli/master/users/remove_command"
|
4
|
+
|
5
|
+
describe Kontena::Cli::Master::Users::RemoveCommand do
|
6
|
+
|
7
|
+
include ClientHelpers
|
8
|
+
|
9
|
+
describe '#execute' do
|
10
|
+
|
11
|
+
it 'requires api url' do
|
12
|
+
expect(subject).to receive(:require_api_url).once
|
13
|
+
subject.run(['john@domain.com'])
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'requires token' do
|
17
|
+
expect(subject).to receive(:require_token).and_return(token)
|
18
|
+
subject.run(['john@domain.com'])
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'sends email to master' do
|
22
|
+
expect(client).to receive(:delete).with(
|
23
|
+
'users/john@domain.com'
|
24
|
+
)
|
25
|
+
subject.run(['john@domain.com'])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative "../../../spec_helper"
|
2
|
+
require "kontena/cli/services/containers_command"
|
3
|
+
|
4
|
+
describe Kontena::Cli::Services::ContainersCommand do
|
5
|
+
|
6
|
+
include ClientHelpers
|
7
|
+
|
8
|
+
describe '#execute' do
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
allow(client).to receive(:get).and_return({
|
12
|
+
'containers' => []
|
13
|
+
})
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'requires api url' do
|
17
|
+
expect(subject).to receive(:require_api_url).once
|
18
|
+
subject.run(['service-a'])
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'requires token' do
|
22
|
+
expect(subject).to receive(:require_token).and_return(token)
|
23
|
+
subject.run(['service-a'])
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'to not throw on missing "overlay_cidr" property' do
|
27
|
+
allow(client).to receive(:get).and_return({
|
28
|
+
'containers' => [
|
29
|
+
{'id' => "service-a-id", 'node' => {'public_ip' => ""}}
|
30
|
+
]
|
31
|
+
})
|
32
|
+
expect {
|
33
|
+
subject.run(['service-a'])
|
34
|
+
}.to_not raise_error(NoMethodError)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'to not throw on nil "overlay_cidr" property' do
|
38
|
+
allow(client).to receive(:get).and_return({
|
39
|
+
'containers' => [
|
40
|
+
{'id' => "service-a-id", 'node' => {'public_ip' => ""}, 'overlay_cidr' => nil}
|
41
|
+
]
|
42
|
+
})
|
43
|
+
expect {
|
44
|
+
subject.run(['service-a'])
|
45
|
+
}.to_not raise_error(NoMethodError)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kontena-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.12.
|
4
|
+
version: 0.12.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kontena, Inc
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-04-
|
11
|
+
date: 2016-04-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -205,6 +205,7 @@ files:
|
|
205
205
|
- lib/kontena/cli/master/users/add_role_command.rb
|
206
206
|
- lib/kontena/cli/master/users/invite_command.rb
|
207
207
|
- lib/kontena/cli/master/users/list_command.rb
|
208
|
+
- lib/kontena/cli/master/users/remove_command.rb
|
208
209
|
- lib/kontena/cli/master/users/remove_role_command.rb
|
209
210
|
- lib/kontena/cli/master/users_command.rb
|
210
211
|
- lib/kontena/cli/master/vagrant/create_command.rb
|
@@ -305,6 +306,7 @@ files:
|
|
305
306
|
- lib/kontena/machine/azure/master_provisioner.rb
|
306
307
|
- lib/kontena/machine/azure/node_destroyer.rb
|
307
308
|
- lib/kontena/machine/azure/node_provisioner.rb
|
309
|
+
- lib/kontena/machine/cert_helper.rb
|
308
310
|
- lib/kontena/machine/cloud_config/cloudinit.yml
|
309
311
|
- lib/kontena/machine/cloud_config/node_generator.rb
|
310
312
|
- lib/kontena/machine/common.rb
|
@@ -339,9 +341,11 @@ files:
|
|
339
341
|
- spec/kontena/cli/master/use_command_spec.rb
|
340
342
|
- spec/kontena/cli/master/users/add_role_command_spec.rb
|
341
343
|
- spec/kontena/cli/master/users/invite_command_spec.rb
|
344
|
+
- spec/kontena/cli/master/users/remove_command_spec.rb
|
342
345
|
- spec/kontena/cli/master/users/remove_role_command_spec.rb
|
343
346
|
- spec/kontena/cli/register_command_spec.rb
|
344
347
|
- spec/kontena/cli/services/add_secret_command_spec.rb
|
348
|
+
- spec/kontena/cli/services/containers_command_spec.rb
|
345
349
|
- spec/kontena/cli/services/link_command_spec.rb
|
346
350
|
- spec/kontena/cli/services/remove_secret_command_spec.rb
|
347
351
|
- spec/kontena/cli/services/restart_command_spec.rb
|
@@ -391,9 +395,11 @@ test_files:
|
|
391
395
|
- spec/kontena/cli/master/use_command_spec.rb
|
392
396
|
- spec/kontena/cli/master/users/add_role_command_spec.rb
|
393
397
|
- spec/kontena/cli/master/users/invite_command_spec.rb
|
398
|
+
- spec/kontena/cli/master/users/remove_command_spec.rb
|
394
399
|
- spec/kontena/cli/master/users/remove_role_command_spec.rb
|
395
400
|
- spec/kontena/cli/register_command_spec.rb
|
396
401
|
- spec/kontena/cli/services/add_secret_command_spec.rb
|
402
|
+
- spec/kontena/cli/services/containers_command_spec.rb
|
397
403
|
- spec/kontena/cli/services/link_command_spec.rb
|
398
404
|
- spec/kontena/cli/services/remove_secret_command_spec.rb
|
399
405
|
- spec/kontena/cli/services/restart_command_spec.rb
|