seira 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8166db4d61ddd6bc9667557e52ee2af61516e303
4
- data.tar.gz: d75de14291b27b1cf5fc35d49ac50ff54c2cc6f4
3
+ metadata.gz: 137cf8ac1506f10dac88ef667c49c5f6a30b515b
4
+ data.tar.gz: f55493c0ddfc6615f0954d9672d78a2399e41bdb
5
5
  SHA512:
6
- metadata.gz: 27d97e3e35d7edb6d4b61d3377be3a8b66dde379382ddc167d19b4683beb8a859f0d64017c7d2bf0d2a6c1f43ec7f127d87841a4c7d4e22dd70ea07045bcc331
7
- data.tar.gz: 3f8979b56c80fd58a94220b354f74d4adabcd45d460212a4086fbca9499c8cc8cee6a890f166647be0299fcb180be832191b070e32c5fffc4f07115f2e16eda7
6
+ metadata.gz: 9d179c9b561dafcf2ecc0162e4c493b488a28e18e62195d8913c2aa2d211f5af805301cc5f8d6dd9f2ec59feae023d36409ce7d0e51b71a7852d140afe636a31
7
+ data.tar.gz: c31974b1f7b50272779f76f1628ed5aade976fe5c36035314b7774d4e3e3092ad1ba38b750ab8ebb6f1e83ecca6c86eec6f066927770c29e7371f5fbc2a16244
data/lib/seira/app.rb CHANGED
@@ -41,12 +41,12 @@ module Seira
41
41
  puts "Possible actions:\n\n"
42
42
  puts "bootstrap: Create new app with main secret, cloudsql secret, and gcr secret in the new namespace."
43
43
  puts "apply: Apply the configuration in kubernetes/<cluster-name>/<app-name> using REVISION environment variable to find/replace REVISION in the YAML."
44
- puts "restart: TODO."
44
+ puts "restart: Forces a rolling deploy for any deployment making use of RESTARTED_AT_VALUE in the deployment."
45
45
  puts "scale: Scales the given tier deployment to the specified number of instances."
46
46
  end
47
47
 
48
48
  def run_restart
49
- # TODO
49
+ run_apply(restart: true)
50
50
  end
51
51
 
52
52
  private
@@ -60,7 +60,7 @@ module Seira
60
60
  end
61
61
 
62
62
  # Kube vanilla based upgrade
63
- def run_apply
63
+ def run_apply(restart: false)
64
64
  destination = "tmp/#{context[:cluster]}/#{app}"
65
65
  revision = ENV['REVISION']
66
66
 
@@ -73,6 +73,10 @@ module Seira
73
73
 
74
74
  replacement_hash = { 'REVISION' => revision }
75
75
 
76
+ if restart
77
+ replacement_hash['RESTARTED_AT_VALUE'] = Time.now.to_s
78
+ end
79
+
76
80
  replacement_hash.each do |k, v|
77
81
  next unless v.nil? || v == ''
78
82
  puts "Found nil or blank value for replacement hash key #{k}. Aborting!"
@@ -144,7 +148,8 @@ module Seira
144
148
 
145
149
  def find_and_replace_revision(source:, destination:, replacement_hash:)
146
150
  puts "Copying source yaml from #{source} to #{destination}"
147
- FileUtils::mkdir_p destination # Create the nested directory
151
+ FileUtils.mkdir_p destination # Create the nested directory
152
+ FileUtils.rm_rf("#{destination}/.", secure: true) # Clean out old files from the tmp folder
148
153
  FileUtils.copy_entry source, destination
149
154
 
150
155
  # Iterate through each yaml file and find/replace and save
