seira 0.3.3 → 0.3.6

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.
@@ -1,6 +1,8 @@
1
1
  module Seira
2
2
  class Db
3
3
  class Create
4
+ include Seira::Commands
5
+
4
6
  attr_reader :app, :action, :args, :context
5
7
 
6
8
  attr_reader :name, :version, :cpu, :memory, :storage, :replica_for, :make_highly_available
@@ -46,7 +48,7 @@ module Seira
46
48
 
47
49
  def run_create_command
48
50
  # The 'beta' is needed for HA and other beta features
49
- create_command = "gcloud beta sql instances create #{name}"
51
+ create_command = "beta sql instances create #{name}"
50
52
 
51
53
  args.each do |arg|
52
54
  if arg.start_with? '--version='
@@ -95,10 +97,8 @@ module Seira
95
97
  create_command += " --async"
96
98
  end
97
99
 
98
- puts "Running: #{create_command}"
99
-
100
100
  # Create the sql instance with the specified/default parameters
101
- if system(create_command)
101
+ if gcloud(create_command, context: context, format: :boolean)
102
102
  async_additional =
103
103
  unless replica_for.nil?
104
104
  ". Database is still being created and may take some time to be available."
@@ -115,7 +115,7 @@ module Seira
115
115
  # Set the root user's password to something secure
116
116
  @root_password = SecureRandom.urlsafe_base64(32)
117
117
 
118
- if system("gcloud sql users set-password postgres '' --instance=#{name} --password=#{root_password}")
118
+ if gcloud("sql users set-password postgres '' --instance=#{name} --password=#{root_password}", context: context, format: :boolean)
119
119
  puts "Set root password to #{root_password}"
120
120
  else
121
121
  puts "Failed to set root password"
@@ -127,7 +127,7 @@ module Seira
127
127
  # Create proxyuser with secure password
128
128
  @proxyuser_password = SecureRandom.urlsafe_base64(32)
129
129
 
130
- if system("gcloud sql users create proxyuser '' --instance=#{name} --password=#{proxyuser_password}")
130
+ if gcloud("sql users create proxyuser '' --instance=#{name} --password=#{proxyuser_password}", context: context, format: :boolean)
131
131
  puts "Created proxyuser with password #{proxyuser_password}"
132
132
  else
133
133
  puts "Failed to create proxyuser"
@@ -160,12 +160,13 @@ module Seira
160
160
  if replica_for.nil?
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
- write_database_env(key: "DATABASE_URL", db_user: 'proxyuser', db_password: proxyuser_password)
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(app: app, key: "DATABASE_URL").nil?
164
165
  write_database_env(key: "#{env_name}_DB_URL", db_user: 'proxyuser', db_password: proxyuser_password)
165
166
  else
166
167
  # When creating a replica, we cannot manage users on the replica. We must manage the users on the primary, which the replica
167
168
  # inherits. For now we will use the same credentials that the primary uses.
168
- primary_uri = URI.parse(Secrets.new(app: app, action: 'get', args: [], context: context).get('DATABASE_URL'))
169
+ primary_uri = URI.parse(Helpers.get_secret(app: app, key: 'DATABASE_URL'))
169
170
  primary_user = primary_uri.user
170
171
  primary_password = primary_uri.password
171
172
  create_pgbouncer_secret(db_user: primary_user, db_password: primary_password)
@@ -174,7 +175,7 @@ module Seira
174
175
  end
175
176
 
176
177
  def create_pgbouncer_secret(db_user:, db_password:)
177
- puts `kubectl create secret generic #{pgbouncer_secret_name} --namespace #{app} --from-literal=DB_USER=#{db_user} --from-literal=DB_PASSWORD=#{db_password}`
178
+ kubectl("create secret generic #{pgbouncer_secret_name} --from-literal=DB_USER=#{db_user} --from-literal=DB_PASSWORD=#{db_password}", context: context)
178
179
  end
179
180
 
180
181
  def write_database_env(key:, db_user:, db_password:)
data/lib/seira/jobs.rb CHANGED
@@ -2,6 +2,8 @@ require 'json'
2
2
 
3
3
  module Seira
4
4
  class Jobs
5
+ include Seira::Commands
6
+
5
7
  VALID_ACTIONS = %w[help list delete run].freeze
