kube_cluster 0.3.2 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -5
- data/bin/increment-version +2 -0
- data/examples/01-basic-redis-pod/Gemfile +5 -0
- data/examples/01-basic-redis-pod/README.md +30 -0
- data/examples/01-basic-redis-pod/bin/dev +8 -0
- data/examples/01-basic-redis-pod/docker-compose.yml +14 -0
- data/examples/01-basic-redis-pod/manifest.rb +1 -1
- data/examples/01-basic-redis-pod/redis.conf +2 -0
- data/examples/02-manifest-with-middleware/Dockerfile +19 -0
- data/examples/02-manifest-with-middleware/Gemfile +5 -0
- data/examples/02-manifest-with-middleware/README.md +34 -0
- data/examples/02-manifest-with-middleware/bin/dev +9 -0
- data/examples/02-manifest-with-middleware/config.ru +3 -0
- data/examples/02-manifest-with-middleware/docker-compose.yml +25 -0
- data/examples/02-manifest-with-middleware/falcon.rb +11 -0
- data/examples/02-manifest-with-middleware/manifest.rb +1 -1
- data/examples/02-manifest-with-middleware/registries.yaml +4 -0
- data/examples/03-app-with-database/Gemfile +5 -0
- data/examples/04-pod-with-ingress/Dockerfile +20 -0
- data/examples/04-pod-with-ingress/Gemfile +10 -0
- data/examples/04-pod-with-ingress/README.md +42 -0
- data/examples/04-pod-with-ingress/bin/dev +10 -0
- data/examples/04-pod-with-ingress/config.ru +3 -0
- data/examples/04-pod-with-ingress/docker-compose.yml +35 -0
- data/examples/04-pod-with-ingress/falcon.rb +11 -0
- data/examples/04-pod-with-ingress/manifest.rb +15 -0
- data/examples/04-pod-with-ingress/pod_with_ingress.rb +60 -0
- data/examples/04-pod-with-ingress/registries.yaml +4 -0
- data/examples/05-helm-chart-to-manifest/Gemfile +5 -0
- data/examples/05-helm-chart-to-manifest/README.md +44 -0
- data/examples/05-helm-chart-to-manifest/bin/dev +6 -0
- data/examples/05-helm-chart-to-manifest/docker-compose.yml +16 -0
- data/examples/05-helm-chart-to-manifest/manifest.rb +18 -0
- data/examples/06-nginx-with-cert-manager/Gemfile +5 -0
- data/examples/06-nginx-with-cert-manager/README.md +57 -0
- data/examples/06-nginx-with-cert-manager/bin/dev +6 -0
- data/examples/06-nginx-with-cert-manager/docker-compose.yml +16 -0
- data/examples/06-nginx-with-cert-manager/manifest.rb +46 -0
- data/examples/06-nginx-with-cert-manager/resources/namespace.rb +7 -0
- data/examples/06-nginx-with-cert-manager/resources/nginx.rb +156 -0
- data/examples/06-nginx-with-cert-manager/resources/self_signed_issuer.rb +11 -0
- data/examples/README.md +3 -0
- data/kube_cluster.gemspec +0 -1
- data/lib/kube/cluster/connection.rb +2 -1
- data/lib/kube/cluster/resource/extensions/README.md +15 -0
- data/lib/kube/cluster/resource/extensions/custom_resource_definition.rb +35 -0
- data/lib/kube/cluster/resource/persistence.rb +16 -3
- data/lib/kube/cluster/resource.rb +9 -0
- data/lib/kube/cluster/version.rb +1 -1
- data/lib/kube/cluster.rb +7 -4
- data/lib/kube/helm/chart.rb +203 -0
- data/lib/kube/helm/endpoint.rb +75 -0
- data/lib/kube/helm/repo.rb +121 -0
- metadata +44 -15
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
services:
|
|
2
|
+
cluster:
|
|
3
|
+
image: "rancher/k3s:latest"
|
|
4
|
+
command: server
|
|
5
|
+
privileged: true
|
|
6
|
+
restart: always
|
|
7
|
+
environment:
|
|
8
|
+
- K3S_TOKEN=change-me-or-face-the-consequences
|
|
9
|
+
- K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml
|
|
10
|
+
- K3S_KUBECONFIG_MODE=666
|
|
11
|
+
volumes:
|
|
12
|
+
- .:/output
|
|
13
|
+
ports:
|
|
14
|
+
- 6443:6443
|
|
15
|
+
- 80:80
|
|
16
|
+
- 443:443
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require "bundler/setup"
|
|
2
|
+
require "kube/cluster"
|
|
3
|
+
|
|
4
|
+
CertManager = Kube::Helm::Repo
|
|
5
|
+
.new("jetstack", url: "https://charts.jetstack.io")
|
|
6
|
+
.fetch("cert-manager", version: "1.17.2")
|
|
7
|
+
|
|
8
|
+
CertManager.crds.each do |crd|
|
|
9
|
+
crd.to_json_schema.then do |s|
|
|
10
|
+
Kube::Schema.register(
|
|
11
|
+
s[:kind],
|
|
12
|
+
schema: s[:schema],
|
|
13
|
+
api_version: s[:api_version]
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
require_relative "resources/namespace"
|
|
19
|
+
require_relative "resources/nginx"
|
|
20
|
+
require_relative "resources/self_signed_issuer"
|
|
21
|
+
|
|
22
|
+
manifest =
|
|
23
|
+
Kube::Cluster::Manifest.new(
|
|
24
|
+
|
|
25
|
+
CertManager.apply_values(
|
|
26
|
+
{
|
|
27
|
+
"installCRDs" => true,
|
|
28
|
+
"replicaCount" => 2,
|
|
29
|
+
"resources" => {
|
|
30
|
+
"requests" => { "cpu" => "50m", "memory" => "64Mi" },
|
|
31
|
+
"limits" => { "cpu" => "200m", "memory" => "128Mi" },
|
|
32
|
+
},
|
|
33
|
+
"webhook" => {
|
|
34
|
+
"replicaCount" => 2,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
release: "cert-manager",
|
|
38
|
+
namespace: "cert-manager",
|
|
39
|
+
),
|
|
40
|
+
|
|
41
|
+
Namespace.new(name: "cert-manager"),
|
|
42
|
+
Nginx.new(name: "nginx-app", host: "app.example.com"),
|
|
43
|
+
SelfSignedIssuer.new,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
puts manifest.to_yaml
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Nginx < Kube::Cluster::Manifest
|
|
4
|
+
def initialize(name:, host:, &block)
|
|
5
|
+
ns = name
|
|
6
|
+
match_labels = { app: name }
|
|
7
|
+
|
|
8
|
+
super(
|
|
9
|
+
Kube::Cluster["Namespace"].new {
|
|
10
|
+
metadata.name = ns
|
|
11
|
+
metadata.labels = match_labels
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
Kube::Cluster["ConfigMap"].new {
|
|
15
|
+
metadata.name = "#{name}-config"
|
|
16
|
+
metadata.namespace = ns
|
|
17
|
+
metadata.labels = match_labels
|
|
18
|
+
|
|
19
|
+
self.data = {
|
|
20
|
+
"default.conf" => <<~NGINX,
|
|
21
|
+
server {
|
|
22
|
+
listen 80;
|
|
23
|
+
server_name _;
|
|
24
|
+
|
|
25
|
+
location / {
|
|
26
|
+
root /usr/share/nginx/html;
|
|
27
|
+
index index.html;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
location /healthz {
|
|
31
|
+
access_log off;
|
|
32
|
+
return 200 "ok\\n";
|
|
33
|
+
add_header Content-Type text/plain;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
NGINX
|
|
37
|
+
|
|
38
|
+
"index.html" => <<~HTML,
|
|
39
|
+
<!DOCTYPE html>
|
|
40
|
+
<html>
|
|
41
|
+
<head><title>Hello from Nginx</title></head>
|
|
42
|
+
<body>
|
|
43
|
+
<h1>Hello from Nginx with TLS!</h1>
|
|
44
|
+
<p>Certificates managed by cert-manager.</p>
|
|
45
|
+
</body>
|
|
46
|
+
</html>
|
|
47
|
+
HTML
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
Kube::Cluster["Deployment"].new {
|
|
52
|
+
metadata.name = name
|
|
53
|
+
metadata.namespace = ns
|
|
54
|
+
metadata.labels = match_labels
|
|
55
|
+
|
|
56
|
+
spec.replicas = 2
|
|
57
|
+
spec.selector.matchLabels = match_labels
|
|
58
|
+
|
|
59
|
+
spec.template.metadata.labels = match_labels
|
|
60
|
+
spec.template.spec.containers = [
|
|
61
|
+
{
|
|
62
|
+
name: name,
|
|
63
|
+
image: "nginx:1.27-alpine",
|
|
64
|
+
ports: [
|
|
65
|
+
{ name: "http", containerPort: 80, protocol: "TCP" },
|
|
66
|
+
],
|
|
67
|
+
resources: {
|
|
68
|
+
requests: { cpu: "50m", memory: "64Mi" },
|
|
69
|
+
limits: { cpu: "200m", memory: "128Mi" },
|
|
70
|
+
},
|
|
71
|
+
volumeMounts: [
|
|
72
|
+
{ name: "nginx-config", mountPath: "/etc/nginx/conf.d", readOnly: true },
|
|
73
|
+
{ name: "nginx-html", mountPath: "/usr/share/nginx/html", readOnly: true },
|
|
74
|
+
],
|
|
75
|
+
livenessProbe: {
|
|
76
|
+
httpGet: { path: "/healthz", port: "http" },
|
|
77
|
+
initialDelaySeconds: 5,
|
|
78
|
+
periodSeconds: 10,
|
|
79
|
+
},
|
|
80
|
+
readinessProbe: {
|
|
81
|
+
httpGet: { path: "/healthz", port: "http" },
|
|
82
|
+
initialDelaySeconds: 2,
|
|
83
|
+
periodSeconds: 5,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
spec.template.spec.volumes = [
|
|
89
|
+
{
|
|
90
|
+
name: "nginx-config",
|
|
91
|
+
configMap: {
|
|
92
|
+
name: "#{name}-config",
|
|
93
|
+
items: [{ key: "default.conf", path: "default.conf" }],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "nginx-html",
|
|
98
|
+
configMap: {
|
|
99
|
+
name: "#{name}-config",
|
|
100
|
+
items: [{ key: "index.html", path: "index.html" }],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
Kube::Cluster["Service"].new {
|
|
107
|
+
metadata.name = name
|
|
108
|
+
metadata.namespace = ns
|
|
109
|
+
metadata.labels = match_labels
|
|
110
|
+
|
|
111
|
+
spec.selector = match_labels
|
|
112
|
+
spec.ports = [
|
|
113
|
+
{ name: "http", port: 80, targetPort: "http", protocol: "TCP" },
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
Kube::Cluster["Ingress"].new {
|
|
118
|
+
metadata.name = name
|
|
119
|
+
metadata.namespace = ns
|
|
120
|
+
metadata.labels = match_labels
|
|
121
|
+
metadata.annotations = {
|
|
122
|
+
"cert-manager.io/cluster-issuer": "selfsigned",
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
spec.ingressClassName = "traefik"
|
|
126
|
+
spec.tls = [
|
|
127
|
+
{
|
|
128
|
+
hosts: [host],
|
|
129
|
+
secretName: "#{name}-tls",
|
|
130
|
+
},
|
|
131
|
+
]
|
|
132
|
+
spec.rules = [
|
|
133
|
+
{
|
|
134
|
+
host: host,
|
|
135
|
+
http: {
|
|
136
|
+
paths: [
|
|
137
|
+
{
|
|
138
|
+
path: "/",
|
|
139
|
+
pathType: "Prefix",
|
|
140
|
+
backend: {
|
|
141
|
+
service: {
|
|
142
|
+
name: name,
|
|
143
|
+
port: { name: "http" },
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
instance_exec(&block) if block
|
|
155
|
+
end
|
|
156
|
+
end
|
data/examples/README.md
ADDED
data/kube_cluster.gemspec
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
module Kube
|
|
4
4
|
module Cluster
|
|
5
5
|
class Connection
|
|
6
|
-
attr_reader :kubeconfig, :ctl
|
|
6
|
+
attr_reader :kubeconfig, :ctl, :helm
|
|
7
7
|
|
|
8
8
|
def initialize(kubeconfig:)
|
|
9
9
|
@kubeconfig = kubeconfig
|
|
10
10
|
@ctl = Kube::Ctl::Instance.new(kubeconfig: kubeconfig)
|
|
11
|
+
@helm = Kube::Helm::Instance.new(kubeconfig: kubeconfig)
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
def inspect
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Resource Extensions
|
|
2
|
+
|
|
3
|
+
Modules in this directory are dynamically mixed into `Kube::Cluster::Resource` instances at initialization time (via `extend`).
|
|
4
|
+
|
|
5
|
+
## Why modules instead of subclasses?
|
|
6
|
+
|
|
7
|
+
`Kube::Cluster["Deployment"]` returns a versioned schema class generated at runtime. Because these classes are versioned and generated dynamically, we can't subclass them directly -- there's no stable class to inherit from.
|
|
8
|
+
|
|
9
|
+
Instead, we define extension modules here and dynamically insert them when the resource is instantiated. In `Resource#initialize`, after the object is built, the matching extension module is applied:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
extend Extensions.const_get(kind) if Extensions.const_defined?(kind)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This gives us a way to add kind-specific behaviour to resource instances without coupling to any particular schema version.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kube
|
|
4
|
+
module Cluster
|
|
5
|
+
class Resource < Kube::Schema::Resource
|
|
6
|
+
module Extensions
|
|
7
|
+
module CustomResourceDefinition
|
|
8
|
+
def to_json_schema
|
|
9
|
+
h = to_h
|
|
10
|
+
kind = h.dig(:spec, :names, :kind)
|
|
11
|
+
group = h.dig(:spec, :group)
|
|
12
|
+
versions = h.dig(:spec, :versions) || []
|
|
13
|
+
|
|
14
|
+
version = versions.find { |v| v[:storage] } ||
|
|
15
|
+
versions.find { |v| v.dig(:schema, :openAPIV3Schema) } ||
|
|
16
|
+
versions.first
|
|
17
|
+
|
|
18
|
+
raise ArgumentError, "CRD has no versions" unless version
|
|
19
|
+
|
|
20
|
+
version_name = version[:name]
|
|
21
|
+
schema = version.dig(:schema, :openAPIV3Schema)
|
|
22
|
+
|
|
23
|
+
raise ArgumentError, "CRD version #{version_name} has no openAPIV3Schema" unless schema
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
kind: kind,
|
|
27
|
+
schema: deep_stringify_keys(schema),
|
|
28
|
+
api_version: "#{group}/#{version_name}",
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -7,6 +7,14 @@ module Kube
|
|
|
7
7
|
module Cluster
|
|
8
8
|
class Resource < Kube::Schema::Resource
|
|
9
9
|
module Persistence
|
|
10
|
+
def name
|
|
11
|
+
to_h.dig(:metadata, :name)&.to_s
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def persisted?
|
|
15
|
+
!name.nil? && !name.empty?
|
|
16
|
+
end
|
|
17
|
+
|
|
10
18
|
def apply
|
|
11
19
|
JSON.generate(deep_stringify_keys(to_h)).then do |json|
|
|
12
20
|
kubectl("apply", "-f", "-", stdin: json)
|
|
@@ -23,7 +31,7 @@ module Kube
|
|
|
23
31
|
false
|
|
24
32
|
else
|
|
25
33
|
json = JSON.generate(deep_stringify_keys(diff))
|
|
26
|
-
kubectl("patch",
|
|
34
|
+
kubectl("patch", kind.downcase, name, *namespace_flags, "--type", type, "-p", json)
|
|
27
35
|
reload
|
|
28
36
|
true
|
|
29
37
|
end
|
|
@@ -34,7 +42,7 @@ module Kube
|
|
|
34
42
|
|
|
35
43
|
def delete
|
|
36
44
|
if persisted?
|
|
37
|
-
kubectl("delete",
|
|
45
|
+
kubectl("delete", kind.downcase, name, *namespace_flags)
|
|
38
46
|
true
|
|
39
47
|
else
|
|
40
48
|
raise Kube::CommandError, "cannot delete a resource without a name"
|
|
@@ -44,7 +52,7 @@ module Kube
|
|
|
44
52
|
def reload
|
|
45
53
|
if persisted?
|
|
46
54
|
tap do
|
|
47
|
-
kubectl("get",
|
|
55
|
+
kubectl("get", kind.downcase, name, *namespace_flags, "-o", "json").then do |json|
|
|
48
56
|
JSON.parse(json).then do |hash|
|
|
49
57
|
@data = deep_symbolize_keys(hash)
|
|
50
58
|
snapshot!
|
|
@@ -58,6 +66,11 @@ module Kube
|
|
|
58
66
|
|
|
59
67
|
private
|
|
60
68
|
|
|
69
|
+
def namespace_flags
|
|
70
|
+
ns = to_h.dig(:metadata, :namespace)
|
|
71
|
+
ns ? ["--namespace", ns.to_s] : []
|
|
72
|
+
end
|
|
73
|
+
|
|
61
74
|
def kubectl(*args)
|
|
62
75
|
@cluster.connection.ctl.run(args.join(" "))
|
|
63
76
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "resource/dirty_tracking"
|
|
4
4
|
require_relative "resource/persistence"
|
|
5
|
+
require_relative "resource/extensions/custom_resource_definition"
|
|
5
6
|
|
|
6
7
|
module Kube
|
|
7
8
|
module Cluster
|
|
@@ -39,6 +40,14 @@ module Kube
|
|
|
39
40
|
@cluster = hash.delete(:cluster)
|
|
40
41
|
super
|
|
41
42
|
snapshot!
|
|
43
|
+
|
|
44
|
+
begin
|
|
45
|
+
extend Object.const_get(
|
|
46
|
+
"Kube::Cluster::Resource::Extensions::#{kind}"
|
|
47
|
+
)
|
|
48
|
+
rescue
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
42
51
|
end
|
|
43
52
|
|
|
44
53
|
# Build a new resource of the same schema subclass from a hash.
|
data/lib/kube/cluster/version.rb
CHANGED
data/lib/kube/cluster.rb
CHANGED
|
@@ -9,6 +9,7 @@ require_relative "cluster/resource"
|
|
|
9
9
|
require_relative "cluster/manifest"
|
|
10
10
|
require_relative "cluster/middleware"
|
|
11
11
|
require 'kube/ctl'
|
|
12
|
+
require_relative 'helm/repo'
|
|
12
13
|
|
|
13
14
|
module Kube
|
|
14
15
|
def self.cluster
|
|
@@ -31,11 +32,13 @@ module Kube
|
|
|
31
32
|
@resource_classes[kind] ||= begin
|
|
32
33
|
schema_class = Kube::Schema[kind]
|
|
33
34
|
Class.new(Resource) do
|
|
34
|
-
@schema
|
|
35
|
-
@defaults
|
|
35
|
+
@schema = schema_class.schema
|
|
36
|
+
@defaults = schema_class.defaults
|
|
37
|
+
@schema_properties = schema_class.schema_properties
|
|
36
38
|
|
|
37
|
-
def self.schema
|
|
38
|
-
def self.defaults
|
|
39
|
+
def self.schema = @schema || superclass.schema
|
|
40
|
+
def self.defaults = @defaults || superclass.defaults
|
|
41
|
+
def self.schema_properties = @schema_properties || superclass.schema_properties
|
|
39
42
|
end
|
|
40
43
|
end
|
|
41
44
|
end
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tempfile"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module Kube
|
|
7
|
+
module Helm
|
|
8
|
+
# Represents a Helm Chart.yaml as a Ruby object.
|
|
9
|
+
#
|
|
10
|
+
# The Chart holds metadata from Chart.yaml and can render resources
|
|
11
|
+
# via #apply_values. It can be backed by either a local directory
|
|
12
|
+
# (with templates on disk) or a remote chart reference.
|
|
13
|
+
#
|
|
14
|
+
# # Virtual chart (just metadata, no templates)
|
|
15
|
+
# chart = Kube::Helm::Chart.new {
|
|
16
|
+
# name = "my-app"
|
|
17
|
+
# version = "1.0.0"
|
|
18
|
+
# appVersion = "3.4.5"
|
|
19
|
+
# }
|
|
20
|
+
#
|
|
21
|
+
# # Load from a local chart directory
|
|
22
|
+
# chart = Kube::Helm::Chart.open("./charts/my-app")
|
|
23
|
+
# manifest = chart.apply_values({ "replicaCount" => 3 })
|
|
24
|
+
#
|
|
25
|
+
# # From a remote repo
|
|
26
|
+
# manifest = Kube::Helm::Repo
|
|
27
|
+
# .new("bitnami", url: "https://charts.bitnami.com/bitnami")
|
|
28
|
+
# .fetch("nginx", version: "18.1.0")
|
|
29
|
+
# .apply_values({ "replicaCount" => 3 })
|
|
30
|
+
#
|
|
31
|
+
class Chart
|
|
32
|
+
attr_reader :path, :ref, :cluster
|
|
33
|
+
|
|
34
|
+
# Open a chart from a local directory. Reads Chart.yaml and stores
|
|
35
|
+
# the path so that helm commands run against the local chart.
|
|
36
|
+
#
|
|
37
|
+
# @param path [String] path to the chart directory
|
|
38
|
+
# @param cluster [Kube::Cluster::Instance, nil] optional cluster connection
|
|
39
|
+
# @return [Chart]
|
|
40
|
+
def self.open(path, cluster: nil)
|
|
41
|
+
chart_file = File.join(path, "Chart.yaml")
|
|
42
|
+
raise Kube::Error, "No Chart.yaml found at #{path}" unless File.exist?(chart_file)
|
|
43
|
+
|
|
44
|
+
yaml = YAML.safe_load_file(chart_file)
|
|
45
|
+
new(yaml, path: path, cluster: cluster)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @param data [Hash] Chart.yaml content (string or symbol keys)
|
|
49
|
+
# @param path [String, nil] filesystem path to chart directory (local charts)
|
|
50
|
+
# @param ref [String, nil] chart reference for helm commands (remote charts)
|
|
51
|
+
# @param cluster [Kube::Cluster::Instance, nil] optional cluster connection
|
|
52
|
+
def initialize(data = {}, path: nil, ref: nil, cluster: nil, &block)
|
|
53
|
+
@data = deep_symbolize_keys(data)
|
|
54
|
+
@path = path
|
|
55
|
+
@ref = ref
|
|
56
|
+
@cluster = cluster
|
|
57
|
+
@data.instance_exec(&block) if block_given?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def name = @data[:name]
|
|
61
|
+
def version = @data[:version]
|
|
62
|
+
def app_version = @data[:appVersion]
|
|
63
|
+
def description = @data[:description]
|
|
64
|
+
def type = @data[:type]
|
|
65
|
+
def dependencies = @data[:dependencies] || []
|
|
66
|
+
|
|
67
|
+
# Render the chart templates with values applied.
|
|
68
|
+
#
|
|
69
|
+
# Shells out to `helm template` and returns a Manifest of typed
|
|
70
|
+
# Resource objects.
|
|
71
|
+
#
|
|
72
|
+
# @param values [Hash] values to apply to the chart templates
|
|
73
|
+
# @param release [String, nil] release name (defaults to chart name)
|
|
74
|
+
# @param namespace [String, nil] namespace for rendered resources
|
|
75
|
+
# @return [Kube::Schema::Manifest]
|
|
76
|
+
def apply_values(values, release: nil, namespace: nil)
|
|
77
|
+
raise Kube::Error, "No chart source" unless source
|
|
78
|
+
|
|
79
|
+
release_name = release || name
|
|
80
|
+
source_ref = source
|
|
81
|
+
ver = version_flag
|
|
82
|
+
|
|
83
|
+
cmd = helm.call { template.(release_name).(source_ref) }
|
|
84
|
+
cmd = cmd.version(ver) if ver
|
|
85
|
+
cmd = cmd.namespace(namespace) if namespace
|
|
86
|
+
|
|
87
|
+
if values.is_a?(Hash) && values.any?
|
|
88
|
+
tmpfile = write_values_tempfile(values)
|
|
89
|
+
cmd = cmd.f(tmpfile.path)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
Kube::Schema::Manifest.parse(helm.run(cmd.to_s))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Show the chart's default values.
|
|
96
|
+
#
|
|
97
|
+
# @return [Hash] the default values as a Ruby hash
|
|
98
|
+
def show_values
|
|
99
|
+
raise Kube::Error, "No chart source" unless source
|
|
100
|
+
|
|
101
|
+
source_ref = source
|
|
102
|
+
ver = version_flag
|
|
103
|
+
cmd = helm.call { show.values.(source_ref) }
|
|
104
|
+
cmd = cmd.version(ver) if ver
|
|
105
|
+
|
|
106
|
+
YAML.safe_load(helm.run(cmd.to_s), permitted_classes: [Symbol]) || {}
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Return the chart's CRDs as Kube::Cluster::CustomResourceDefinition objects.
|
|
110
|
+
#
|
|
111
|
+
# First tries `helm show crds`. If that returns nothing (some charts
|
|
112
|
+
# ship CRDs in templates rather than the crds/ directory), falls back
|
|
113
|
+
# to rendering via `helm template --set installCRDs=true` and filtering
|
|
114
|
+
# for CustomResourceDefinition resources.
|
|
115
|
+
#
|
|
116
|
+
# chart.crds.each do |crd|
|
|
117
|
+
# s = crd.to_json_schema
|
|
118
|
+
# Kube::Schema.register(s[:kind], schema: s[:schema], api_version: s[:api_version])
|
|
119
|
+
# end
|
|
120
|
+
#
|
|
121
|
+
# @return [Array<Kube::Cluster::CustomResourceDefinition>]
|
|
122
|
+
def crds
|
|
123
|
+
raise Kube::Error, "No chart source" unless source
|
|
124
|
+
|
|
125
|
+
results = crds_from_show
|
|
126
|
+
results = crds_from_template if results.empty?
|
|
127
|
+
results
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def to_s
|
|
131
|
+
version ? "#{name}:#{version}" : name.to_s
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def crds_from_show
|
|
137
|
+
source_ref = source
|
|
138
|
+
ver = version_flag
|
|
139
|
+
cmd = helm.call { show.crds.(source_ref) }
|
|
140
|
+
cmd = cmd.version(ver) if ver
|
|
141
|
+
|
|
142
|
+
yaml_output = helm.run(cmd.to_s)
|
|
143
|
+
parse_crds(yaml_output)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def crds_from_template
|
|
147
|
+
source_ref = source
|
|
148
|
+
ver = version_flag
|
|
149
|
+
release_name = name || "crds"
|
|
150
|
+
|
|
151
|
+
cmd = helm.call { template.(release_name).(source_ref) }
|
|
152
|
+
cmd = cmd.version(ver) if ver
|
|
153
|
+
cmd = cmd.set("installCRDs=true")
|
|
154
|
+
|
|
155
|
+
yaml_output = helm.run(cmd.to_s)
|
|
156
|
+
parse_crds(yaml_output)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def parse_crds(yaml_output)
|
|
160
|
+
return [] if yaml_output.nil? || yaml_output.strip.empty?
|
|
161
|
+
|
|
162
|
+
docs = YAML.safe_load_stream(yaml_output, permitted_classes: [Symbol])
|
|
163
|
+
docs.compact
|
|
164
|
+
.select { |doc| doc.is_a?(Hash) && doc["kind"] == "CustomResourceDefinition" }
|
|
165
|
+
.map { |doc| Kube::Cluster["CustomResourceDefinition"].new(doc) }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# The chart source for helm commands — either a local path or a remote ref.
|
|
169
|
+
def source
|
|
170
|
+
@path || @ref
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Version flag for remote charts. Local charts don't need --version.
|
|
174
|
+
def version_flag
|
|
175
|
+
@ref ? version : nil
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def helm
|
|
179
|
+
@cluster&.connection&.helm || Kube::Helm::Instance.new
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def write_values_tempfile(values)
|
|
183
|
+
tmpfile = Tempfile.new(["helm-values-", ".yaml"])
|
|
184
|
+
tmpfile.write(values.to_yaml)
|
|
185
|
+
tmpfile.flush
|
|
186
|
+
tmpfile
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def deep_symbolize_keys(obj)
|
|
190
|
+
case obj
|
|
191
|
+
when Hash
|
|
192
|
+
obj.each_with_object({}) do |(k, v), result|
|
|
193
|
+
result[k.to_sym] = deep_symbolize_keys(v)
|
|
194
|
+
end
|
|
195
|
+
when Array
|
|
196
|
+
obj.map { |v| deep_symbolize_keys(v) }
|
|
197
|
+
else
|
|
198
|
+
obj
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|