cuber 0.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.md +56 -0
- data/README.md +5 -0
- data/bin/cuber +3 -0
- data/cuber.gemspec +13 -0
- data/lib/cuber/cli.rb +55 -0
- data/lib/cuber/commands/deploy.rb +98 -0
- data/lib/cuber/commands/info.rb +134 -0
- data/lib/cuber/commands/logs.rb +18 -0
- data/lib/cuber/commands/restart.rb +14 -0
- data/lib/cuber/commands/run.rb +48 -0
- data/lib/cuber/commands/version.rb +13 -0
- data/lib/cuber/cuberfile_parser.rb +91 -0
- data/lib/cuber/cuberfile_validator.rb +146 -0
- data/lib/cuber/templates/deployment.yml.erb +332 -0
- data/lib/cuber/templates/pod.yml.erb +22 -0
- data/lib/cuber/utils.rb +24 -0
- data/lib/cuber/version.rb +3 -0
- data/lib/cuber.rb +15 -0
- metadata +43 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5561ce42263c62d390dc44cff4a723add20afd04bf432511f16d28259889c6e
|
4
|
+
data.tar.gz: e0740fa2bee7dc18e9bc3d6304fcb73b592a5c114bab3df8349cbd9e82b2547d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a3877f6bcfda0e789a3b9f906fd89026976bc7402491b82e106a80965376011e86425f208abc0ebb13cc56ed8272addd2b1511125a8911504bcb7a206d10e0e
|
7
|
+
data.tar.gz: d9faec4d34f5a3c756168e4b57898cc61411da003ad103b0911b24af39996bd1d936cbd40cdd49141e4caae3ab261f2ef37b0fa0a6b7b6b24551bb8c8cce8118
|
data/LICENSE.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# Cuber License
|
2
|
+
|
3
|
+
Copyright 2021 Cuber (AbstractBrain srls)
|
4
|
+
|
5
|
+
*This software end-user license agreement (EULA) is a legal agreement between you, the customer who has obtained a copy of the software, as an individual or as an organization, and Cuber (AbstractBrain srls).*
|
6
|
+
|
7
|
+
## License Grant
|
8
|
+
This EULA grants you a non-exclusive, non-transferable, limited license to install and use this software. You can use any number of copies of this software inside your organization.
|
9
|
+
|
10
|
+
## Ownership
|
11
|
+
The ownership and copyright of the software belongs to Cuber and is not transferred in any way by this license.
|
12
|
+
|
13
|
+
## Restricted Uses
|
14
|
+
You are not allowed to distribute, publish, sublicense or sell copies of this software.
|
15
|
+
It is your responsibility to never share your license key.
|
16
|
+
You are also not allowed to use the software as part of a product or service that provides similar functionality to the software itself.
|
17
|
+
|
18
|
+
## Modifications
|
19
|
+
You are allowed to inspect and make changes to this software for private use, excluding any modification made to circumvent licenses or copyright. You are also allowed to make temporary forks of the project in order to contribute to it. This EULA also applies to modified versions of the software.
|
20
|
+
|
21
|
+
## Contributions and Copyright Transfer
|
22
|
+
In case you contribute to the project, for example with suggestions or pull requests, you assign all rights, including copyright, from yourself to Cuber. You also confirm that your employer, if you have one, has given you the right to do so.
|
23
|
+
|
24
|
+
## No Warranty
|
25
|
+
This software is provided “as is” without warranty of any kind, either express or implied.
|
26
|
+
|
27
|
+
## Limitation of Liability
|
28
|
+
In no event shall the author of this software be liable for any damages (including, without limitation, damages for loss of business profits, business interruption, loss of data, or any other pecuniary loss), even if the author of this software is aware of the possibility of such damages and known defects.
|
29
|
+
In no event will Cuber liability exceed the software license price as indicated in the invoice.
|
30
|
+
|
31
|
+
## Use of Data
|
32
|
+
Cuber does not collect any kind of data, except for what is strictly necessary for billing purposes.
|
33
|
+
|
34
|
+
## Software Updates
|
35
|
+
You can receive software updates, if any, for the duration of the license that you have purchased.
|
36
|
+
|
37
|
+
## Fees and Payment
|
38
|
+
You can use this software for free, within the limits of the free tier, if present. When you purchase a license key, the software license fees will be due and payable in full at the time of purchase or when you decide to renew the license because it is expiring.
|
39
|
+
|
40
|
+
## Expiration of License Key
|
41
|
+
A license key has a limited duration and must be renewed after that billing period if you want to continue to use the software with all its functionalities.
|
42
|
+
|
43
|
+
## Termination
|
44
|
+
If the license expires or it is terminated due to violation of these terms or because one of the parties requests the termination of this contract, you must destroy all the copies of the software, including any modified version.
|
45
|
+
|
46
|
+
## Third Party Software
|
47
|
+
Third party software that is used by this software is subject to the respective license and is not subject to this EULA.
|
48
|
+
|
49
|
+
## Governing Law
|
50
|
+
This agreement is governed by the Italian law and you agree that any dispute arising from or relating to the subject matter of this EULA shall be governed by the exclusive jurisdiction and venue of the Italian courts.
|
51
|
+
|
52
|
+
## Amendment
|
53
|
+
Cuber reserves the right, in its sole discretion, to amend this EULA. You can find an updated version of this EULA by visiting <https://github.com/cuber-cloud/cuber-gem/blob/master/LICENSE.md>
|
54
|
+
|
55
|
+
## License and Copyright Notice
|
56
|
+
This license and the copyright notice must be retained on any copy of the software.
|
data/README.md
ADDED
data/bin/cuber
ADDED
data/cuber.gemspec
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative 'lib/cuber/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'cuber'
|
5
|
+
s.version = Cuber::VERSION
|
6
|
+
s.summary = 'Deploy your apps on Kubernetes easily.'
|
7
|
+
s.author = 'Cuber'
|
8
|
+
s.homepage = 'https://cuber.cloud'
|
9
|
+
s.license = 'LicenseRef-LICENSE.md'
|
10
|
+
s.executables = ['cuber']
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
s.add_dependency 'jwt'
|
13
|
+
end
|
data/lib/cuber/cli.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'open3'
|
4
|
+
require 'erb'
|
5
|
+
require 'base64'
|
6
|
+
require 'yaml'
|
7
|
+
require 'json'
|
8
|
+
require 'shellwords'
|
9
|
+
require 'time'
|
10
|
+
require 'openssl'
|
11
|
+
require 'jwt'
|
12
|
+
|
13
|
+
module Cuber
|
14
|
+
class CLI
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@options = {}
|
18
|
+
parse_command!
|
19
|
+
parse_cuberfile
|
20
|
+
validate_cuberfile
|
21
|
+
execute
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def parse_command!
|
27
|
+
@options[:cmd] = ARGV.shift&.to_sym
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_cuberfile
|
31
|
+
abort 'Cuberfile not found in current directory' unless File.exists? 'Cuberfile'
|
32
|
+
content = File.read 'Cuberfile'
|
33
|
+
parser = CuberfileParser.new
|
34
|
+
parser.instance_eval(content)
|
35
|
+
cuberfile_options = parser.instance_variables.map do |name|
|
36
|
+
[name[1..-1].to_sym, parser.instance_variable_get(name)]
|
37
|
+
end.to_h
|
38
|
+
@options.merge! cuberfile_options
|
39
|
+
end
|
40
|
+
|
41
|
+
def validate_cuberfile
|
42
|
+
validator = CuberfileValidator.new @options
|
43
|
+
errors = validator.validate
|
44
|
+
errors.each { |err| $stderr.puts "Cuberfile: #{err}" }
|
45
|
+
abort unless errors.empty?
|
46
|
+
end
|
47
|
+
|
48
|
+
def execute
|
49
|
+
command_class = @options[:cmd]&.capitalize
|
50
|
+
abort "Cuber: \"#{@options[:cmd]}\" is not a command" unless command_class && Cuber::Commands.const_defined?(command_class)
|
51
|
+
Cuber::Commands.const_get(command_class).new(@options).execute
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Cuber::Commands
|
2
|
+
class Deploy
|
3
|
+
include Cuber::Utils
|
4
|
+
|
5
|
+
def initialize options
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute
|
10
|
+
if @options[:release]
|
11
|
+
print_step 'Deploying a past release'
|
12
|
+
else
|
13
|
+
checkout
|
14
|
+
set_release_name
|
15
|
+
if @options[:buildpacks]
|
16
|
+
pack
|
17
|
+
else
|
18
|
+
build
|
19
|
+
push
|
20
|
+
end
|
21
|
+
end
|
22
|
+
configure
|
23
|
+
apply
|
24
|
+
rollout
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def print_step desc
|
30
|
+
puts
|
31
|
+
puts "\e[34m-----> #{desc}\e[0m"
|
32
|
+
end
|
33
|
+
|
34
|
+
def checkout
|
35
|
+
print_step 'Cloning Git repository'
|
36
|
+
path = '.cuber/repo'
|
37
|
+
FileUtils.mkdir_p path
|
38
|
+
FileUtils.rm_rf path, secure: true
|
39
|
+
system('git', 'clone', '--depth', '1', @options[:repo], path) || abort('Cuber: git clone failed')
|
40
|
+
end
|
41
|
+
|
42
|
+
def commit_hash
|
43
|
+
out, status = Open3.capture2 'git', 'rev-parse', '--short', 'HEAD', chdir: '.cuber/repo'
|
44
|
+
abort 'Cuber: cannot get commit hash' unless status.success?
|
45
|
+
out.strip
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_release_name
|
49
|
+
@options[:release] = "#{commit_hash}-#{Time.now.utc.iso8601.delete('^0-9')}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def pack
|
53
|
+
print_step 'Building image using buildpacks'
|
54
|
+
tag = "#{@options[:image]}:#{@options[:release]}"
|
55
|
+
cmd = ['pack', 'build', tag, '--builder', @options[:buildpacks], '--publish']
|
56
|
+
cmd += ['--pull-policy', 'always', '--clear-cache'] if @options[:cache] == false
|
57
|
+
system(*cmd, chdir: '.cuber/repo') || abort('Cuber: pack build failed')
|
58
|
+
end
|
59
|
+
|
60
|
+
def build
|
61
|
+
print_step 'Building image from Dockerfile'
|
62
|
+
dockerfile = @options[:dockerfile] || 'Dockerfile'
|
63
|
+
tag = "#{@options[:image]}:#{@options[:release]}"
|
64
|
+
cmd = ['docker', 'build']
|
65
|
+
cmd += ['--pull', '--no-cache'] if @options[:cache] == false
|
66
|
+
cmd += ['--progress', 'plain', '-f', dockerfile, '-t', tag, '.']
|
67
|
+
system(*cmd, chdir: '.cuber/repo') || abort('Cuber: docker build failed')
|
68
|
+
end
|
69
|
+
|
70
|
+
def push
|
71
|
+
print_step 'Pushing image to Docker registry'
|
72
|
+
tag = "#{@options[:image]}:#{@options[:release]}"
|
73
|
+
system('docker', 'push', tag) || abort('Cuber: docker push failed')
|
74
|
+
end
|
75
|
+
|
76
|
+
def configure
|
77
|
+
print_step 'Generating Kubernetes configuration'
|
78
|
+
@options[:instance] = "#{@options[:app]}-#{Time.now.utc.iso8601.delete('^0-9')}"
|
79
|
+
@options[:dockerconfigjson] = Base64.strict_encode64 File.read File.expand_path(@options[:dockerconfig] || '~/.docker/config.json')
|
80
|
+
render 'deployment.yml', '.cuber/kubernetes/deployment.yml'
|
81
|
+
end
|
82
|
+
|
83
|
+
def apply
|
84
|
+
print_step 'Applying configuration to Kubernetes cluster'
|
85
|
+
kubectl 'apply',
|
86
|
+
'-f', '.cuber/kubernetes/deployment.yml',
|
87
|
+
'--prune', '-l', "app.kubernetes.io/name=#{@options[:app]},app.kubernetes.io/managed-by=cuber"
|
88
|
+
end
|
89
|
+
|
90
|
+
def rollout
|
91
|
+
print_step 'Verifying deployment status'
|
92
|
+
@options[:procs].each_key do |procname|
|
93
|
+
kubectl 'rollout', 'status', "deployment/#{procname}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Cuber::Commands
|
2
|
+
class Info
|
3
|
+
include Cuber::Utils
|
4
|
+
|
5
|
+
def initialize options
|
6
|
+
@options = options
|
7
|
+
@namespace = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute
|
11
|
+
set_namespace
|
12
|
+
print_app_version
|
13
|
+
print_public_ip
|
14
|
+
print_env
|
15
|
+
print_migration
|
16
|
+
print_proc
|
17
|
+
print_cron
|
18
|
+
print_pods
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def set_namespace
|
24
|
+
@namespace = kubeget 'namespace', @options[:app]
|
25
|
+
abort 'Cuber: app not found' if @namespace.dig('metadata', 'labels', 'app.kubernetes.io/managed-by') != 'cuber'
|
26
|
+
end
|
27
|
+
|
28
|
+
def print_section title
|
29
|
+
puts
|
30
|
+
puts "\e[34m=== #{title}\e[0m"
|
31
|
+
end
|
32
|
+
|
33
|
+
def print_app_version
|
34
|
+
print_section 'App'
|
35
|
+
puts "#{@namespace['metadata']['labels']['app.kubernetes.io/name']}"
|
36
|
+
puts "version #{@namespace['metadata']['labels']['app.kubernetes.io/version']}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def print_public_ip
|
40
|
+
print_section 'Public IP'
|
41
|
+
if @namespace['metadata']['annotations']['ingress'] == 'true'
|
42
|
+
json = kubeget 'ingress', 'web-ingress'
|
43
|
+
else
|
44
|
+
json = kubeget 'service', 'load-balancer'
|
45
|
+
end
|
46
|
+
ip = json.dig 'status', 'loadBalancer', 'ingress', 0, 'ip'
|
47
|
+
if ip
|
48
|
+
puts "#{ip}"
|
49
|
+
else
|
50
|
+
puts "None detected"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def print_env
|
55
|
+
print_section 'Env'
|
56
|
+
json = kubeget 'configmap', 'env'
|
57
|
+
json['data']&.each do |key, value|
|
58
|
+
puts "#{key}=#{value}"
|
59
|
+
end
|
60
|
+
json = kubeget 'secrets', 'app-secrets'
|
61
|
+
json['data']&.each do |key, value|
|
62
|
+
puts "#{key}=#{Base64.decode64(value)[0...5] + '***'}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def print_migration
|
67
|
+
print_section 'Migration'
|
68
|
+
migration = "migrate-#{@namespace['metadata']['labels']['app.kubernetes.io/instance']}"
|
69
|
+
json = kubeget 'job', migration, '--ignore-not-found'
|
70
|
+
if json
|
71
|
+
migration_command = json['spec']['template']['spec']['containers'][0]['command'].shelljoin
|
72
|
+
migration_status = json['status']['succeeded'].to_i.zero? ? 'Pending' : 'Completed'
|
73
|
+
puts "migrate: #{migration_command} (#{migration_status})"
|
74
|
+
else
|
75
|
+
puts "None detected"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def print_proc
|
80
|
+
print_section 'Proc'
|
81
|
+
json = kubeget 'deployments'
|
82
|
+
json['items'].each do |proc|
|
83
|
+
name = proc['metadata']['name']
|
84
|
+
command = proc['spec']['template']['spec']['containers'][0]['command'].shelljoin
|
85
|
+
available = proc['status']['availableReplicas'].to_i
|
86
|
+
updated = proc['status']['updatedReplicas'].to_i
|
87
|
+
replicas = proc['status']['replicas'].to_i
|
88
|
+
scale = proc['spec']['replicas'].to_i
|
89
|
+
puts "#{name}: #{command} (#{available}/#{scale}) #{'OUT-OF-DATE' if replicas - updated > 0}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def print_cron
|
94
|
+
print_section 'Cron'
|
95
|
+
json = kubeget 'cronjobs'
|
96
|
+
json['items'].each do |cron|
|
97
|
+
name = cron['metadata']['name']
|
98
|
+
schedule = cron['spec']['schedule']
|
99
|
+
command = cron['spec']['jobTemplate']['spec']['template']['spec']['containers'][0]['command'].shelljoin
|
100
|
+
last = cron['status']['lastScheduleTime']
|
101
|
+
puts "#{name}: #{schedule} #{command} (#{time_ago_in_words last})"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def print_pods
|
106
|
+
print_section 'Pods'
|
107
|
+
json = kubeget 'pods'
|
108
|
+
json['items'].each do |pod|
|
109
|
+
name = pod['metadata']['name']
|
110
|
+
created_at = pod['metadata']['creationTimestamp']
|
111
|
+
pod_status = pod['status']['phase']
|
112
|
+
container_ready = pod['status']['containerStatuses'][0]['ready']
|
113
|
+
container_status = pod['status']['containerStatuses'][0]['state'].values.first['reason']
|
114
|
+
if pod_status == 'Succeeded' || (pod_status == 'Running' && container_ready)
|
115
|
+
puts "#{name}: \e[32m#{container_status || pod_status}\e[0m (#{time_ago_in_words created_at})"
|
116
|
+
else
|
117
|
+
puts "#{name}: \e[31m#{container_status || pod_status}\e[0m (#{time_ago_in_words created_at})"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def time_ago_in_words time
|
123
|
+
time = Time.parse time unless time.is_a? Time
|
124
|
+
seconds = (Time.now - time).round
|
125
|
+
case
|
126
|
+
when seconds < 60 then "#{seconds}s"
|
127
|
+
when seconds < 60*60 then "#{(seconds / 60)}m"
|
128
|
+
when seconds < 60*60*24 then "#{(seconds / 60 / 60)}h"
|
129
|
+
else "#{(seconds / 60 / 60 / 24)}d"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Cuber::Commands
|
2
|
+
class Logs
|
3
|
+
include Cuber::Utils
|
4
|
+
|
5
|
+
def initialize options
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute
|
10
|
+
pod = ARGV.first
|
11
|
+
cmd = ['logs']
|
12
|
+
cmd += pod ? [pod] : ['-l', "app.kubernetes.io/name=#{@options[:app]}"]
|
13
|
+
cmd += ['--all-containers']
|
14
|
+
kubectl *cmd
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Cuber::Commands
|
2
|
+
class Run
|
3
|
+
include Cuber::Utils
|
4
|
+
|
5
|
+
def initialize options
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute
|
10
|
+
set_current_release
|
11
|
+
kubeexec command
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def set_current_release
|
17
|
+
json = kubeget 'namespace', @options[:app]
|
18
|
+
@options[:app] = json['metadata']['labels']['app.kubernetes.io/name']
|
19
|
+
@options[:release] = json['metadata']['labels']['app.kubernetes.io/version']
|
20
|
+
@options[:image] = json['metadata']['annotations']['image']
|
21
|
+
@options[:buildpacks] = json['metadata']['annotations']['buildpacks']
|
22
|
+
end
|
23
|
+
|
24
|
+
def command
|
25
|
+
if ARGV.length == 0
|
26
|
+
'sh'
|
27
|
+
elsif ARGV.length == 1
|
28
|
+
ARGV.first
|
29
|
+
else
|
30
|
+
ARGV.shelljoin
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def kubeexec command
|
35
|
+
@options[:pod] = "pod-#{command.downcase.gsub(/[^a-z0-9]+/, '-')}-#{Time.now.utc.iso8601.delete('^0-9')}"
|
36
|
+
path = ".cuber/kubernetes/#{@options[:pod]}.yml"
|
37
|
+
full_command = command.shellsplit
|
38
|
+
full_command.unshift 'launcher' if @options[:buildpacks]
|
39
|
+
render 'pod.yml', path
|
40
|
+
kubectl 'apply', '-f', path
|
41
|
+
kubectl 'wait', '--for', 'condition=ready', "pod/#{@options[:pod]}"
|
42
|
+
kubectl 'exec', '-it', @options[:pod], '--', *full_command
|
43
|
+
kubectl 'delete', 'pod', @options[:pod], '--wait=false'
|
44
|
+
File.delete path
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Cuber
|
2
|
+
class CuberfileParser
|
3
|
+
def initialize
|
4
|
+
@app = nil
|
5
|
+
@release = nil
|
6
|
+
@repo = nil
|
7
|
+
@buildpacks = nil
|
8
|
+
@dockerfile = nil
|
9
|
+
@image = nil
|
10
|
+
@cache = nil
|
11
|
+
@dockerconfig = nil
|
12
|
+
@kubeconfig = nil
|
13
|
+
@migrate = nil
|
14
|
+
@procs = {}
|
15
|
+
@cron = {}
|
16
|
+
@secrets = {}
|
17
|
+
@env = {}
|
18
|
+
@lb = {}
|
19
|
+
@ingress = nil
|
20
|
+
@ssl = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing m, *args
|
24
|
+
abort "Cuberfile: \"#{m}\" is not a command"
|
25
|
+
end
|
26
|
+
|
27
|
+
def app name
|
28
|
+
@app = name
|
29
|
+
end
|
30
|
+
|
31
|
+
def release version
|
32
|
+
@release = version
|
33
|
+
end
|
34
|
+
|
35
|
+
def repo uri
|
36
|
+
@repo = uri
|
37
|
+
end
|
38
|
+
|
39
|
+
def buildpacks builder
|
40
|
+
@buildpacks = builder
|
41
|
+
end
|
42
|
+
|
43
|
+
def dockerfile path
|
44
|
+
@dockerfile = path
|
45
|
+
end
|
46
|
+
|
47
|
+
def image name
|
48
|
+
@image = name
|
49
|
+
end
|
50
|
+
|
51
|
+
def cache enabled
|
52
|
+
@cache = enabled
|
53
|
+
end
|
54
|
+
|
55
|
+
def dockerconfig path
|
56
|
+
@dockerconfig = path
|
57
|
+
end
|
58
|
+
|
59
|
+
def kubeconfig path
|
60
|
+
@kubeconfig = path
|
61
|
+
end
|
62
|
+
|
63
|
+
def migrate cmd, check: nil
|
64
|
+
@migrate = { cmd: cmd, check: check }
|
65
|
+
end
|
66
|
+
|
67
|
+
def proc name, cmd, scale: 1, env: {}
|
68
|
+
@procs[name] = { cmd: cmd, scale: scale, env: env }
|
69
|
+
end
|
70
|
+
|
71
|
+
def cron name, schedule, cmd
|
72
|
+
@cron[name] = { schedule: schedule, cmd: cmd }
|
73
|
+
end
|
74
|
+
|
75
|
+
def env key, value, secret: false
|
76
|
+
secret ? (@secrets[key] = value) : (@env[key] = value)
|
77
|
+
end
|
78
|
+
|
79
|
+
def lb key, value
|
80
|
+
@lb[key] = value
|
81
|
+
end
|
82
|
+
|
83
|
+
def ingress enabled
|
84
|
+
@ingress = enabled
|
85
|
+
end
|
86
|
+
|
87
|
+
def ssl crt, key
|
88
|
+
@ssl = { crt: crt, key: key }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module Cuber
|
2
|
+
class CuberfileValidator
|
3
|
+
|
4
|
+
def initialize options
|
5
|
+
@options = options
|
6
|
+
@errors = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def validate
|
10
|
+
validate_app
|
11
|
+
validate_release
|
12
|
+
validate_repo
|
13
|
+
validate_buildpacks
|
14
|
+
validate_dockerfile
|
15
|
+
validate_image
|
16
|
+
validate_cache
|
17
|
+
validate_dockerconfig
|
18
|
+
validate_kubeconfig
|
19
|
+
validate_migrate
|
20
|
+
validate_procs
|
21
|
+
validate_cron
|
22
|
+
validate_env
|
23
|
+
validate_lb
|
24
|
+
validate_ingress
|
25
|
+
validate_ssl
|
26
|
+
validate_key
|
27
|
+
validate_limits
|
28
|
+
@errors
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def validate_app
|
34
|
+
@errors << 'app name must be present' if @options[:app].to_s.strip.empty?
|
35
|
+
@errors << 'app name can only include lowercase letters, digits or dashes' if @options[:app] !~ /\A[a-z0-9\-]+\z/
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate_release
|
39
|
+
return unless @options[:release]
|
40
|
+
@errors << 'release has an invalid format' if @options[:release] !~ /\A[a-zA-Z0-9_\-\.]+\z/
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate_repo
|
44
|
+
@errors << 'repo must be present' if @options[:repo].to_s.strip.empty?
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate_buildpacks
|
48
|
+
return unless @options[:buildpacks]
|
49
|
+
@errors << 'buildpacks is not compatible with the dockerfile option' if @options[:dockerfile]
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate_dockerfile
|
53
|
+
return unless @options[:dockerfile]
|
54
|
+
@errors << 'dockerfile must be a file' unless File.exists? @options[:dockerfile]
|
55
|
+
end
|
56
|
+
|
57
|
+
def validate_image
|
58
|
+
@errors << 'image must be present' if @options[:image].to_s.strip.empty?
|
59
|
+
end
|
60
|
+
|
61
|
+
def validate_cache
|
62
|
+
return unless @options[:cache]
|
63
|
+
@errors << 'cache must be true or false' if @options[:cache] != true && @options[:cache] != false
|
64
|
+
end
|
65
|
+
|
66
|
+
def validate_dockerconfig
|
67
|
+
return unless @options[:dockerconfig]
|
68
|
+
@errors << 'dockerconfig must be a file' unless File.exists? @options[:dockerconfig]
|
69
|
+
end
|
70
|
+
|
71
|
+
def validate_kubeconfig
|
72
|
+
@errors << 'kubeconfig must be present' if @options[:kubeconfig].to_s.strip.empty?
|
73
|
+
@errors << 'kubeconfig must be a file' unless File.exists? @options[:kubeconfig]
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_migrate
|
77
|
+
return unless @options[:migrate]
|
78
|
+
@errors << 'migrate command must be present' if @options[:migrate][:cmd].to_s.strip.empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
def validate_procs
|
82
|
+
@options[:procs].each do |procname, proc|
|
83
|
+
@errors << "proc \"#{procname}\" name can only include lowercase letters" if procname !~ /\A[a-z]+\z/
|
84
|
+
@errors << "proc \"#{procname}\" command must be present" if proc[:cmd].to_s.strip.empty?
|
85
|
+
@errors << "proc \"#{procname}\" scale must be a positive number" unless proc[:scale].is_a?(Integer) && proc[:scale] > 0
|
86
|
+
proc[:env].each do |key, value|
|
87
|
+
@errors << "proc \"#{procname}\" env name can only include uppercase letters, digits or underscores" if key !~ /\A[A-Z_]+[A-Z0-9_]*\z/
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def validate_cron
|
93
|
+
@options[:cron].each do |jobname, cron|
|
94
|
+
@errors << "cron \"#{jobname}\" name can only include lowercase letters" if jobname !~ /\A[a-z]+\z/
|
95
|
+
@errors << "cron \"#{jobname}\" schedule must be present" if cron[:schedule].to_s.strip.empty?
|
96
|
+
@errors << "cron \"#{jobname}\" command must be present" if cron[:cmd].to_s.strip.empty?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def validate_env
|
101
|
+
@options[:env].merge(@options[:secrets]).each do |key, value|
|
102
|
+
@errors << "env \"#{key}\" name can only include uppercase letters, digits or underscores" if key !~ /\A[A-Z_]+[A-Z0-9_]*\z/
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def validate_lb
|
107
|
+
@options[:lb].each do |key, value|
|
108
|
+
@errors << "lb \"#{key}\" key can only include letters, digits, underscores, dashes, dots or slash" if key !~ /\A[a-zA-Z0-9_\-\.\/]+\z/
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def validate_ingress
|
113
|
+
return unless @options[:ingress]
|
114
|
+
@errors << 'ingress must be true or false' if @options[:ingress] != true && @options[:ingress] != false
|
115
|
+
end
|
116
|
+
|
117
|
+
def validate_ssl
|
118
|
+
return unless @options[:ssl]
|
119
|
+
@errors << 'ssl crt must be a file' unless File.exists? @options[:ssl][:crt]
|
120
|
+
@errors << 'ssl key must be a file' unless File.exists? @options[:ssl][:key]
|
121
|
+
end
|
122
|
+
|
123
|
+
def validate_key
|
124
|
+
return unless File.exists? File.expand_path '~/.cuber.key'
|
125
|
+
token = File.read File.expand_path '~/.cuber.key'
|
126
|
+
ecdsa_public = OpenSSL::PKey.read <<~PEM
|
127
|
+
-----BEGIN PUBLIC KEY-----
|
128
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERAh4uT9yojc06y5wgU6CY6sr0Hrv
|
129
|
+
P8AUw6uw2PgUdbm7DKkJwFvQYMj3g+TmrxmPV3KQ8uzegfYRbHr6DyonNQ==
|
130
|
+
-----END PUBLIC KEY-----
|
131
|
+
PEM
|
132
|
+
JWT.decode token, ecdsa_public, true, { iss: 'Cuber', verify_iss: true, algorithm: 'ES256' }
|
133
|
+
rescue JWT::DecodeError
|
134
|
+
@errors << 'your license key is invalid or expired'
|
135
|
+
end
|
136
|
+
|
137
|
+
def validate_limits
|
138
|
+
return if File.exists? File.expand_path '~/.cuber.key'
|
139
|
+
scale = @options[:procs].collect { |procname, proc| proc[:scale].to_i }.sum
|
140
|
+
@errors << 'please purchase a license key or reduce the number of procs' if scale > 5
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
Cuber::CuberfileValidator.freeze
|
@@ -0,0 +1,332 @@
|
|
1
|
+
kind: Namespace
|
2
|
+
apiVersion: v1
|
3
|
+
metadata:
|
4
|
+
name: <%= @options[:app] %>
|
5
|
+
labels:
|
6
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
7
|
+
app.kubernetes.io/instance: <%= @options[:instance] %>
|
8
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
9
|
+
app.kubernetes.io/managed-by: cuber
|
10
|
+
annotations:
|
11
|
+
image: <%= @options[:image].to_s.to_json %>
|
12
|
+
buildpacks: <%= @options[:buildpacks].to_s.to_json %>
|
13
|
+
ingress: <%= @options[:ingress].to_s.to_json %>
|
14
|
+
|
15
|
+
---
|
16
|
+
apiVersion: v1
|
17
|
+
kind: Secret
|
18
|
+
metadata:
|
19
|
+
name: regcred
|
20
|
+
namespace: <%= @options[:app] %>
|
21
|
+
labels:
|
22
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
23
|
+
app.kubernetes.io/instance: <%= @options[:instance] %>
|
24
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
25
|
+
app.kubernetes.io/managed-by: cuber
|
26
|
+
data:
|
27
|
+
.dockerconfigjson: <%= @options[:dockerconfigjson] %>
|
28
|
+
type: kubernetes.io/dockerconfigjson
|
29
|
+
|
30
|
+
---
|
31
|
+
apiVersion: v1
|
32
|
+
kind: Secret
|
33
|
+
metadata:
|
34
|
+
name: app-secrets
|
35
|
+
namespace: <%= @options[:app] %>
|
36
|
+
labels:
|
37
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
38
|
+
app.kubernetes.io/instance: <%= @options[:instance] %>
|
39
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
40
|
+
app.kubernetes.io/managed-by: cuber
|
41
|
+
data:
|
42
|
+
<%- @options[:secrets].each do |key, value| -%>
|
43
|
+
<%= key %>: <%= Base64.strict_encode64 value %>
|
44
|
+
<%- end -%>
|
45
|
+
|
46
|
+
---
|
47
|
+
apiVersion: v1
|
48
|
+
kind: ConfigMap
|
49
|
+
metadata:
|
50
|
+
name: env
|
51
|
+
namespace: <%= @options[:app] %>
|
52
|
+
labels:
|
53
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
54
|
+
app.kubernetes.io/instance: <%= @options[:instance] %>
|
55
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
56
|
+
app.kubernetes.io/managed-by: cuber
|
57
|
+
data:
|
58
|
+
<%- @options[:env].each do |key, value| -%>
|
59
|
+
<%= key %>: <%= value.to_s.to_json %>
|
60
|
+
<%- end -%>
|
61
|
+
|
62
|
+
<%- if @options[:migrate] -%>
|
63
|
+
---
|
64
|
+
apiVersion: batch/v1
|
65
|
+
kind: Job
|
66
|
+
metadata:
|
67
|
+
name: migrate-<%= @options[:instance] %>
|
68
|
+
namespace: <%= @options[:app] %>
|
69
|
+
labels:
|
70
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
71
|
+
app.kubernetes.io/instance: <%= @options[:instance] %>
|
72
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
73
|
+
app.kubernetes.io/managed-by: cuber
|
74
|
+
spec:
|
75
|
+
template:
|
76
|
+
metadata:
|
77
|
+
labels:
|
78
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
79
|
+
app.kubernetes.io/instance: <%= @options[:instance] %>
|
80
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
81
|
+
app.kubernetes.io/managed-by: cuber
|
82
|
+
spec:
|
83
|
+
containers:
|
84
|
+
- name: migration
|
85
|
+
image: <%= @options[:image] %>:<%= @options[:release] %>
|
86
|
+
imagePullPolicy: Always
|
87
|
+
<%- if @options[:buildpacks] -%>
|
88
|
+
command: ["launcher"]
|
89
|
+
args: <%= @options[:migrate][:cmd].shellsplit %>
|
90
|
+
<%- else -%>
|
91
|
+
command: <%= @options[:migrate][:cmd].shellsplit %>
|
92
|
+
<%- end -%>
|
93
|
+
envFrom:
|
94
|
+
- configMapRef:
|
95
|
+
name: env
|
96
|
+
- secretRef:
|
97
|
+
name: app-secrets
|
98
|
+
imagePullSecrets:
|
99
|
+
- name: regcred
|
100
|
+
restartPolicy: Never
|
101
|
+
<%- end -%>
|
102
|
+
|
103
|
+
<%- @options[:procs].each do |procname, proc| -%>
|
104
|
+
---
|
105
|
+
apiVersion: apps/v1
|
106
|
+
kind: Deployment
|
107
|
+
metadata:
|
108
|
+
name: <%= procname %>
|
109
|
+
namespace: <%= @options[:app] %>
|
110
|
+
labels:
|
111
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
112
|
+
app.kubernetes.io/instance: <%= @options[:instance] %>
|
113
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
114
|
+
app.kubernetes.io/managed-by: cuber
|
115
|
+
spec:
|
116
|
+
revisionHistoryLimit: 0
|
117
|
+
replicas: <%= proc[:scale] %>
|
118
|
+
selector:
|
119
|
+
matchLabels:
|
120
|
+
app: <%= procname %>-proc
|
121
|
+
template:
|
122
|
+
metadata:
|
123
|
+
labels:
|
124
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
125
|
+
app.kubernetes.io/instance: <%= @options[:instance] %>
|
126
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
127
|
+
app.kubernetes.io/managed-by: cuber
|
128
|
+
app: <%= procname %>-proc
|
129
|
+
spec:
|
130
|
+
containers:
|
131
|
+
- name: <%= procname %>-proc
|
132
|
+
image: <%= @options[:image] %>:<%= @options[:release] %>
|
133
|
+
imagePullPolicy: Always
|
134
|
+
<%- if @options[:buildpacks] -%>
|
135
|
+
command: ["launcher"]
|
136
|
+
args: <%= proc[:cmd].shellsplit %>
|
137
|
+
<%- else -%>
|
138
|
+
command: <%= proc[:cmd].shellsplit %>
|
139
|
+
<%- end -%>
|
140
|
+
envFrom:
|
141
|
+
- configMapRef:
|
142
|
+
name: env
|
143
|
+
- secretRef:
|
144
|
+
name: app-secrets
|
145
|
+
env:
|
146
|
+
<%- proc[:env].each do |key, value| -%>
|
147
|
+
- name: <%= key %>
|
148
|
+
value: <%= value.to_s.to_json %>
|
149
|
+
<%- end -%>
|
150
|
+
<%- if procname.to_s == 'web' -%>
|
151
|
+
- name: PORT
|
152
|
+
value: "8080"
|
153
|
+
ports:
|
154
|
+
- containerPort: 8080
|
155
|
+
readinessProbe:
|
156
|
+
httpGet:
|
157
|
+
path: /
|
158
|
+
port: 8080
|
159
|
+
<%- end -%>
|
160
|
+
<%- if @options[:migrate] && @options[:migrate][:check] -%>
|
161
|
+
initContainers:
|
162
|
+
- name: migration-check
|
163
|
+
image: <%= @options[:image] %>:<%= @options[:release] %>
|
164
|
+
imagePullPolicy: Always
|
165
|
+
<%- if @options[:buildpacks] -%>
|
166
|
+
command: ["launcher"]
|
167
|
+
args: <%= @options[:migrate][:check].shellsplit %>
|
168
|
+
<%- else -%>
|
169
|
+
command: <%= @options[:migrate][:check].shellsplit %>
|
170
|
+
<%- end -%>
|
171
|
+
envFrom:
|
172
|
+
- configMapRef:
|
173
|
+
name: env
|
174
|
+
- secretRef:
|
175
|
+
name: app-secrets
|
176
|
+
<%- end -%>
|
177
|
+
imagePullSecrets:
|
178
|
+
- name: regcred
|
179
|
+
<%- end -%>
|
180
|
+
|
181
|
+
<%- @options[:cron].each do |jobname, cron| -%>
|
182
|
+
---
|
183
|
+
apiVersion: batch/v1
|
184
|
+
kind: CronJob
|
185
|
+
metadata:
|
186
|
+
name: cron-<%= jobname %>
|
187
|
+
namespace: <%= @options[:app] %>
|
188
|
+
labels:
|
189
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
190
|
+
app.kubernetes.io/instance: <%= @options[:instance] %>
|
191
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
192
|
+
app.kubernetes.io/managed-by: cuber
|
193
|
+
spec:
|
194
|
+
schedule: <%= cron[:schedule].to_s.to_json %>
|
195
|
+
concurrencyPolicy: Forbid
|
196
|
+
successfulJobsHistoryLimit: 1
|
197
|
+
failedJobsHistoryLimit: 1
|
198
|
+
jobTemplate:
|
199
|
+
metadata:
|
200
|
+
labels:
|
201
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
202
|
+
app.kubernetes.io/instance: <%= @options[:instance] %>
|
203
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
204
|
+
app.kubernetes.io/managed-by: cuber
|
205
|
+
spec:
|
206
|
+
backoffLimit: 0
|
207
|
+
template:
|
208
|
+
metadata:
|
209
|
+
labels:
|
210
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
211
|
+
app.kubernetes.io/instance: <%= @options[:instance] %>
|
212
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
213
|
+
app.kubernetes.io/managed-by: cuber
|
214
|
+
spec:
|
215
|
+
containers:
|
216
|
+
- name: task
|
217
|
+
image: <%= @options[:image] %>:<%= @options[:release] %>
|
218
|
+
imagePullPolicy: Always
|
219
|
+
<%- if @options[:buildpacks] -%>
|
220
|
+
command: ["launcher"]
|
221
|
+
args: <%= cron[:cmd].shellsplit %>
|
222
|
+
<%- else -%>
|
223
|
+
command: <%= cron[:cmd].shellsplit %>
|
224
|
+
<%- end -%>
|
225
|
+
envFrom:
|
226
|
+
- configMapRef:
|
227
|
+
name: env
|
228
|
+
- secretRef:
|
229
|
+
name: app-secrets
|
230
|
+
imagePullSecrets:
|
231
|
+
- name: regcred
|
232
|
+
restartPolicy: Never
|
233
|
+
<%- end -%>
|
234
|
+
|
235
|
+
<%- if @options[:ssl] -%>
|
236
|
+
---
|
237
|
+
apiVersion: v1
|
238
|
+
kind: Secret
|
239
|
+
metadata:
|
240
|
+
name: ssl
|
241
|
+
namespace: <%= @options[:app] %>
|
242
|
+
labels:
|
243
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
244
|
+
app.kubernetes.io/instance: <%= @options[:instance] %>
|
245
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
246
|
+
app.kubernetes.io/managed-by: cuber
|
247
|
+
data:
|
248
|
+
tls.crt: <%= Base64.strict_encode64 File.read @options[:ssl][:crt] %>
|
249
|
+
tls.key: <%= Base64.strict_encode64 File.read @options[:ssl][:key] %>
|
250
|
+
type: kubernetes.io/tls
|
251
|
+
<%- end -%>
|
252
|
+
|
253
|
+
<%- if @options[:ingress] -%>
|
254
|
+
---
|
255
|
+
apiVersion: v1
|
256
|
+
kind: Service
|
257
|
+
metadata:
|
258
|
+
name: web-service
|
259
|
+
namespace: <%= @options[:app] %>
|
260
|
+
labels:
|
261
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
262
|
+
app.kubernetes.io/instance: <%= @options[:instance] %>
|
263
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
264
|
+
app.kubernetes.io/managed-by: cuber
|
265
|
+
spec:
|
266
|
+
selector:
|
267
|
+
app: web-proc
|
268
|
+
ports:
|
269
|
+
- protocol: TCP
|
270
|
+
port: 80
|
271
|
+
targetPort: 8080
|
272
|
+
|
273
|
+
---
|
274
|
+
apiVersion: networking.k8s.io/v1
|
275
|
+
kind: Ingress
|
276
|
+
metadata:
|
277
|
+
name: web-ingress
|
278
|
+
namespace: <%= @options[:app] %>
|
279
|
+
labels:
|
280
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
281
|
+
app.kubernetes.io/instance: <%= @options[:instance] %>
|
282
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
283
|
+
app.kubernetes.io/managed-by: cuber
|
284
|
+
annotations:
|
285
|
+
<%- @options[:lb].each do |key, value| -%>
|
286
|
+
<%= key %>: <%= value.to_s.to_json %>
|
287
|
+
<%- end -%>
|
288
|
+
spec:
|
289
|
+
<%- if @options[:ssl] -%>
|
290
|
+
tls:
|
291
|
+
- secretName: ssl
|
292
|
+
<%- end -%>
|
293
|
+
rules:
|
294
|
+
- http:
|
295
|
+
paths:
|
296
|
+
- path: /
|
297
|
+
pathType: Prefix
|
298
|
+
backend:
|
299
|
+
service:
|
300
|
+
name: web-service
|
301
|
+
port:
|
302
|
+
number: 80
|
303
|
+
<%- else -%>
|
304
|
+
---
|
305
|
+
apiVersion: v1
|
306
|
+
kind: Service
|
307
|
+
metadata:
|
308
|
+
name: load-balancer
|
309
|
+
namespace: <%= @options[:app] %>
|
310
|
+
labels:
|
311
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
312
|
+
app.kubernetes.io/instance: <%= @options[:instance] %>
|
313
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
314
|
+
app.kubernetes.io/managed-by: cuber
|
315
|
+
annotations:
|
316
|
+
<%- @options[:lb].each do |key, value| -%>
|
317
|
+
<%= key %>: <%= value.to_s.to_json %>
|
318
|
+
<%- end -%>
|
319
|
+
spec:
|
320
|
+
type: LoadBalancer
|
321
|
+
selector:
|
322
|
+
app: web-proc
|
323
|
+
ports:
|
324
|
+
- name: http
|
325
|
+
protocol: TCP
|
326
|
+
port: 80
|
327
|
+
targetPort: 8080
|
328
|
+
- name: https
|
329
|
+
protocol: TCP
|
330
|
+
port: 443
|
331
|
+
targetPort: 8080
|
332
|
+
<%- end -%>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
apiVersion: v1
|
2
|
+
kind: Pod
|
3
|
+
metadata:
|
4
|
+
name: <%= @options[:pod] %>
|
5
|
+
namespace: <%= @options[:app] %>
|
6
|
+
labels:
|
7
|
+
app.kubernetes.io/name: <%= @options[:app] %>
|
8
|
+
app.kubernetes.io/version: <%= @options[:release] %>
|
9
|
+
app.kubernetes.io/managed-by: cuber
|
10
|
+
spec:
|
11
|
+
containers:
|
12
|
+
- name: pod-proc
|
13
|
+
image: <%= @options[:image] %>:<%= @options[:release] %>
|
14
|
+
imagePullPolicy: Always
|
15
|
+
command: ["sleep", "infinity"]
|
16
|
+
envFrom:
|
17
|
+
- configMapRef:
|
18
|
+
name: env
|
19
|
+
- secretRef:
|
20
|
+
name: app-secrets
|
21
|
+
imagePullSecrets:
|
22
|
+
- name: regcred
|
data/lib/cuber/utils.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Cuber::Utils
|
2
|
+
|
3
|
+
def kubectl *args
|
4
|
+
cmd = ['kubectl', '--kubeconfig', @options[:kubeconfig], '-n', @options[:app]] + args
|
5
|
+
system(*cmd) || abort("Cuber: \"#{cmd.shelljoin}\" failed")
|
6
|
+
end
|
7
|
+
|
8
|
+
def kubeget type, name = nil, *args
|
9
|
+
cmd = ['kubectl', 'get', type, name, '-o', 'json', '--kubeconfig', @options[:kubeconfig], '-n', @options[:app], *args].compact
|
10
|
+
out, status = Open3.capture2 *cmd
|
11
|
+
abort "Cuber: \"#{cmd.shelljoin}\" failed" unless status.success?
|
12
|
+
out.empty? ? nil : JSON.parse(out)
|
13
|
+
end
|
14
|
+
|
15
|
+
def render template, target_file = nil
|
16
|
+
template = File.join __dir__, 'templates', "#{template}.erb"
|
17
|
+
renderer = ERB.new File.read(template), trim_mode: '-'
|
18
|
+
content = renderer.result binding
|
19
|
+
return content unless target_file
|
20
|
+
FileUtils.mkdir_p File.dirname target_file
|
21
|
+
File.write target_file, content
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/cuber.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'cuber/version'
|
2
|
+
require 'cuber/cli'
|
3
|
+
require 'cuber/cuberfile_parser'
|
4
|
+
require 'cuber/cuberfile_validator'
|
5
|
+
require 'cuber/utils'
|
6
|
+
require 'cuber/commands/version'
|
7
|
+
require 'cuber/commands/info'
|
8
|
+
require 'cuber/commands/logs'
|
9
|
+
require 'cuber/commands/restart'
|
10
|
+
require 'cuber/commands/run'
|
11
|
+
require 'cuber/commands/deploy'
|
12
|
+
|
13
|
+
module Cuber
|
14
|
+
|
15
|
+
end
|
metadata
CHANGED
@@ -1,23 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cuber
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cuber
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
11
|
+
date: 2022-02-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: jwt
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
description:
|
14
28
|
email:
|
15
|
-
executables:
|
29
|
+
executables:
|
30
|
+
- cuber
|
16
31
|
extensions: []
|
17
32
|
extra_rdoc_files: []
|
18
|
-
files:
|
19
|
-
|
20
|
-
|
33
|
+
files:
|
34
|
+
- LICENSE.md
|
35
|
+
- README.md
|
36
|
+
- bin/cuber
|
37
|
+
- cuber.gemspec
|
38
|
+
- lib/cuber.rb
|
39
|
+
- lib/cuber/cli.rb
|
40
|
+
- lib/cuber/commands/deploy.rb
|
41
|
+
- lib/cuber/commands/info.rb
|
42
|
+
- lib/cuber/commands/logs.rb
|
43
|
+
- lib/cuber/commands/restart.rb
|
44
|
+
- lib/cuber/commands/run.rb
|
45
|
+
- lib/cuber/commands/version.rb
|
46
|
+
- lib/cuber/cuberfile_parser.rb
|
47
|
+
- lib/cuber/cuberfile_validator.rb
|
48
|
+
- lib/cuber/templates/deployment.yml.erb
|
49
|
+
- lib/cuber/templates/pod.yml.erb
|
50
|
+
- lib/cuber/utils.rb
|
51
|
+
- lib/cuber/version.rb
|
52
|
+
homepage: https://cuber.cloud
|
53
|
+
licenses:
|
54
|
+
- LicenseRef-LICENSE.md
|
21
55
|
metadata: {}
|
22
56
|
post_install_message:
|
23
57
|
rdoc_options: []
|
@@ -34,8 +68,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
34
68
|
- !ruby/object:Gem::Version
|
35
69
|
version: '0'
|
36
70
|
requirements: []
|
37
|
-
rubygems_version: 3.2.
|
71
|
+
rubygems_version: 3.2.32
|
38
72
|
signing_key:
|
39
73
|
specification_version: 4
|
40
|
-
summary:
|
74
|
+
summary: Deploy your apps on Kubernetes easily.
|
41
75
|
test_files: []
|