6
8
  SUMMARY = "Manage your application's jobs.".freeze
7
9
 
@@ -39,11 +41,11 @@ module Seira
39
41
  end
40
42
 
41
43
  def run_list
42
- puts `kubectl get jobs --namespace=#{app} -o wide`
44
+ kubectl("get jobs -o wide", context: context)
43
45
  end
44
46
 
45
47
  def run_delete
46
- puts `kubectl delete job #{job_name} --namespace=#{app}`
48
+ kubectl("delete job #{job_name}", context: context)
47
49
  end
48
50
 
49
51
  def run_run
@@ -109,8 +111,9 @@ module Seira
109
111
  end
110
112
  File.open("#{destination}/#{file_name}", 'w') { |file| file.write(new_contents) }
111
113
 
112
- puts "Running 'kubectl apply -f #{destination}'"
113
- system("kubectl apply -f #{destination}")
114
+ kubectl("apply -f #{destination}", context: context)
115
+ log_link = Helpers.log_link(context: context, app: app, query: unique_name)
116
+ puts "View logs at: #{log_link}" unless log_link.nil?
114
117
  end
115
118
 
116
119
  unless async
@@ -118,7 +121,7 @@ module Seira
118
121
  print 'Waiting for job to complete...'
119
122
  job_spec = nil
120
123
  loop do
121
- job_spec = JSON.parse(`kubectl --namespace=#{app} get job #{unique_name} -o json`)
124
+ job_spec = JSON.parse(kubectl("get job #{unique_name} -o json", context: context, return_output: true, clean_output: true))
122
125
  break if !job_spec['status']['succeeded'].nil? || !job_spec['status']['failed'].nil?
123
126
  print '.'
124
127
  sleep 3
@@ -137,7 +140,7 @@ module Seira
137
140
  puts "Job finished with status #{status}. Leaving Job object in cluster, clean up manually when confirmed."
138
141
  else
139
142
  print "Job finished with status #{status}. Deleting Job from cluster for cleanup."
140
- system("kubectl delete job #{unique_name} -n #{app}")
143
+ kubectl("delete job #{unique_name}", context: context)
141
144
  end
142
145
  end
143
146
  end
@@ -5,6 +5,8 @@ require 'fileutils'
5
5
  # Example usages:
6
6
  module Seira
7
7
  class NodePools
8
+ include Seira::Commands
9
+
8
10
  VALID_ACTIONS = %w[help list list-nodes add cordon drain delete].freeze
9
11
  SUMMARY = "For managing node pools for a cluster.".freeze
10
12
 
@@ -56,7 +58,7 @@ module Seira
56
58
  # TODO: Info about what is running on it?
57
59
  # TODO: What information do we get in the json format we could include here?
58
60
  def run_list
59
- puts `gcloud container node-pools list --cluster #{context[:cluster]}`
61
+ gcloud("container node-pools list --cluster #{context[:cluster]}", context: context, format: :boolean)
60
62
  end
61
63
 
62
64
  def run_list_nodes
@@ -89,7 +91,7 @@ module Seira
89
91
  end
90
92
 
91
93
  command =
92
- "gcloud container node-pools create #{new_pool_name} \
94
+ "container node-pools create #{new_pool_name} \
93
95
  --cluster=#{context[:cluster]} \
94
96
  --disk-size=#{disk_size} \
95
97
  --image-type=#{image_type} \
@@ -97,7 +99,7 @@ module Seira
97
99
  --num-nodes=#{num_nodes} \
98
100
  --service-account=#{service_account}"
99
101
 
100
- if system(command)
102
+ if gcloud(command, conext: context, format: :boolean)
101
103
  puts 'New pool created successfully'
102
104
  else
103
105
  puts 'Failed to create new pool'
@@ -112,7 +114,7 @@ module Seira
112
114
  nodes = nodes_for_pool(node_pool_name)
113
115
 
114
116
  nodes.each do |node|
115
- unless system("kubectl cordon #{node}")
117
+ unless kubectl("cordon #{node}", context: :none)
116
118
  puts "Failed to cordon node #{node}"
117
119
  exit(1)
118
120
  end
