cuber 0.0.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +86 -0
- data/SSAL-LICENSE +9 -0
- data/bin/cuber +3 -0
- data/cuber.gemspec +13 -0
- data/lib/cuber/cli.rb +55 -0
- data/lib/cuber/commands/deploy.rb +101 -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: 26ed60418289b2d4b6e39d87691661c9c5d9c57cac883d4d21a224d7e7fc7271
|
4
|
+
data.tar.gz: c4093343c2fac80c5adabba7cfe285e60e30fc722d4dbb01e24df840144bbbbf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 168df66b846819cda138affc0bdbbd95185815367aeaadc210ef215404b514239ed812ed2f18885255c9909a1a3345e4819b96709fd2387e5586c6037025327c
|
7
|
+
data.tar.gz: ea4fffbab3fd2c3ba50d2ff587a029472ea826fad9deb33c95190b54c331ca97ea1f1cbafb1e4fc5c4627540bcfc256c6b00e31286087879df366701ca76df41
|
data/README.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
<a href="https://cuber.cloud"><img src="https://cuber.cloud/assets/images/logo.svg" alt="Cuber" height="80" width="80"></a>
|
2
|
+
|
3
|
+
# CUBER
|
4
|
+
|
5
|
+
[](https://badge.fury.io/rb/cuber)
|
6
|
+
|
7
|
+
Deploy your apps on Kubernetes easily.
|
8
|
+
|
9
|
+
## What is Cuber?
|
10
|
+
|
11
|
+
Cuber is an automation tool (written in Ruby) that can package and deploy your apps (written in any language and framework) on Kubernetes.
|
12
|
+
|
13
|
+
Unlike other tools that add more options and more complexity to Kubernetes, Cuber is made to simplify and reduce the complexity.
|
14
|
+
You just need to create a `Cuberfile`, with ~10 lines of code, and then type `cuber deploy` to package and deploy your app on any Kubernetes cluster.
|
15
|
+
|
16
|
+
Kubernetes is up to 80% cheaper compared to PaaS like Heroku and you can choose between different cloud providers (no lock-in).
|
17
|
+
It is also reliable and it can scale enterprise applications at any size.
|
18
|
+
The only downside is that it's difficult to master...
|
19
|
+
Cuber makes Kubernetes simple!
|
20
|
+
In this way you have the simplicity of a PaaS, at the cost of bare infrastructure and without the additional cost of a DevOps team.
|
21
|
+
|
22
|
+
[Read more](https://cuber.cloud/docs/overview)
|
23
|
+
|
24
|
+
## Installation
|
25
|
+
|
26
|
+
First you need to [install the prerequisites](https://cuber.cloud/docs/installation): `ruby`, `git`, `docker`, `pack`, `kubectl`.
|
27
|
+
|
28
|
+
Then install Cuber:
|
29
|
+
|
30
|
+
```
|
31
|
+
$ gem install cuber
|
32
|
+
```
|
33
|
+
|
34
|
+
## Quickstart
|
35
|
+
|
36
|
+
Open your application folder and create a `Cuberfile`, for example:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
# Give a name to your app
|
40
|
+
app 'myapp'
|
41
|
+
|
42
|
+
# Get the code from this Git repository
|
43
|
+
repo '.'
|
44
|
+
|
45
|
+
# Build the Docker image automatically (or provide a Dockerfile)
|
46
|
+
buildpacks 'heroku/buildpacks:20'
|
47
|
+
|
48
|
+
# Publish the Docker image in a registry
|
49
|
+
image 'username/myapp'
|
50
|
+
|
51
|
+
# Connect to this Kubernetes cluster
|
52
|
+
kubeconfig 'path/to/kubeconfig.yml'
|
53
|
+
|
54
|
+
# Run and scale any command on Kubernetes
|
55
|
+
proc :web, 'your web server command'
|
56
|
+
```
|
57
|
+
|
58
|
+
You can also see [a more complete example](https://cuber.cloud/docs/quickstart).
|
59
|
+
|
60
|
+
Then in your terminal:
|
61
|
+
|
62
|
+
```
|
63
|
+
$ cuber deploy
|
64
|
+
```
|
65
|
+
|
66
|
+
Finally you can also monitor the status of your application:
|
67
|
+
|
68
|
+
```
|
69
|
+
$ cuber info
|
70
|
+
```
|
71
|
+
|
72
|
+
Check out the [Cuberfile configuration](https://cuber.cloud/docs/cuberfile) and the [Cuber CLI commands](https://cuber.cloud/docs/cli) for more information.
|
73
|
+
|
74
|
+
## License
|
75
|
+
|
76
|
+
Cuber is released under a source-available license:
|
77
|
+
[Standard Source Available License (SSAL)](https://github.com/collimarco/Standard-Source-Available-License)
|
78
|
+
|
79
|
+
Cuber is completely free up to 5 procs per app (and you can publish unlimited apps). If you are a large customer, and you need more procs to scale your applications, please [purchase a license](https://cuber.cloud/buy) (it also includes dedicated support).
|
80
|
+
|
81
|
+
Contributions are welcome: you can fork this project on GitHub and submit a pull request. If you submit a change / fix, we can use it without restrictions and you transfer the copyright of your contribution to Cuber.
|
82
|
+
|
83
|
+
## Learn more
|
84
|
+
|
85
|
+
You can find more information and documentation on [cuber.cloud →](https://cuber.cloud)
|
86
|
+
|
data/SSAL-LICENSE
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Copyright 2022 Cuber (AbstractBrain srls)
|
2
|
+
|
3
|
+
Use and modifications are permitted under the following conditions:
|
4
|
+
|
5
|
+
1. The above copyright notice and this permission must be retained in all copies.
|
6
|
+
2. The modifications to the software must not circumvent license key validations or other license-related code.
|
7
|
+
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
|
9
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY.
|
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-SSAL-LICENSE'
|
10
|
+
s.executables = ['cuber']
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
s.add_dependency 'jwt', '~> 2.3.0'
|
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,101 @@
|
|
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
|
+
cmd = ['git', 'clone']
|
40
|
+
cmd += ['--branch', @options[:repo][:branch]] if @options[:repo][:branch]
|
41
|
+
cmd += ['--depth', '1', @options[:repo][:url], path]
|
42
|
+
system(*cmd) || abort('Cuber: git clone failed')
|
43
|
+
end
|
44
|
+
|
45
|
+
def commit_hash
|
46
|
+
out, status = Open3.capture2 'git', 'rev-parse', '--short', 'HEAD', chdir: '.cuber/repo'
|
47
|
+
abort 'Cuber: cannot get commit hash' unless status.success?
|
48
|
+
out.strip
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_release_name
|
52
|
+
@options[:release] = "#{commit_hash}-#{Time.now.utc.iso8601.delete('^0-9')}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def pack
|
56
|
+
print_step 'Building image using buildpacks'
|
57
|
+
tag = "#{@options[:image]}:#{@options[:release]}"
|
58
|
+
cmd = ['pack', 'build', tag, '--builder', @options[:buildpacks], '--publish']
|
59
|
+
cmd += ['--pull-policy', 'always', '--clear-cache'] if @options[:cache] == false
|
60
|
+
system(*cmd, chdir: '.cuber/repo') || abort('Cuber: pack build failed')
|
61
|
+
end
|
62
|
+
|
63
|
+
def build
|
64
|
+
print_step 'Building image from Dockerfile'
|
65
|
+
dockerfile = @options[:dockerfile] || 'Dockerfile'
|
66
|
+
tag = "#{@options[:image]}:#{@options[:release]}"
|
67
|
+
cmd = ['docker', 'build']
|
68
|
+
cmd += ['--pull', '--no-cache'] if @options[:cache] == false
|
69
|
+
cmd += ['--platform', 'linux/amd64', '--progress', 'plain', '-f', dockerfile, '-t', tag, '.']
|
70
|
+
system(*cmd, chdir: '.cuber/repo') || abort('Cuber: docker build failed')
|
71
|
+
end
|
72
|
+
|
73
|
+
def push
|
74
|
+
print_step 'Pushing image to Docker registry'
|
75
|
+
tag = "#{@options[:image]}:#{@options[:release]}"
|
76
|
+
system('docker', 'push', tag) || abort('Cuber: docker push failed')
|
77
|
+
end
|
78
|
+
|
79
|
+
def configure
|
80
|
+
print_step 'Generating Kubernetes configuration'
|
81
|
+
@options[:instance] = "#{@options[:app]}-#{Time.now.utc.iso8601.delete('^0-9')}"
|
82
|
+
@options[:dockerconfigjson] = Base64.strict_encode64 File.read File.expand_path(@options[:dockerconfig] || '~/.docker/config.json')
|
83
|
+
render 'deployment.yml', '.cuber/kubernetes/deployment.yml'
|
84
|
+
end
|
85
|
+
|
86
|
+
def apply
|
87
|
+
print_step 'Applying configuration to Kubernetes cluster'
|
88
|
+
kubectl 'apply',
|
89
|
+
'-f', '.cuber/kubernetes/deployment.yml',
|
90
|
+
'--prune', '-l', "app.kubernetes.io/name=#{@options[:app]},app.kubernetes.io/managed-by=cuber"
|
91
|
+
end
|
92
|
+
|
93
|
+
def rollout
|
94
|
+
print_step 'Verifying deployment status'
|
95
|
+
@options[:procs].each_key do |procname|
|
96
|
+
kubectl 'rollout', 'status', "deployment/#{procname}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
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' unless @options[:buildpacks].to_s.strip.empty?
|
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 url, branch: nil
|
36
|
+
@repo = { url: url, branch: branch }
|
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].nil? || @options[:repo][:url].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.2.1
|
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-06-08 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: 2.3.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.3.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
|
+
- README.md
|
35
|
+
- SSAL-LICENSE
|
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-SSAL-LICENSE
|
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.33
|
38
72
|
signing_key:
|
39
73
|
specification_version: 4
|
40
|
-
summary:
|
74
|
+
summary: Deploy your apps on Kubernetes easily.
|
41
75
|
test_files: []
|