@@ -0,0 +1,327 @@
1
+ module Seira
2
+ class Db
3
+ class Create
4
+ attr_reader :app, :action, :args, :context
5
+
6
+ attr_reader :name, :version, :cpu, :memory, :storage, :replica_for, :make_highly_available
7
+ attr_reader :root_password, :proxyuser_password
8
+
9
+ def initialize(app:, action:, args:, context:)
10
+ @app = app
11
+ @action = action
12
+ @args = args
13
+ @context = context
14
+
15
+ # We allow overriding the version, so you could specify a mysql version but much of the
16
+ # below assumes postgres for now
17
+ @version = 'POSTGRES_9_6'
18
+ @cpu = 1 # Number of CPUs
19
+ @memory = 4 # GB
20
+ @storage = 10 # GB
21
+ @replica_for = nil
22
+ @make_highly_available = false
23
+
24
+ @root_password = nil
25
+ @proxyuser_password = nil
26
+ end
27
+
28
+ def run(existing_instances)
29
+ @name = "#{app}-#{Seira::Random.unique_name(existing_instances)}"
30
+
31
+ run_create_command
32
+
33
+ if replica_for.nil?
34
+ update_root_password
35
+ create_proxy_user
36
+ end
37
+
38
+ set_secrets
39
+ write_pgbouncer_yaml
40
+
41
+ puts "To use this database, deploy the pgbouncer config file that was created and use the ENV that was set."
42
+ puts "To make this database the primary, promote it using the CLI and update the DATABASE_URL."
43
+ end
44
+
45
+ private
46
+
47
+ def run_create_command
48
+ # The 'beta' is needed for HA and other beta features
49
+ create_command = "gcloud beta sql instances create #{name}"
50
+
51
+ args.each do |arg|
52
+ if arg.start_with? '--version='
53
+ @version = arg.split('=')[1]
54
+ elsif arg.start_with? '--cpu='
55
+ @cpu = arg.split('=')[1]
56
+ elsif arg.start_with? '--memory='
57
+ @memory = arg.split('=')[1]
58
+ elsif arg.start_with? '--storage='
59
+ @storage = arg.split('=')[1]
60
+ elsif arg.start_with? '--primary='
61
+ @replica_for = arg.split('=')[1] # TODO: Read secret to get it automatically
62
+ elsif arg.start_with? '--highly-available'
63
+ @make_highly_available = true
64
+ elsif /^--[\w\-]+=.+$/.match? arg
65
+ create_command += " #{arg}"
66
+ else
67
+ puts "Warning: Unrecognized argument '#{arg}'"
68
+ end
69
+ end
70
+
71
+ if make_highly_available && !replica_for.nil?
72
+ puts "Cannot create an HA read-replica."
73
+ exit(1)
74
+ end
75
+
76
+ # Basic configs
77
+ create_command += " --database-version=#{version}"
78
+
79
+ # A read replica cannot have HA, inherits the cpu, mem and storage of its primary
80
+ if replica_for.nil?
81
+ # Make sure to do automated daily backups by default, unless it's a replica
82
+ create_command += " --backup"
83
+ create_command += " --cpu=#{cpu}"
84
+ create_command += " --memory=#{memory}"
85
+ create_command += " --storage-size=#{storage}"
86
+
87
+ # Make HA if asked for
88
+ create_command += " --availability-type=REGIONAL" if make_highly_available
89
+ else
90
+ create_command += " --master-instance-name=#{replica_for}"
91
+ # We don't need to wait for it to finish to move ahead if it's a replica, as we don't
92
+ # make any changes to the database itself
93
+ create_command += " --async"
94
+ end
95
+
96
+ puts "Running: #{create_command}"
97
+
98
+ # Create the sql instance with the specified/default parameters
99
+ if system(create_command)
100
+ async_additional =
101
+ unless replica_for.nil?
102
+ ". Database is still being created and may take some time to be available."
103
+ end
104
+
105
+ puts "Successfully created sql instance #{name}#{async_additional}"
106
+ else
107
+ puts "Failed to create sql instance"
108
+ exit(1)
109
+ end
110
+ end
111
+
112
+ def update_root_password
113
+ # Set the root user's password to something secure
114
+ @root_password = SecureRandom.urlsafe_base64(32)
115
+
116
+ if system("gcloud sql users set-password postgres '' --instance=#{name} --password=#{root_password}")
117
+ puts "Set root password to #{root_password}"
118
+ else
119
+ puts "Failed to set root password"
120
+ exit(1)
121
+ end
122
+ end
123
+
124
+ def create_proxy_user
125
+ # Create proxyuser with secure password
126
+ @proxyuser_password = SecureRandom.urlsafe_base64(32)
127
+
128
+ if system("gcloud sql users create proxyuser '' --instance=#{name} --password=#{proxyuser_password}")
129
+ puts "Created proxyuser with password #{proxyuser_password}"
130
+ else
131
+ puts "Failed to create proxyuser"
132
+ exit(1)
133
+ end
134
+
135
+ # Connect to the instance and remove some of the default group memberships and permissions
136
+ # from proxyuser, leaving it with only what it needs to be able to do
137
+ expect_script = <<~BASH
138
+ set timeout 90
139
+ spawn gcloud sql connect #{name}
140
+ expect "Password for user postgres:"
141
+ send "#{root_password}\\r"
142
+ expect "postgres=>"
143
+ send "ALTER ROLE proxyuser NOCREATEDB NOCREATEROLE;\\r"
144
+ expect "postgres=>"
145
+ BASH
146
+ if system("expect <<EOF\n#{expect_script}EOF")
147
+ puts "Successfully removed unnecessary permissions from proxyuser"
148
+ else
149
+ puts "Failed to remove unnecessary permissions from proxyuser"
150
+ exit(1)
151
+ end
152
+ end
153
+
154
+ def set_secrets
155
+ env_name = name.tr('-', '_').upcase
156
+
157
+ # If setting as primary, update relevant secrets. Only primaries have root passwords.
158
+ if replica_for.nil?
159
+ create_pgbouncer_secret(db_user: 'proxyuser', db_password: proxyuser_password)
160
+ Secrets.new(app: app, action: 'set', args: ["#{env_name}_ROOT_PASSWORD=#{root_password}"], context: context).run
161
+ write_database_env(key: "DATABASE_URL", db_user: 'proxyuser', db_password: proxyuser_password)
162
+ write_database_env(key: "#{env_name}_DB_URL", db_user: 'proxyuser', db_password: proxyuser_password)
163
+ else
164
+ # When creating a replica, we cannot manage users on the replica. We must manage the users on the primary, which the replica
165
+ # inherits. For now we will use the same credentials that the primary uses.
166
+ primary_uri = URI.parse(Secrets.new(app: app, action: 'get', args: [], context: context).get('DATABASE_URL'))
167
+ primary_user = primary_uri.user
168
+ primary_password = primary_uri.password
169
+ create_pgbouncer_secret(db_user: primary_user, db_password: primary_password)
170
+ write_database_env(key: "#{env_name}_DB_URL", db_user: primary_user, db_password: primary_password)
171
+ end
172
+ end
173
+
174
+ def create_pgbouncer_secret(db_user:, db_password:)
175
+ puts `kubectl create secret generic #{pgbouncer_secret_name} --namespace #{app} --from-literal=DB_USER=#{db_user} --from-literal=DB_PASSWORD=#{db_password}`
176
+ end
177
+
178
+ def write_database_env(key:, db_user:, db_password:)
179
+ Secrets.new(app: app, action: 'set', args: ["#{key}=postgres://#{db_user}:#{db_password}@#{pgbouncer_service_name}:6432"], context: context).run
180
+ end
181
+
182
+ def pgbouncer_secret_name
183
+ "#{name}-pgbouncer-secrets"
184
+ end
185
+
186
+ def pgbouncer_configs_name
187
+ "#{name}-pgbouncer-configs"
188
+ end
189
+
190
+ def pgbouncer_service_name
191
+ "#{name}-pgbouncer-service"
192
+ end
193
+
194
+ def pgbouncer_tier
195
+ name.gsub("handshake-", "")
196
+ end
197
+
198
+ def write_pgbouncer_yaml
199
+ # TODO: Clean this up by moving into a proper templated yaml file
200
+ pgbouncer_yaml = <<-FOO
201
+ ---
202
+ apiVersion: v1
203
+ kind: ConfigMap
204
+ metadata:
205
+ name: #{pgbouncer_configs_name}
206
+ namespace: #{app}
207
+ data:
208
+ DB_HOST: "127.0.0.1"
209
+ DB_PORT: "5432"
210
+ LISTEN_PORT: "6432"
211
+ LISTEN_ADDRESS: "*"
212
+ TCP_KEEPALIVE: "1"
213
+ TCP_KEEPCNT: "5"
214
+ TCP_KEEPIDLE: "300" # see: https://git.io/vi0Aj
215
+ TCP_KEEPINTVL: "300"
216
+ LOG_DISCONNECTIONS: "0" # spammy, not needed
217
+ MAX_CLIENT_CONN: "500"
218
+ MAX_DB_CONNECTIONS: "90"
219
+ DEFAULT_POOL_SIZE: "90"
220
+ POOL_MODE: "transaction"
221
+ ---
222
+ apiVersion: extensions/v1beta1
223
+ kind: Deployment
224
+ metadata:
225
+ name: #{name}-pgbouncer
226
+ namespace: #{app}
227
+ labels:
228
+ app: #{app}
229
+ tier: #{pgbouncer_tier}
230
+ database: #{name}
231
+ spec:
232
+ replicas: 1
233
+ minReadySeconds: 20
234
+ strategy:
235
+ type: RollingUpdate
236
+ rollingUpdate:
237
+ maxSurge: 1
238
+ maxUnavailable: 1
239
+ template:
240
+ metadata:
241
+ labels:
242
+ app: #{app}
243
+ tier: #{pgbouncer_tier}
244
+ database: #{name}
245
+ spec:
246
+ containers:
247
+ - image: handshake/pgbouncer:0.1.2
248
+ name: pgbouncer
249
+ ports:
250
+ - containerPort: 6432
251
+ protocol: TCP
252
+ envFrom:
253
+ - configMapRef:
254
+ name: #{pgbouncer_configs_name}
255
+ - secretRef:
256
+ name: #{pgbouncer_secret_name}
257
+ readinessProbe:
258
+ tcpSocket:
259
+ port: 6432
260
+ initialDelaySeconds: 5
261
+ periodSeconds: 10
262
+ livenessProbe:
263
+ tcpSocket:
264
+ port: 6432
265
+ initialDelaySeconds: 15
266
+ periodSeconds: 20
267
+ resources:
268
+ requests:
269
+ cpu: 100m
270
+ memory: 300m
271
+ - image: gcr.io/cloudsql-docker/gce-proxy:1.11 # Gcloud SQL Proxy
272
+ name: cloudsql-proxy
273
+ command:
274
+ - /cloud_sql_proxy
275
+ - --dir=/cloudsql
276
+ - -instances=#{context[:project]}:#{context[:default_zone]}:#{name}=tcp:5432
277
+ - -credential_file=/secrets/cloudsql/credentials.json
278
+ ports:
279
+ - containerPort: 5432
280
+ protocol: TCP
281
+ envFrom:
282
+ - configMapRef:
283
+ name: cloudsql-configs
284
+ volumeMounts:
285
+ - name: cloudsql-credentials
286
+ mountPath: /secrets/cloudsql
287
+ readOnly: true
288
+ - name: ssl-certs
289
+ mountPath: /etc/ssl/certs
290
+ - name: cloudsql
291
+ mountPath: /cloudsql
292
+ volumes:
293
+ - name: cloudsql-credentials
294
+ secret:
295
+ secretName: cloudsql-credentials
296
+ - name: cloudsql
297
+ emptyDir:
298
+ - name: ssl-certs
299
+ hostPath:
300
+ path: /etc/ssl/certs
301
+ ---
302
+ apiVersion: v1
303
+ kind: Service
304
+ metadata:
305
+ name: #{pgbouncer_service_name}
306
+ namespace: #{app}
307
+ labels:
308
+ app: #{app}
309
+ tier: #{pgbouncer_tier}
310
+ spec:
311
+ type: NodePort
312
+ ports:
313
+ - protocol: TCP
314
+ port: 6432
315
+ targetPort: 6432
316
+ nodePort: 0
317
+ selector:
318
+ app: #{app}
319
+ tier: #{pgbouncer_tier}
320
+ database: #{name}
321
+ FOO
322
+
323
+ File.write("kubernetes/#{context[:cluster]}/#{app}/pgbouncer-#{name.gsub("#{app}-", '')}.yaml", pgbouncer_yaml)
324
+ end
325
+ end
326
+ end
327
+ end
data/lib/seira/db.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'securerandom'
2
2
 
3
+ require_relative 'db/create.rb'
4
+
3
5
  module Seira
4
6
  class Db
5
7
  VALID_ACTIONS = %w[help create delete list].freeze
@@ -33,104 +35,23 @@ module Seira
33
35
 
34
36
  def run_help
35
37
  puts SUMMARY
36
- puts "\n\n"
37
- puts "TODO"
38
+ puts "\n"
39
+ puts "create: Create a new postgres instance in cloud sql. Supports creating replicas and other numerous flags."
40
+ puts "delete: Delete a postgres instance from cloud sql. Use with caution, and remove all kubernetes configs first."
41
+ puts "list: List all postgres instances."
38
42
  end
39
43
 
40
44
  def run_create
41
- # We allow overriding the version, so you could specify a mysql version but much of the
42
- # below assumes postgres for now
43
- version = 'POSTGRES_9_6'
44
- cpu = 1 # Number of CPUs
45
- memory = 4 # GB
46
- storage = 10 # GB
47
- set_as_primary = false
48
-
49
- name = "#{app}-#{Seira::Random.unique_name(existing_instances)}"
50
-
51
- create_command = "gcloud sql instances create #{name}"
52
-
53
- args.each do |arg|
54
- if arg.start_with? '--version='
55
- version = arg.split('=')[1]
56
- elsif arg.start_with? '--cpu='
57
- cpu = arg.split('=')[1]
58
- elsif arg.start_with? '--memory='
59
- memory = arg.split('=')[1]
60
- elsif arg.start_with? '--storage='
61
- storage = arg.split('=')[1]
62
- elsif arg.start_with? '--set-as-primary='
63
- set_as_primary = %w[true yes t y].include?(arg.split('=')[1])
64
- elsif /^--[\w\-]+=.+$/.match? arg
65
- create_command += " #{arg}"
66
- else
67
- puts "Warning: Unrecognized argument '#{arg}'"
68
- end
69
- end
70
-
71
- create_command += " --database-version=#{version}"
72
- create_command += " --cpu=#{cpu}"
73
- create_command += " --memory=#{memory}"
74
- create_command += " --storage-size=#{storage}"
75
-
76
- # Create the sql instance with the specified/default parameters
77
- if system(create_command)
78
- puts "Successfully created sql instance #{name}"
79
- else
80
- puts "Failed to create sql instance"
81
- exit(1)
82
- end
83
-
84
- # Set the root user's password to something secure
85
- root_password = SecureRandom.urlsafe_base64(32)
86
- if system("gcloud sql users set-password postgres '' --instance=#{name} --password=#{root_password}")
87
- puts "Set root password to #{root_password}"
88
- else
89
- puts "Failed to set root password"
90
- exit(1)
91
- end
92
-
93
- # Create proxyuser with secure password
94
- proxyuser_password = SecureRandom.urlsafe_base64(32)
95
- if system("gcloud sql users create proxyuser '' --instance=#{name} --password=#{proxyuser_password}")
96
- puts "Created proxyuser with password #{proxyuser_password}"
97
- else
98
- puts "Failed to create proxyuser"
99
- exit(1)
100
- end
101
-
102
- # Connect to the instance and remove some of the default group memberships and permissions
103
- # from proxyuser, leaving it with only what it needs to be able to do
104
- expect_script = <<~BASH
105
- set timeout 90
106
- spawn gcloud sql connect #{name}
107
- expect "Password for user postgres:"
108
- send "#{root_password}\\r"
109
- expect "postgres=>"
110
- send "ALTER ROLE proxyuser NOCREATEDB NOCREATEROLE;\\r"
111
- expect "postgres=>"
112
- BASH
113
- if system("expect <<EOF\n#{expect_script}EOF")
114
- puts "Successfully removed unnecessary permissions from proxyuser"
115
- else
116
- puts "Failed to remove unnecessary permissions from proxyuser"
117
- exit(1)
118
- end
119
-
120
- # If setting as primary, update relevant secrets
121
- if set_as_primary
122
- Secrets.new(app: app, action: 'create-pgbouncer-secret', args: ['proxyuser', proxyuser_password], context: context).run
123
- Secrets.new(app: app, action: 'set', args: ["DATABASE_URL=postgres://proxyuser:#{proxyuser_password}@#{app}-pgbouncer-service:6432"], context: context).run
124
- end
125
- # Regardless of primary or not, store a URL for this db matching its unique name
126
- env_name = name.tr('-', '_').upcase
127
- Secrets.new(app: app, action: 'set', args: ["#{env_name}_DB_URL=postgres://proxyuser:#{proxyuser_password}@#{app}-pgbouncer-service:6432", "#{env_name}_ROOT_PASSWORD=#{root_password}"], context: context).run
45
+ Seira::Db::Create.new(app: app, action: action, args: args, context: context).run(existing_instances)
128
46
  end
129
47
 
130
48
  def run_delete
131
49
  name = "#{app}-#{args[0]}"
132
50
  if system("gcloud sql instances delete #{name}")
133
51
  puts "Successfully deleted sql instance #{name}"
52
+
53
+ # TODO: Automate the below
54
+ puts "Don't forget to delete the deployment, configmap, secret, and service for the pgbouncer instance."
134
55
  else
135
56
  puts "Failed to delete sql instance #{name}"
136
57
  end
data/lib/seira/secrets.rb CHANGED
@@ -38,8 +38,6 @@ module Seira
38
38
  run_list
39
39
  when 'list-decoded'
40
40
  run_list_decoded
41
- when 'create-pgbouncer-secret'
42
- run_create_pgbouncer_secret
43
41
  else
44
42
  fail "Unknown command encountered"
45
43
  end
@@ -65,6 +63,11 @@ module Seira
65
63
  "#{app}-secrets"
66
64
  end
67
65
 
66
+ def get(key)
67
+ secrets = fetch_current_secrets
68
+ Base64.decode64(secrets['data'][key])
69
+ end
70
+
68
71
  private
69
72
 
70
73
  def run_help
@@ -88,8 +91,7 @@ module Seira
88
91
  end
89
92
 
90
93
  def run_get
91
- secrets = fetch_current_secrets
92
- puts "#{key}: #{Base64.decode64(secrets['data'][key])}"
94
+ puts "#{key}: #{get(key)}"
93
95
  end
94
96
 
95
97
  def run_set
@@ -120,12 +122,6 @@ module Seira
120
122
  end
121
123
  end
122
124
 
123
- def run_create_pgbouncer_secret
124
- db_user = args[0]
125
- db_password = args[1]
126
- puts `kubectl create secret generic #{PGBOUNCER_SECRETS_NAME} --namespace #{app} --from-literal=DB_USER=#{db_user} --from-literal=DB_PASSWORD=#{db_password}`
127
- end
128
-
129
125
  # In the normal case the secret we are updating is just main_secret_name,
130
126
  # but in special cases we may be doing an operation on a different secret
131
127
  def write_secrets(secrets:, secret_name: main_secret_name)
@@ -48,6 +48,10 @@ module Seira
48
48
  nil
49
49
  end
50
50
 
51
+ def project_for_cluster(cluster)
52
+ settings['seira']['clusters'][cluster]['project']
53
+ end
54
+
51
55
  private
52
56
 
53
57
  def parse_settings
data/lib/seira/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Seira
2
- VERSION = "0.1.4".freeze
2
+ VERSION = "0.1.5".freeze
3
3
  end
data/lib/seira.rb CHANGED
@@ -30,7 +30,7 @@ module Seira
30
30
  'setup' => Seira::Setup
31
31
  }.freeze