@@ -135,7 +137,7 @@ module Seira
135
137
  # --delete-local-data prevents failing due to presence of local data, which cannot be moved
136
138
  # but is bad practice to use for anything that can't be lost
137
139
  puts "Draining #{node}"
138
- unless system("kubectl drain --force --ignore-daemonsets --delete-local-data #{node}")
140
+ unless kubectl("drain --force --ignore-daemonsets --delete-local-data #{node}", context: :none)
139
141
  puts "Failed to drain node #{node}"
140
142
  exit(1)
141
143
  end
@@ -155,7 +157,7 @@ module Seira
155
157
 
156
158
  exit(1) unless HighLine.agree "Node pool has successfully been cordoned and drained, and should be safe to delete. Continue deleting node pool #{node_pool_name}?"
157
159
 
158
- if system("gcloud container node-pools delete #{node_pool_name} --cluster #{context[:cluster]}")
160
+ if gcloud("container node-pools delete #{node_pool_name} --cluster #{context[:cluster]}", context: context, format: :boolean)
159
161
  puts 'Node pool deleted successfully'
160
162
  else
161
163
  puts 'Failed to delete old pool'
@@ -165,11 +167,11 @@ module Seira
165
167
 
166
168
  # TODO: Represent by a ruby object?
167
169
  def node_pools
168
- JSON.parse(`gcloud container node-pools list --cluster #{context[:cluster]} --format json`)
170
+ JSON.parse(gcloud("container node-pools list --cluster #{context[:cluster]}", context: context, format: :json))
169
171
  end
170
172
 
171
173
  def nodes_for_pool(pool_name)
172
- `kubectl get nodes -l cloud.google.com/gke-nodepool=#{pool_name} -o name`.split("\n")
174
+ kubectl("get nodes -l cloud.google.com/gke-nodepool=#{pool_name} -o name", context: :none, return_output: true).split("\n")
173
175
  end
174
176
 
175
177
  def fail_if_lone_node_pool
data/lib/seira/pods.rb CHANGED
@@ -2,6 +2,8 @@ require 'json'
2
2
 
3
3
  module Seira
4
4
  class Pods
5
+ include Seira::Commands
6
+
5
7
  VALID_ACTIONS = %w[help list delete logs top run connect].freeze
6
8
  SUMMARY = "Manage your application's pods.".freeze
7
9
 
@@ -45,19 +47,19 @@ module Seira
45
47
  end
46
48
 
47
49
  def run_list
48
- puts `kubectl get pods --namespace=#{app} -o wide`
50
+ kubectl("get pods -o wide", context: context)
49
51
  end
50
52
 
51
53
  def run_delete
52
- puts `kubectl delete pod #{pod_name} --namespace=#{app}`
54
+ kubectl("delete pod #{pod_name}", context: context)
53
55
  end
54
56
 
55
57
  def run_logs
56
- puts `kubectl logs #{pod_name} --namespace=#{app} -c #{app}`
58
+ kubectl("logs #{pod_name} -c #{app}")
57
59
  end
58
60
 
59
61
  def run_top
60
- puts `kubectl top pod #{pod_name} --namespace=#{app} --containers`
62
+ kubectl("top pod #{pod_name} --containers", context: context)
61
63
  end
62
64
 
63
65
  def run_connect
data/lib/seira/proxy.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  module Seira
2
2
  class Proxy
3
+ include Seira::Commands
4
+
3
5
  SUMMARY = "Open up the proxy UI for a given cluster.".freeze
4
6
 
5
7
  def initialize
@@ -7,7 +9,7 @@ module Seira
7
9
 
8
10
  def run
9
11
  begin
10
- system("kubectl proxy")
12
+ kubectl("proxy", context: :none)
11
13
  rescue
12
14
  end
13
15
  end
data/lib/seira/secrets.rb CHANGED
@@ -8,6 +8,8 @@ require 'base64'
8
8
  # TODO: Can we avoid writing to disk completely and instead pipe in raw json?
9
9
  module Seira
10
10
  class Secrets
11
+ include Seira::Commands
12
+
11
13
  VALID_ACTIONS = %w[help get set unset list list-decoded].freeze
12
14
  PGBOUNCER_SECRETS_NAME = 'pgbouncer-secrets'.freeze
