k8sflow 0.10.0
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 +7 -0
- data/Changelog +3 -0
- data/License +20 -0
- data/README.md +2 -0
- data/bin/k8sflow +4 -0
- data/lib/k8sflow/cli.rb +10 -0
- data/lib/k8sflow/command/image.rb +165 -0
- data/lib/k8sflow/command/maintenance.rb +38 -0
- data/lib/k8sflow/command/run.rb +114 -0
- data/lib/k8sflow/command.rb +3 -0
- data/lib/k8sflow/utils/heroku.rb +57 -0
- data/lib/k8sflow/version.rb +3 -0
- data/lib/k8sflow.rb +13 -0
- data/spec/kubeflow_spec.rb +6 -0
- data/spec/topic_spec.rb +7 -0
- metadata +76 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 212a6cc23a37f94da1a18f90469d06b974501ac1
|
4
|
+
data.tar.gz: 11dd94712047a9649259c1cd00e504fc07984d75
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0f79e9da6c2ddea9e9430fa2d28f1ebf5a0b969001529c1a4b326a55c48a96152e9da70a21e87fad1fa8e806fe5ab82bfaa5091311bb9d90e60eedac6b8ade7f
|
7
|
+
data.tar.gz: a238b77ff59ac2777b5ca992c1d48b546cf30d8bb2a1d189e6da74829a9347d07d02230274615a115de61339512c354e7099896c6ff28ba31dc0d52a00e8f9d8
|
data/Changelog
ADDED
data/License
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Antoine Legrand (ant.legrand@gmail.com)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/bin/k8sflow
ADDED
data/lib/k8sflow/cli.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'erb'
|
2
|
+
module K8sflow
|
3
|
+
|
4
|
+
module Image
|
5
|
+
class ImageBase < Clitopic::Command::Base
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def kv_parse(list)
|
9
|
+
vars = {}
|
10
|
+
list.each do |e|
|
11
|
+
sp = e.split("=")
|
12
|
+
key = sp[0..-2].join("=")
|
13
|
+
value = sp[-1]
|
14
|
+
vars[key] = value
|
15
|
+
end
|
16
|
+
return vars
|
17
|
+
end
|
18
|
+
|
19
|
+
def vars
|
20
|
+
if @vars.nil?
|
21
|
+
@vars = {}
|
22
|
+
if !options[:build_vars].nil?
|
23
|
+
if options[:build_vars].is_a?(Hash)
|
24
|
+
@vars = options[:build_vars]
|
25
|
+
else
|
26
|
+
@vars = kv_parse(options[:build_vars])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
if !options[:vars].nil?
|
30
|
+
if options[:vars].is_a?(Hash)
|
31
|
+
@vars.merge!(options[:vars])
|
32
|
+
else
|
33
|
+
@vars.merge!(kv_parse(options[:vars]))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
@vars["tag"] = @options[:tag]
|
39
|
+
return @vars
|
40
|
+
end
|
41
|
+
|
42
|
+
def files
|
43
|
+
f = []
|
44
|
+
options[:files].each do |file|
|
45
|
+
if !file.start_with?("/")
|
46
|
+
file = "#{@options[:path]}/#{file}"
|
47
|
+
end
|
48
|
+
file_list = Dir.glob(file)
|
49
|
+
f << file_list
|
50
|
+
end
|
51
|
+
return f
|
52
|
+
end
|
53
|
+
|
54
|
+
def tag
|
55
|
+
@options[:tag]
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def create_dockerfile
|
60
|
+
tag = @options[:tag]
|
61
|
+
dir = "#{@options[:path]}/#{@options[:tag]}"
|
62
|
+
puts "- Remove previous dir: #{dir}"
|
63
|
+
FileUtils.rm_rf dir
|
64
|
+
puts "- Create dir: #{dir}"
|
65
|
+
FileUtils.mkdir_p dir
|
66
|
+
files.each do |file_list|
|
67
|
+
puts "- Copy files: #{file_list}"
|
68
|
+
FileUtils.cp(file_list, dir)
|
69
|
+
end
|
70
|
+
puts "- Read Dockerfile template: #{@options[:path]}/#{@options[:tpl]}"
|
71
|
+
tpl = "#{@options[:path]}/#{@options[:tpl]}"
|
72
|
+
File.open(tpl, 'rb') do |file|
|
73
|
+
tpl = file.read
|
74
|
+
end
|
75
|
+
b = binding
|
76
|
+
puts "- Write Dockerfile: #{dir}/Dockerfile"
|
77
|
+
puts " with vars:\n#{vars.map{|k,v| " #{k}: #{v}"}.join("\n")}"
|
78
|
+
File.open("#{dir}/Dockerfile", 'wb') do |file|
|
79
|
+
file.write(ERB.new(tpl).result b)
|
80
|
+
end
|
81
|
+
|
82
|
+
puts "-----------------------------------------------"
|
83
|
+
end
|
84
|
+
|
85
|
+
def dockerbuild
|
86
|
+
dir = "#{@options[:path]}/#{@options[:tag]}"
|
87
|
+
cmd = "docker -H #{@options[:docker_api]} build #{@options[:build_opts]} -t #{@options[:registry]}:#{tag} #{dir} "
|
88
|
+
puts cmd
|
89
|
+
system(cmd)
|
90
|
+
end
|
91
|
+
|
92
|
+
def dockerpush
|
93
|
+
cmd = "docker -H #{@options[:docker_api]} push #{@options[:registry]}:#{tag}"
|
94
|
+
puts cmd
|
95
|
+
system(cmd)
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
class ImageTopic < Clitopic::Topic::Base
|
103
|
+
register name: 'image',
|
104
|
+
description: 'Mange image lifecylces'
|
105
|
+
|
106
|
+
option :registry, '-r', '--registry DOCKER_REPO', 'Docker registry to pull/fetch images'
|
107
|
+
option :path, '-p', '--path DOCKERFILE PATH', 'dockerfiles source directory to'
|
108
|
+
option :build_vars, "--build-vars key=value,key=value" , Array, "Default variables"
|
109
|
+
option :docker_api, "-H", "--host", "docker api endpoint", default: "tcp://localhost:4243"
|
110
|
+
option :tag, '-t', '--tag TAG', "Image tag", default: 'latest'
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
class Dockerfile < ImageBase
|
115
|
+
register name: 'dockerfile',
|
116
|
+
banner: 'image:dockerfile [options]',
|
117
|
+
description: 'Generate a dockerfile with templated vars .',
|
118
|
+
topic: 'image'
|
119
|
+
|
120
|
+
option :files, "-f", "--files FILE1,FILE2", Array, "List files to copy in dockerfile directory, i.e 'files/*',/etc/conf.cfg'"
|
121
|
+
option :tpl, "-l", "--tpl=Dockerfile", "The Dockerfile Template", default: 'Dockerfile.tpl'
|
122
|
+
option :vars, "-x", "--vars=VARS", Array, "Variables required by the dockerfile"
|
123
|
+
option :tag, '-t', '--tag TAG', "Image tag", default: 'master'
|
124
|
+
|
125
|
+
def self.call
|
126
|
+
create_dockerfile
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class Build < ImageBase
|
131
|
+
register name: 'build',
|
132
|
+
description: 'build docker image',
|
133
|
+
banner: 'image:build [OPTIONS]',
|
134
|
+
topic: 'image'
|
135
|
+
|
136
|
+
option :build, "--build OPTS", "docker build options"
|
137
|
+
def self.call
|
138
|
+
dockerbuild
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class Push < ImageBase
|
143
|
+
register name: 'push',
|
144
|
+
description: 'push dockerimage to docker hub',
|
145
|
+
banner: 'image:push [OPTIONS]',
|
146
|
+
topic: 'image'
|
147
|
+
|
148
|
+
option :build, "--build=[OPTS]", "docker build options"
|
149
|
+
def self.call
|
150
|
+
dockerbuild if @options[:build] != nil
|
151
|
+
dockerpush
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class List < Clitopic::Command::Base
|
156
|
+
register name: 'list',
|
157
|
+
description: 'List available tags in a registry',
|
158
|
+
topic: 'image'
|
159
|
+
|
160
|
+
def self.call
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module K8sflow
|
2
|
+
module Maintenance
|
3
|
+
class MaintenanceTopic < Clitopic::Topic::Base
|
4
|
+
register name: 'maintenance',
|
5
|
+
description: 'Switch application in maintenance mode'
|
6
|
+
|
7
|
+
option :app, '-A', '--app app-backend', 'Application backend set in HAPROXY'
|
8
|
+
option :api, '--api API_ENDPOINT', 'Endpoint to configure haproxy'
|
9
|
+
end
|
10
|
+
|
11
|
+
class Status < Clitopic::Command::Base
|
12
|
+
register name: 'status',
|
13
|
+
description: 'View maintenance status',
|
14
|
+
topic: 'maintenance'
|
15
|
+
|
16
|
+
def self.call
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class On < Clitopic::Command::Base
|
21
|
+
register name: 'on',
|
22
|
+
description: 'Enable maintenance mode',
|
23
|
+
topic: 'maintenance'
|
24
|
+
|
25
|
+
def self.call
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Off < Clitopic::Command::Base
|
30
|
+
register name: 'off',
|
31
|
+
description: 'disable maintenance mode',
|
32
|
+
topic: 'maintenance'
|
33
|
+
|
34
|
+
def self.call
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'optparse/time'
|
3
|
+
require 'docker'
|
4
|
+
require 'k8sflow/utils/heroku'
|
5
|
+
module K8sflow
|
6
|
+
module Command
|
7
|
+
|
8
|
+
class Run < Clitopic::Command::Base
|
9
|
+
register name: 'index',
|
10
|
+
banner: 'Usage: k8sflow run [options] CMD',
|
11
|
+
description: "Run CMD on a (de)attached container
|
12
|
+
|
13
|
+
Exemples:
|
14
|
+
Run a rails console
|
15
|
+
$ k8sflow run -t 2.27.3 -r mydocker/repo -e APP_ENV=production -h tcp://docker-host:4243 rails console
|
16
|
+
|
17
|
+
Run a web server detached and bind container port to host port
|
18
|
+
$ k8sflow run -t 2.10 -p 3000:3000 'run server -p 3000'
|
19
|
+
",
|
20
|
+
topic: {name: 'run', description: 'Run CMD on a (de)attached container'}
|
21
|
+
|
22
|
+
option :port, "-p", "--port ctn_port:host_port", Array, "Publish a container's port(s) to the host"
|
23
|
+
option :port_all, "-P", "--port-all", Array, "Publish all exposed ports to random ports"
|
24
|
+
option :detach, "-d", "--detach", "daemonize container", default: false
|
25
|
+
option :tag, "-t", "--tag TAG", "image-tag"
|
26
|
+
option :registry, "-r", "--registry REPO", "image-repository"
|
27
|
+
option :app, "-A", "--app APPNAME", "application name"
|
28
|
+
option :envs, "-e", "--env VAR=VALUE,VAR2=VALUE2", Array, "envvars list"
|
29
|
+
option :heroku, "--heroku APP", "get all envs from heroku"
|
30
|
+
option :heroku_db, "--heroku-db", "get DB envs only from heroku"
|
31
|
+
option :tty, "--[no-]tty", "Use tty"
|
32
|
+
option :docker_api, "-h", "--host", "docker api endpoint", default: "tcp://localhost:4243"
|
33
|
+
option :aliases, "--aliases a=x,b=y", "commands aliases, usefull in the default file"
|
34
|
+
|
35
|
+
class << self
|
36
|
+
attr_accessor :aliases
|
37
|
+
|
38
|
+
def aliases
|
39
|
+
@aliases ||= {}
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_cmd(args)
|
43
|
+
if args.size == 0
|
44
|
+
raise ArgumentError.new('no CMD')
|
45
|
+
else
|
46
|
+
cmd = args.join(" ").strip
|
47
|
+
puts cmd
|
48
|
+
if !@options[:aliases].nil? && @options[:aliases].is_a?(Hash)
|
49
|
+
@aliases.merge!(@options[:aliases])
|
50
|
+
end
|
51
|
+
cmd = aliases[cmd] if aliases.has_key?(cmd)
|
52
|
+
return cmd
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def env_vars(options)
|
57
|
+
envs = {}
|
58
|
+
if options.has_key?(:envs)
|
59
|
+
options[:envs].each do |e|
|
60
|
+
sp = e.split("=")
|
61
|
+
key = sp[0..-2].join("=")
|
62
|
+
value = sp[-1]
|
63
|
+
envs[key] = value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
if options.has_key?(:heroku)
|
67
|
+
envs.merge!( K8sflow::Utils::HerokuClient.envs(options[:heroku], options[:heroku_db]))
|
68
|
+
end
|
69
|
+
pp envs
|
70
|
+
return envs
|
71
|
+
end
|
72
|
+
|
73
|
+
def docker_run(cmd, envs, options)
|
74
|
+
Docker.url = options[:docker_api]
|
75
|
+
container_info = {
|
76
|
+
'Cmd' => cmd.split(" "),
|
77
|
+
'Image' => "#{options[:registry]}:#{options[:tag]}",
|
78
|
+
'Env' => envs.map{|k| "#{k[0]}=#{k[1]}"},
|
79
|
+
'OpenStdin' => true
|
80
|
+
}
|
81
|
+
if !options[:detach]
|
82
|
+
container_info["Tty"] = true
|
83
|
+
end
|
84
|
+
if options[:port]
|
85
|
+
ctn_port, host_port = options[:port].split(":")
|
86
|
+
container_info["HostConfig"] = {
|
87
|
+
"PortBindings": { "#{ctn_port}/tcp": [{ "HostPort": host_port.to_s}] }
|
88
|
+
}
|
89
|
+
end
|
90
|
+
if options[:port_all] == true
|
91
|
+
container_info["PublishAllPorts"] = true
|
92
|
+
end
|
93
|
+
pp container_info
|
94
|
+
container = Docker::Container.create(container_info)
|
95
|
+
container.start
|
96
|
+
puts "container created with id: #{container.id}"
|
97
|
+
if !options[:detach]
|
98
|
+
puts "docker -H #{Docker.url} attach #{container.id}"
|
99
|
+
exec("docker -H #{Docker.url} attach #{container.id}")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def call
|
104
|
+
pp options
|
105
|
+
cmd = get_cmd(arguments)
|
106
|
+
envs = env_vars(options)
|
107
|
+
docker_run(cmd, envs, options)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'heroku-api'
|
3
|
+
require 'netrc'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module K8sflow
|
7
|
+
module Utils
|
8
|
+
class HerokuClient
|
9
|
+
HEROKU_API_HOST = "api.heroku.com"
|
10
|
+
attr_accessor :heroku, :api_token
|
11
|
+
class << self
|
12
|
+
def client
|
13
|
+
if @client.nil?
|
14
|
+
user, token = netrc[HEROKU_API_HOST]
|
15
|
+
@client = Heroku::API.new(:api_key => token)
|
16
|
+
end
|
17
|
+
return @client
|
18
|
+
end
|
19
|
+
|
20
|
+
def netrc # :nodoc:
|
21
|
+
@netrc ||= begin
|
22
|
+
File.exists?(netrc_path) ? Netrc.read(netrc_path) : raise(StandardError)
|
23
|
+
rescue => error
|
24
|
+
puts netrc_path
|
25
|
+
raise ".netrc missing or no entry found. Try `heroku auth:login`"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def netrc_path # :nodoc:
|
30
|
+
default = Netrc.default_path
|
31
|
+
encrypted = default + ".gpg"
|
32
|
+
if File.exists?(encrypted)
|
33
|
+
encrypted
|
34
|
+
else
|
35
|
+
default
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def envs(app, db_only=true)
|
40
|
+
envs = client.get_config_vars(app).body
|
41
|
+
# pp "overrided vars: #{@options.envvars}"
|
42
|
+
db_vars = ["DATABASE_URL",
|
43
|
+
"MEMCACHIER_PASSWORD",
|
44
|
+
"MEMCACHIER_SERVERS",
|
45
|
+
"MEMCACHIER_USERNAME",
|
46
|
+
"REDISTOGO_URL",
|
47
|
+
"REDIS_PROVIDER"]
|
48
|
+
if db_only == true
|
49
|
+
envs.select!{|k,v| db_vars.index(k) != nil}
|
50
|
+
end
|
51
|
+
pp envs
|
52
|
+
return envs
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/k8sflow.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'clitopic'
|
2
|
+
require 'k8sflow/version'
|
3
|
+
|
4
|
+
Clitopic.version = K8sflow::VERSION
|
5
|
+
#Clitopic.parser = Clitopic::Parser::OptParser
|
6
|
+
Clitopic.commands_dir = "#{__FILE__}/k8sflow/command"
|
7
|
+
Clitopic.default_files = [File.join(Dir.getwd, ".k8sflow.yml"), File.join(Dir.home, ".k8sflow.yml")]
|
8
|
+
|
9
|
+
|
10
|
+
require 'k8sflow/cli'
|
11
|
+
|
12
|
+
|
13
|
+
K8sflow::Command::Run.aliases = {'c' => 'rails console', 's' => 'rails server', 'console' => "rails console", "server" => "rails server"}
|
data/spec/topic_spec.rb
ADDED
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: k8sflow
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.10.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Antoine Legrand
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: cli-topic
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.9'
|
27
|
+
description: Manage workflow from source to kubernetes deployement
|
28
|
+
email:
|
29
|
+
- ant.legrand@gmail.com
|
30
|
+
executables:
|
31
|
+
- k8sflow
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- Changelog
|
36
|
+
- License
|
37
|
+
- README.md
|
38
|
+
- bin/k8sflow
|
39
|
+
- lib/k8sflow.rb
|
40
|
+
- lib/k8sflow/cli.rb
|
41
|
+
- lib/k8sflow/command.rb
|
42
|
+
- lib/k8sflow/command/image.rb
|
43
|
+
- lib/k8sflow/command/maintenance.rb
|
44
|
+
- lib/k8sflow/command/run.rb
|
45
|
+
- lib/k8sflow/utils/heroku.rb
|
46
|
+
- lib/k8sflow/version.rb
|
47
|
+
- spec/kubeflow_spec.rb
|
48
|
+
- spec/topic_spec.rb
|
49
|
+
homepage: http://gitlab.com/ant31
|
50
|
+
licenses:
|
51
|
+
- MIT
|
52
|
+
metadata: {}
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.0.0
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
requirements: []
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 2.4.5
|
70
|
+
signing_key:
|
71
|
+
specification_version: 4
|
72
|
+
summary: Manage workflow from source to docker to kubernetes deployement
|
73
|
+
test_files:
|
74
|
+
- spec/kubeflow_spec.rb
|
75
|
+
- spec/topic_spec.rb
|
76
|
+
has_rdoc:
|