seira 0.1.4 → 0.1.5

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 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