13
15
  SUMMARY = "Manage your application's secrets and environment variables.".freeze
@@ -45,7 +47,7 @@ module Seira
45
47
 
46
48
  def copy_secret_across_namespace(key:, to:, from:)
47
49
  puts "Copying the #{key} secret from namespace #{from} to #{to}."
48
- json_string = `kubectl get secret #{key} --namespace #{from} -o json`
50
+ json_string = kubectl("get secret #{key} -o json", context: context, return_output: true)
49
51
  secrets = JSON.parse(json_string)
50
52
 
51
53
  # At this point we would preferably simply do a write_secrets call, but the metadata is highly coupled to old
@@ -145,10 +147,10 @@ module Seira
145
147
  end
146
148
 
147
149
  # The command we use depends on if it already exists or not
148
- secret_exists = system("kubectl get secret #{secret_name} --namespace #{app} > /dev/null")
150
+ secret_exists = kubectl("get secret #{secret_name}", context: context) # TODO: Do not log, pipe output to dev/null
149
151
  command = secret_exists ? "replace" : "create"
150
152
 
151
- if system("kubectl #{command} --namespace #{app} -f #{file_name}")
153
+ if kubectl("#{command} -f #{file_name}", context: context)
152
154
  puts "Successfully created/replaced #{secret_name} secret #{key} in cluster #{Seira::Cluster.current_cluster}"
153
155
  else
154
156
  puts "Failed to update secret"
@@ -158,7 +160,7 @@ module Seira
158
160
 
159
161
  # Returns the still-base64encoded secrets hashmap
160
162
  def fetch_current_secrets
161
- json_string = `kubectl get secret #{main_secret_name} --namespace #{app} -o json`
163
+ json_string = kubectl("get secret #{main_secret_name} -o json", context: context, return_output: true)
162
164
  json = JSON.parse(json_string)
163
165
  fail "Unexpected Kind" unless json['kind'] == 'Secret'
164
166
  json
@@ -4,7 +4,7 @@ require 'yaml'
4
4
  module Seira
5
5
  class Settings
6
6
  DEFAULT_CONFIG_PATH = '.seira.yml'.freeze
7
-
7
+
8
8
  attr_reader :config_path
9
9
 
10
10
  def initialize(config_path: DEFAULT_CONFIG_PATH)
@@ -40,6 +40,10 @@ module Seira
40
40
  settings['seira']['clusters']
41
41
  end
42
42
 
43
+ def log_link_format
44
+ settings['seira']['log_link_format']
45
+ end
46
+
43
47
  def full_cluster_name_for_shorthand(shorthand)
44
48
  return shorthand if valid_cluster_names.include?(shorthand)
45
49
 
data/lib/seira/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Seira
2
- VERSION = "0.3.3".freeze
2
+ VERSION = "0.3.6".freeze
3
3
  end
data/seira.gemspec CHANGED
@@ -26,5 +26,5 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency "bundler", "~> 1.14"
27
27
  spec.add_development_dependency "rake", "~> 10.0"
28
28
  spec.add_development_dependency "rspec", "~> 3.0"
29
- spec.add_development_dependency "rubocop", "0.51.0"
29
+ spec.add_development_dependency "rubocop", "0.54.0"
30
30
  end
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.3.3
4
+ version: 0.3.6
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-03-09 00:00:00.000000000 Z
11
+ date: 2018-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - '='
88
88
  - !ruby/object:Gem::Version
89
- version: 0.51.0
89
+ version: 0.54.0
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - '='
95
95
  - !ruby/object:Gem::Version
96
- version: 0.51.0
96
+ version: 0.54.0
97
97
  description: An opinionated library for building applications on Kubernetes.
98
98
  email:
99
99
  - scott@joinhandshake.com
@@ -120,6 +120,9 @@ files:
120
120
  - lib/seira.rb
121
121
  - lib/seira/app.rb
122
122
  - lib/seira/cluster.rb
123
+ - lib/seira/commands.rb
124
+ - lib/seira/commands/gcloud.rb
125
+ - lib/seira/commands/kubectl.rb
123
126
  - lib/seira/db.rb
124
127
  - lib/seira/db/create.rb
125
128
  - lib/seira/jobs.rb