32
32
 
33
- attr_reader :cluster, :app, :category, :action, :args
33
+ attr_reader :project, :cluster, :app, :category, :action, :args
34
34
  attr_reader :settings
35
35
 
36
36
  # Pop from beginning repeatedly for the first 4 main args, and then take the remaining back to original order for
@@ -64,6 +64,7 @@ module Seira
64
64
  end
65
65
 
66
66
  @cluster = @settings.full_cluster_name_for_shorthand(cluster)
67
+ @project = @settings.project_for_cluster(@cluster)
67
68
  end
68
69
 
69
70
  def run
@@ -100,7 +101,9 @@ module Seira
100
101
 
101
102
  def passed_context
102
103
  {
103
- cluster: cluster
104
+ cluster: cluster,
105
+ project: project,
106
+ default_zone: settings.default_zone
104
107
  }
105
108
  end
106
109
 
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.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Ringwelski
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-20 00:00:00.000000000 Z
11
+ date: 2017-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -106,6 +106,7 @@ files:
106
106
  - lib/seira/app.rb
107
107
  - lib/seira/cluster.rb
108
108
  - lib/seira/db.rb
109
+ - lib/seira/db/create.rb
109
110
  - lib/seira/memcached.rb
110
111
  - lib/seira/pods.rb
111
112
  - lib/seira/proxy.rb