seira 0.3.7 → 0.4.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 +4 -4
- data/lib/helpers.rb +7 -7
- data/lib/seira/app.rb +18 -3
- data/lib/seira/db.rb +4 -4
- data/lib/seira/db/create.rb +4 -4
- data/lib/seira/jobs.rb +5 -6
- data/lib/seira/pods.rb +52 -73
- data/lib/seira/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6bcdddd7385af9bd0c795472c6f1fe911d9586b1
|
4
|
+
data.tar.gz: ad65f785990edea62925605e04bba6b4d116cd93
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a7c87cba6fa27f9debf2f11b0db52ecab602465e92e77c1a9774f875152d79c1b11b996d05dfcfbc671731a5557b2fb975031dad69c08a5709892e1b79593eb0
|
7
|
+
data.tar.gz: 5e8d13d180754d03d0abeb381047efae3ae65eb795bb6320f36fe3e5ce81915ac2ca530108571ae90451f1a1c0aa7dd6b760ea1b1e4ac35f9ac779acada61ae5
|
data/lib/helpers.rb
CHANGED
@@ -11,23 +11,23 @@ module Seira
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
def fetch_pods(filters:,
|
15
|
-
filter_string = { app: app }.merge(filters).map { |k, v| "#{k}=#{v}" }.join(',')
|
16
|
-
output = Seira::Commands.kubectl("get pods -o json --selector=#{filter_string}", context:
|
14
|
+
def fetch_pods(filters:, context:)
|
15
|
+
filter_string = { app: context[:app] }.merge(filters).map { |k, v| "#{k}=#{v}" }.join(',')
|
16
|
+
output = Seira::Commands.kubectl("get pods -o json --selector=#{filter_string}", context: context, return_output: true)
|
17
17
|
JSON.parse(output)['items']
|
18
18
|
end
|
19
19
|
|
20
|
-
def log_link(context:,
|
20
|
+
def log_link(context:, query:)
|
21
21
|
link = context[:settings].log_link_format
|
22
22
|
return nil if link.nil?
|
23
|
-
link.gsub! 'APP', app
|
23
|
+
link.gsub! 'APP', context[:app]
|
24
24
|
link.gsub! 'CLUSTER', context[:cluster]
|
25
25
|
link.gsub! 'QUERY', query
|
26
26
|
link
|
27
27
|
end
|
28
28
|
|
29
|
-
def get_secret(
|
30
|
-
Secrets.new(app: app, action: 'get', args: [], context: context).get(key)
|
29
|
+
def get_secret(key:, context:)
|
30
|
+
Secrets.new(app: context[:app], action: 'get', args: [], context: context).get(key)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
data/lib/seira/app.rb
CHANGED
@@ -77,9 +77,16 @@ module Seira
|
|
77
77
|
# Kube vanilla based upgrade
|
78
78
|
def run_apply(restart: false)
|
79
79
|
async = false
|
80
|
+
revision = nil
|
81
|
+
deployment = :all
|
82
|
+
|
80
83
|
args.each do |arg|
|
81
84
|
if arg == '--async'
|
82
85
|
async = true
|
86
|
+
elsif arg.start_with? '--deployment='
|
87
|
+
deployment = arg.split('=')[1]
|
88
|
+
elsif revision.nil?
|
89
|
+
revision = arg
|
83
90
|
else
|
84
91
|
puts "Warning: unrecognized argument #{arg}"
|
85
92
|
end
|
@@ -87,7 +94,7 @@ module Seira
|
|
87
94
|
|
88
95
|
Dir.mktmpdir do |dir|
|
89
96
|
destination = "#{dir}/#{context[:cluster]}/#{app}"
|
90
|
-
revision
|
97
|
+
revision ||= ENV['REVISION']
|
91
98
|
|
92
99
|
if revision.nil?
|
93
100
|
current_revision = ask_cluster_for_current_revision
|
@@ -116,12 +123,20 @@ module Seira
|
|
116
123
|
replacement_hash: replacement_hash
|
117
124
|
)
|
118
125
|
|
119
|
-
|
126
|
+
to_apply = destination
|
127
|
+
to_apply += "/#{deployment}.yaml" unless deployment == :all
|
128
|
+
kubectl("apply -f #{to_apply}", context: context)
|
120
129
|
|
121
130
|
unless async
|
122
131
|
puts "Monitoring rollout status..."
|
123
132
|
# Wait for rollout of all deployments to complete (running `kubectl rollout status` in parallel via xargs)
|
124
|
-
|
133
|
+
rollout_wait_command =
|
134
|
+
if deployment == :all
|
135
|
+
"kubectl get deployments -n #{app} -o name | xargs -n1 -P10 kubectl rollout status -n #{app}"
|
136
|
+
else
|
137
|
+
"kubectl rollout status -n #{app} deployments/#{app}-#{deployment}"
|
138
|
+
end
|
139
|
+
exit 1 unless system(rollout_wait_command)
|
125
140
|
end
|
126
141
|
end
|
127
142
|
end
|
data/lib/seira/db.rb
CHANGED
@@ -47,7 +47,7 @@ module Seira
|
|
47
47
|
|
48
48
|
# NOTE: Relies on the pgbouncer instance being named based on the db name, as is done in create command
|
49
49
|
def primary_instance
|
50
|
-
database_url = Helpers.get_secret(
|
50
|
+
database_url = Helpers.get_secret(context: context, key: 'DATABASE_URL')
|
51
51
|
return nil unless database_url
|
52
52
|
|
53
53
|
primary_uri = URI.parse(database_url)
|
@@ -105,7 +105,7 @@ module Seira
|
|
105
105
|
def run_connect
|
106
106
|
name = args[0] || primary_instance
|
107
107
|
puts "Connecting to #{name}..."
|
108
|
-
root_password = Helpers.get_secret(
|
108
|
+
root_password = Helpers.get_secret(context: context, key: "#{name.tr('-', '_').upcase}_ROOT_PASSWORD") || "Not found in secrets"
|
109
109
|
puts "Your root password for 'postgres' user is: #{root_password}"
|
110
110
|
system("gcloud sql connect #{name}")
|
111
111
|
end
|
@@ -239,7 +239,7 @@ module Seira
|
|
239
239
|
# TODO(josh): move pgbouncer naming logic here and in Create to a common location
|
240
240
|
instance_name = primary_instance
|
241
241
|
tier = instance_name.gsub("#{app}-", '')
|
242
|
-
matching_pods = Helpers.fetch_pods(
|
242
|
+
matching_pods = Helpers.fetch_pods(context: context, filters: { tier: tier })
|
243
243
|
if matching_pods.empty?
|
244
244
|
puts 'Could not find pgbouncer pod to connect to'
|
245
245
|
exit 1
|
@@ -247,7 +247,7 @@ module Seira
|
|
247
247
|
pod_name = matching_pods.first['metadata']['name']
|
248
248
|
psql_command =
|
249
249
|
if as_admin
|
250
|
-
root_password = Helpers.get_secret(
|
250
|
+
root_password = Helpers.get_secret(context: context, key: "#{instance_name.tr('-', '_').upcase}_ROOT_PASSWORD")
|
251
251
|
"psql postgres://postgres:#{root_password}@127.0.0.1:5432"
|
252
252
|
else
|
253
253
|
'psql'
|
data/lib/seira/db/create.rb
CHANGED
@@ -161,12 +161,12 @@ module Seira
|
|
161
161
|
create_pgbouncer_secret(db_user: 'proxyuser', db_password: proxyuser_password)
|
162
162
|
Secrets.new(app: app, action: 'set', args: ["#{env_name}_ROOT_PASSWORD=#{root_password}"], context: context).run
|
163
163
|
# Set DATABASE_URL if not already set
|
164
|
-
write_database_env(key: "DATABASE_URL", db_user: 'proxyuser', db_password: proxyuser_password) if Helpers.get_secret(
|
164
|
+
write_database_env(key: "DATABASE_URL", db_user: 'proxyuser', db_password: proxyuser_password) if Helpers.get_secret(context: context, key: "DATABASE_URL").nil?
|
165
165
|
write_database_env(key: "#{env_name}_DB_URL", db_user: 'proxyuser', db_password: proxyuser_password)
|
166
166
|
else
|
167
167
|
# When creating a replica, we cannot manage users on the replica. We must manage the users on the primary, which the replica
|
168
168
|
# inherits. For now we will use the same credentials that the primary uses.
|
169
|
-
primary_uri = URI.parse(Helpers.get_secret(
|
169
|
+
primary_uri = URI.parse(Helpers.get_secret(context: context, key: 'DATABASE_URL'))
|
170
170
|
primary_user = primary_uri.user
|
171
171
|
primary_password = primary_uri.password
|
172
172
|
create_pgbouncer_secret(db_user: primary_user, db_password: primary_password)
|
@@ -238,7 +238,7 @@ metadata:
|
|
238
238
|
tier: #{pgbouncer_tier}
|
239
239
|
database: #{name}
|
240
240
|
spec:
|
241
|
-
replicas:
|
241
|
+
replicas: 2
|
242
242
|
minReadySeconds: 20
|
243
243
|
strategy:
|
244
244
|
type: RollingUpdate
|
@@ -281,7 +281,7 @@ spec:
|
|
281
281
|
resources:
|
282
282
|
requests:
|
283
283
|
cpu: 100m
|
284
|
-
memory:
|
284
|
+
memory: 300Mi
|
285
285
|
- image: gcr.io/cloudsql-docker/gce-proxy:1.11 # Gcloud SQL Proxy
|
286
286
|
name: cloudsql-proxy
|
287
287
|
command:
|
data/lib/seira/jobs.rb
CHANGED
@@ -67,6 +67,7 @@ module Seira
|
|
67
67
|
|
68
68
|
if arg == '--async'
|
69
69
|
async = true
|
70
|
+
no_delete = true
|
70
71
|
elsif arg == '--no-delete'
|
71
72
|
no_delete = true
|
72
73
|
else
|
@@ -76,11 +77,6 @@ module Seira
|
|
76
77
|
args.shift
|
77
78
|
end
|
78
79
|
|
79
|
-
if async && !no_delete
|
80
|
-
puts "Cannot delete Job after running if Job is async, since we don't know when it finishes."
|
81
|
-
exit(1)
|
82
|
-
end
|
83
|
-
|
84
80
|
# TODO: Configurable CPU and memory by args such as large, small, xlarge.
|
85
81
|
command = args.join(' ')
|
86
82
|
unique_name = "#{app}-run-#{Random.unique_name}"
|
@@ -112,7 +108,7 @@ module Seira
|
|
112
108
|
File.open("#{destination}/#{file_name}", 'w') { |file| file.write(new_contents) }
|
113
109
|
|
114
110
|
kubectl("apply -f #{destination}", context: context)
|
115
|
-
log_link = Helpers.log_link(context: context,
|
111
|
+
log_link = Helpers.log_link(context: context, query: unique_name)
|
116
112
|
puts "View logs at: #{log_link}" unless log_link.nil?
|
117
113
|
end
|
118
114
|
|
@@ -142,6 +138,9 @@ module Seira
|
|
142
138
|
print "Job finished with status #{status}. Deleting Job from cluster for cleanup."
|
143
139
|
kubectl("delete job #{unique_name}", context: context)
|
144
140
|
end
|
141
|
+
|
142
|
+
# If the job did not succeed, exit nonzero so calling scripts know something went wrong
|
143
|
+
exit(1) unless status == "succeeded"
|
145
144
|
end
|
146
145
|
end
|
147
146
|
end
|
data/lib/seira/pods.rb
CHANGED
@@ -63,110 +63,89 @@ module Seira
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def run_connect
|
66
|
-
|
67
|
-
|
66
|
+
tier = nil
|
67
|
+
pod_name = nil
|
68
|
+
dedicated = false
|
69
|
+
command = 'sh'
|
68
70
|
|
69
|
-
|
70
|
-
connect_to_pod(target_pod_name)
|
71
|
-
else
|
72
|
-
puts "Could not find web pod to connect to"
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def run_run
|
77
|
-
# Set defaults
|
78
|
-
tier = 'web'
|
79
|
-
clear_commands = false
|
80
|
-
detached = false
|
81
|
-
container_name = app
|
82
|
-
|
83
|
-
# Loop through args and process any that aren't just the command to run
|
84
|
-
loop do
|
85
|
-
arg = args.first
|
86
|
-
if arg.nil?
|
87
|
-
puts 'Please specify a command to run'
|
88
|
-
exit(1)
|
89
|
-
end
|
90
|
-
break unless arg.start_with? '--'
|
71
|
+
args.each do |arg|
|
91
72
|
if arg.start_with? '--tier='
|
92
73
|
tier = arg.split('=')[1]
|
93
|
-
elsif arg
|
94
|
-
|
95
|
-
elsif arg
|
96
|
-
|
97
|
-
elsif arg
|
98
|
-
|
74
|
+
elsif arg.start_with? '--pod='
|
75
|
+
pod_name = arg.split('=')[1]
|
76
|
+
elsif arg.start_with? '--command='
|
77
|
+
command = arg.split('=')[1..-1].join('=')
|
78
|
+
elsif arg == '--dedicated'
|
79
|
+
dedicated = true
|
99
80
|
else
|
100
81
|
puts "Warning: Unrecognized argument #{arg}"
|
101
82
|
end
|
102
|
-
args.shift
|
103
83
|
end
|
104
84
|
|
105
|
-
#
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
puts "Unable to find #{tier} tier pod to copy config from"
|
85
|
+
# If a pod name is specified, connect to that pod
|
86
|
+
# If a tier is specified, connect to a random pod from that tier
|
87
|
+
# Otherwise connect to a terminal pod
|
88
|
+
target_pod = pod_name || Helpers.fetch_pods(context: context, filters: { tier: tier || 'terminal' }).sample
|
89
|
+
if target_pod.nil?
|
90
|
+
puts 'Could not find pod to connect to'
|
112
91
|
exit(1)
|
113
92
|
end
|
114
93
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
94
|
+
if dedicated
|
95
|
+
# Create a dedicated temp pod to run in
|
96
|
+
# This is useful if you would like to have a persistent connection that doesn't get killed
|
97
|
+
# when someone updates the terminal deployment, or if you want to avoid noisy neighbors
|
98
|
+
# connected to the same pod.
|
99
|
+
temp_name = "temp-#{Random.unique_name}"
|
100
|
+
|
101
|
+
# Construct a spec for the temp pod
|
102
|
+
spec = target_pod['spec']
|
103
|
+
temp_pod = {
|
104
|
+
apiVersion: target_pod['apiVersion'],
|
105
|
+
kind: 'Pod',
|
106
|
+
spec: spec,
|
107
|
+
metadata: {
|
108
|
+
name: temp_name
|
109
|
+
}
|
124
110
|
}
|
125
|
-
|
126
|
-
|
127
|
-
|
111
|
+
# Don't restart the pod when it dies
|
112
|
+
spec['restartPolicy'] = 'Never'
|
113
|
+
# Overwrite container commands with something that times out, so if the client disconnects
|
114
|
+
# there's a limited amount of time that the temp pod is still taking up resources
|
115
|
+
# Note that this will break a pods which depends on containers running real commands, but
|
116
|
+
# for a simple terminal pod it's fine
|
128
117
|
spec['containers'].each do |container|
|
129
|
-
container['command'] = ['
|
118
|
+
container['command'] = ['sleep', '86400'] # 86400 seconds = 24 hours
|
130
119
|
end
|
131
|
-
end
|
132
120
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
puts "Could not find container '#{container_name}' to run command in"
|
121
|
+
puts 'Creating dedicated pod...'
|
122
|
+
unless system("kubectl --namespace=#{app} create -f - <<JSON\n#{temp_pod.to_json}\nJSON")
|
123
|
+
puts 'Failed to create dedicated pod'
|
137
124
|
exit(1)
|
138
125
|
end
|
139
|
-
target_container['command'] = ['bash', '-c', command]
|
140
|
-
end
|
141
|
-
|
142
|
-
puts "Creating temporary pod #{temp_name}"
|
143
|
-
unless system("kubectl --namespace=#{app} create -f - <<JSON\n#{temp_pod.to_json}\nJSON")
|
144
|
-
puts 'Failed to create pod'
|
145
|
-
exit(1)
|
146
|
-
end
|
147
126
|
|
148
|
-
|
149
|
-
# Check pod status until it's ready to connect to
|
150
|
-
print 'Waiting for pod to start...'
|
127
|
+
print 'Waiting for dedicated pod to start...'
|
151
128
|
loop do
|
152
|
-
pod = JSON.parse(
|
129
|
+
pod = JSON.parse(kubectl("get pods/#{temp_name} -o json", context: context, return_output: true))
|
153
130
|
break if pod['status']['phase'] == 'Running'
|
154
131
|
print '.'
|
155
132
|
sleep 1
|
156
133
|
end
|
157
134
|
print "\n"
|
158
135
|
|
159
|
-
# Connect to the pod, running the specified command
|
160
136
|
connect_to_pod(temp_name, command)
|
161
137
|
|
162
|
-
# Clean up
|
163
|
-
unless
|
164
|
-
puts
|
138
|
+
# Clean up on disconnect so temp pod isn't taking up resources
|
139
|
+
unless kubectl("delete pods/#{temp_name}", context: context)
|
140
|
+
puts 'Failed to delete temp pod'
|
165
141
|
end
|
142
|
+
else
|
143
|
+
# If we don't need a dedicated pod, it's way easier - just connect to the already running one
|
144
|
+
connect_to_pod(target_pod.dig('metadata', 'name'))
|
166
145
|
end
|
167
146
|
end
|
168
147
|
|
169
|
-
def connect_to_pod(name, command = '
|
148
|
+
def connect_to_pod(name, command = 'sh')
|
170
149
|
puts "Connecting to #{name}..."
|
171
150
|
system("kubectl exec -ti #{name} --namespace=#{app} -- #{command}")
|
172
151
|
end
|
data/lib/seira/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: seira
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scott Ringwelski
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04-
|
11
|
+
date: 2018-04-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: highline
|