matsuri 0.1.0 → 0.3.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 +5 -5
- data/.envrc +1 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +9 -0
- data/AGENTS.md +18 -0
- data/CHANGELOG.md +642 -0
- data/Gemfile +2 -0
- data/README.md +86 -8
- data/Rakefile +20 -0
- data/bin/console +1 -0
- data/bin/matsuri +6 -0
- data/bin/setup +2 -0
- data/docs/acceptors.md +181 -0
- data/docs/boot.md +101 -0
- data/docs/design/acceptors.md +157 -0
- data/docs/design/boot.md +131 -0
- data/docs/design/ssa.md +142 -0
- data/docs/design/stacks.md +153 -0
- data/docs/diff.md +85 -0
- data/docs/known-issues.md +19 -0
- data/docs/local-dev.md +173 -0
- data/docs/presenters.md +317 -0
- data/docs/resources.md +121 -0
- data/docs/setup.md +97 -0
- data/docs/ssa.md +149 -0
- data/docs/stacks.md +146 -0
- data/flake.lock +61 -0
- data/flake.nix +35 -0
- data/lib/matsuri/acceptor.rb +33 -0
- data/lib/matsuri/acceptors/image_tag.rb +15 -0
- data/lib/matsuri/cmd.rb +46 -0
- data/lib/matsuri/cmds/apply.rb +78 -0
- data/lib/matsuri/cmds/cli.rb +147 -0
- data/lib/matsuri/cmds/create.rb +87 -0
- data/lib/matsuri/cmds/delete.rb +86 -0
- data/lib/matsuri/cmds/diff.rb +105 -0
- data/lib/matsuri/cmds/generate.rb +24 -0
- data/lib/matsuri/cmds/kubectl.rb +16 -0
- data/lib/matsuri/cmds/recreate.rb +65 -0
- data/lib/matsuri/cmds/scale.rb +37 -0
- data/lib/matsuri/cmds/show.rb +123 -0
- data/lib/matsuri/cmds/status.rb +67 -0
- data/lib/matsuri/concerns/awaiting.rb +24 -0
- data/lib/matsuri/concerns/diffable.rb +135 -0
- data/lib/matsuri/concerns/export_manifest.rb +56 -0
- data/lib/matsuri/concerns/import_manifest.rb +64 -0
- data/lib/matsuri/concerns/pod_template.rb +37 -0
- data/lib/matsuri/concerns/pods/spread_pods.rb +47 -0
- data/lib/matsuri/concerns/pods/spread_topology.rb +63 -0
- data/lib/matsuri/concerns/registry_helpers.rb +59 -0
- data/lib/matsuri/config.rb +101 -0
- data/lib/matsuri/core_ext.rb +107 -0
- data/lib/matsuri/dsl/cluster/aggregated_cluster_role.rb +75 -0
- data/lib/matsuri/dsl/cluster/binding.rb +118 -0
- data/lib/matsuri/dsl/cluster/cluster_role.rb +41 -0
- data/lib/matsuri/dsl/cluster/role.rb +49 -0
- data/lib/matsuri/dsl/cluster/scope.rb +245 -0
- data/lib/matsuri/dsl/cluster/service_account.rb +76 -0
- data/lib/matsuri/dsl/concerns/default_binding.rb +34 -0
- data/lib/matsuri/dsl/concerns/manifest_set.rb +21 -0
- data/lib/matsuri/dsl/concerns/metadata.rb +56 -0
- data/lib/matsuri/dsl/concerns/rbac_rules.rb +39 -0
- data/lib/matsuri/initializer.rb +41 -0
- data/lib/matsuri/kubernetes/base.rb +107 -0
- data/lib/matsuri/kubernetes/config_map.rb +100 -0
- data/lib/matsuri/kubernetes/cron_job.rb +115 -0
- data/lib/matsuri/kubernetes/daemon_set.rb +62 -0
- data/lib/matsuri/kubernetes/deployment.rb +65 -0
- data/lib/matsuri/kubernetes/endpoints.rb +57 -0
- data/lib/matsuri/kubernetes/horizontal_pod_autoscaler.rb +116 -0
- data/lib/matsuri/kubernetes/imported_manifest.rb +61 -0
- data/lib/matsuri/kubernetes/ingress.rb +66 -0
- data/lib/matsuri/kubernetes/persistent_volume.rb +56 -0
- data/lib/matsuri/kubernetes/persistent_volume_claim.rb +41 -0
- data/lib/matsuri/kubernetes/pod.rb +355 -0
- data/lib/matsuri/kubernetes/replica_set.rb +46 -0
- data/lib/matsuri/kubernetes/secret.rb +86 -0
- data/lib/matsuri/kubernetes/service.rb +49 -0
- data/lib/matsuri/kubernetes/stateful_set.rb +75 -0
- data/lib/matsuri/kubernetes/storage_class.rb +45 -0
- data/lib/matsuri/platform.rb +38 -0
- data/lib/matsuri/presenter.rb +47 -0
- data/lib/matsuri/presenters/helpers.rb +39 -0
- data/lib/matsuri/presenters/prune_empty.rb +59 -0
- data/lib/matsuri/presenters/seed_image.rb +28 -0
- data/lib/matsuri/presenters/seed_replicas.rb +25 -0
- data/lib/matsuri/presenters/seed_resources.rb +29 -0
- data/lib/matsuri/registry.rb +267 -0
- data/lib/matsuri/shell_out.rb +119 -0
- data/lib/matsuri/stack.rb +276 -0
- data/lib/matsuri/task.rb +20 -0
- data/lib/matsuri/tasks/cluster.rb +79 -0
- data/lib/matsuri/tasks/docker.rb +25 -0
- data/lib/matsuri/tasks/hash_diff.rb +61 -0
- data/lib/matsuri/tasks/kubectl.rb +21 -0
- data/lib/matsuri/tasks/kubectl_ops/apply.rb +24 -0
- data/lib/matsuri/tasks/kubectl_ops/create.rb +27 -0
- data/lib/matsuri/tasks/kubectl_ops/delete.rb +21 -0
- data/lib/matsuri/tasks/kubectl_ops/diff.rb +47 -0
- data/lib/matsuri/tasks/kubectl_ops/get.rb +36 -0
- data/lib/matsuri/tasks/kubectl_ops/recreate.rb +35 -0
- data/lib/matsuri/tasks/kubectl_ops/replace.rb +29 -0
- data/lib/matsuri/tasks/kubectl_ops/rollout_status.rb +22 -0
- data/lib/matsuri/tasks/kubectl_ops/scale.rb +22 -0
- data/lib/matsuri/tasks/kubectl_ops/server_side_apply.rb +32 -0
- data/lib/matsuri/tasks/kubectl_ops/status.rb +20 -0
- data/lib/matsuri/tasks/kubectl_ops.rb +218 -0
- data/lib/matsuri/tasks/pod.rb +56 -0
- data/lib/matsuri/tasks/stacks/seed.rb +93 -0
- data/lib/matsuri/tasks/stacks.rb +31 -0
- data/lib/matsuri/transform/algebra.rb +52 -0
- data/lib/matsuri/transform/callable.rb +54 -0
- data/lib/matsuri/transform/combinators.rb +61 -0
- data/lib/matsuri/transform/env.rb +43 -0
- data/lib/matsuri/transform/kubernetes.rb +89 -0
- data/lib/matsuri/transform.rb +20 -0
- data/lib/matsuri/version.rb +3 -1
- data/lib/matsuri.rb +158 -2
- data/matsuri.gemspec +20 -7
- data/tests/bin/minikube-start.sh +27 -0
- data/tests/bin/probe-save-config.sh +46 -0
- data/tests/bin/probe-ssa.sh +206 -0
- data/tests/example/.gitignore +1 -0
- data/tests/example/Gemfile +7 -0
- data/tests/example/README.md +91 -0
- data/tests/example/bin/matsuri +14 -0
- data/tests/example/config/initializer.rb +12 -0
- data/tests/example/config/matsuri.rb +9 -0
- data/tests/example/config/platform.rb +39 -0
- data/tests/example/lib/acme/nodes/dedicated.rb +26 -0
- data/tests/example/lib/acme/nodes/preemptible.rb +17 -0
- data/tests/example/lib/acme/nodes/required_cores.rb +26 -0
- data/tests/example/lib/acme/nodes/resource_class.rb +26 -0
- data/tests/example/lib/acme/pods/cacerts.rb +15 -0
- data/tests/example/lib/acme/pods/healthcheck.rb +36 -0
- data/tests/example/lib/acme.rb +26 -0
- data/tests/example/platform/config_maps/billing-settings.rb +8 -0
- data/tests/example/platform/config_maps/cacerts.rb +9 -0
- data/tests/example/platform/deployments/billing-web.rb +5 -0
- data/tests/example/platform/deployments/checkout-web.rb +6 -0
- data/tests/example/platform/namespaces/billing-dev.yaml +7 -0
- data/tests/example/platform/namespaces/checkout-dev.yaml +7 -0
- data/tests/example/platform/pods/billing.rb +31 -0
- data/tests/example/platform/pods/checkout.rb +27 -0
- data/tests/example/platform/services/billing-web.rb +6 -0
- data/tests/example/platform/services/checkout-web.rb +4 -0
- data/tests/example/platform/stacks/billing.rb +8 -0
- data/tests/example/platform/stacks/checkout.rb +19 -0
- data/tests/k8s/save-config/deployment-declared.yaml +19 -0
- data/tests/k8s/save-config/deployment-seeded.yaml +20 -0
- data/tests/k8s/save-config/namespace.yaml +4 -0
- data/tests/k8s/ssa/declared-bumped.yaml +22 -0
- data/tests/k8s/ssa/declared.yaml +22 -0
- data/tests/k8s/ssa/namespace.yaml +4 -0
- data/tests/k8s/ssa/seed-via-create.yaml +24 -0
- metadata +288 -20
- data/.travis.yml +0 -4
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'matsuri/core_ext'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
# rubocop:disable Lint/MissingCopEnableDirective
|
|
7
|
+
# rubocop:disable Style/Alias
|
|
8
|
+
module Matsuri
|
|
9
|
+
module Kubernetes
|
|
10
|
+
class Pod < Matsuri::Kubernetes::Base
|
|
11
|
+
include Matsuri::Transform::Env
|
|
12
|
+
|
|
13
|
+
let(:api_version) { 'v1' }
|
|
14
|
+
let(:kind) { 'Pod' }
|
|
15
|
+
|
|
16
|
+
let(:primary_container_path) { ['spec', 'containers', 0] }
|
|
17
|
+
|
|
18
|
+
# Overridables
|
|
19
|
+
let(:spec) do
|
|
20
|
+
{
|
|
21
|
+
containers: containers,
|
|
22
|
+
initContainers: init_containers,
|
|
23
|
+
volumes: volumes,
|
|
24
|
+
|
|
25
|
+
imagePullSecrets: image_pull_secrets,
|
|
26
|
+
|
|
27
|
+
# Networking and Host
|
|
28
|
+
hostname: hostname,
|
|
29
|
+
subdomain: subdomain,
|
|
30
|
+
dnsConfig: dns_config,
|
|
31
|
+
dnsPolicy: dns_policy,
|
|
32
|
+
hostAliases: host_aliases,
|
|
33
|
+
hostIPC: host_ipc,
|
|
34
|
+
hostNetwork: host_network,
|
|
35
|
+
|
|
36
|
+
# Scheduling
|
|
37
|
+
nodeName: node_name,
|
|
38
|
+
nodeSelector: node_selector,
|
|
39
|
+
affinity: affinity,
|
|
40
|
+
tolerations: tolerations,
|
|
41
|
+
priority: priority,
|
|
42
|
+
priorityClassName: priority_class_name,
|
|
43
|
+
schedulerName: scheduler_name,
|
|
44
|
+
topologySpreadConstraints: topology_spread_constraints,
|
|
45
|
+
|
|
46
|
+
# Lifecycle
|
|
47
|
+
readinessGates: readiness_gates,
|
|
48
|
+
restartPolicy: restart_policy,
|
|
49
|
+
activeDeadlineSeconds: active_deadline_seconds,
|
|
50
|
+
terminationGracePeriodSeconds: termination_grace_period_seconds,
|
|
51
|
+
|
|
52
|
+
# Security
|
|
53
|
+
hostPID: host_pid,
|
|
54
|
+
shareProcessNamespace: share_process_namespace,
|
|
55
|
+
securityContext: pod_security_context,
|
|
56
|
+
serviceAccountName: service_account_name,
|
|
57
|
+
automountServiceAccountToken: automount_service_account_token
|
|
58
|
+
}.compact
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Get image tag from the command line
|
|
62
|
+
let(:image_tag) { options[:image_tag] }
|
|
63
|
+
|
|
64
|
+
let(:containers) { [container] }
|
|
65
|
+
let(:init_containers) { nil }
|
|
66
|
+
let(:volumes) { [volume] }
|
|
67
|
+
let(:image_pull_secrets) { [] }
|
|
68
|
+
|
|
69
|
+
# Scheduling
|
|
70
|
+
let(:node_name) { nil }
|
|
71
|
+
let(:node_selector) { { } }
|
|
72
|
+
let(:tolerations) { [toleration].compact }
|
|
73
|
+
let(:toleration) { nil }
|
|
74
|
+
let(:affinity) { { nodeAffinity: node_affinity, podAffinity: pod_affinity, podAntiAffinity: pod_anti_affinity }.compact }
|
|
75
|
+
let(:node_affinity) { nil } # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#nodeaffinity-v1-core
|
|
76
|
+
let(:pod_affinity) { nil } # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#podaffinity-v1-core
|
|
77
|
+
let(:pod_anti_affinity) { nil } # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#podantiaffinity-v1-core
|
|
78
|
+
let(:priority) { nil }
|
|
79
|
+
let(:priority_class_name) { nil }
|
|
80
|
+
let(:scheduler_name) { nil }
|
|
81
|
+
let(:topology_spread_constraints) { nil } # https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
|
|
82
|
+
|
|
83
|
+
# Networking and Host
|
|
84
|
+
let(:hostname) { nil } # https://kubernetes.io/docs/admin/dns/
|
|
85
|
+
let(:subdomain) { nil }
|
|
86
|
+
let(:dns_config) { nil }
|
|
87
|
+
let(:dns_policy) { nil }
|
|
88
|
+
let(:host_aliases) { nil }
|
|
89
|
+
let(:host_ipc) { nil }
|
|
90
|
+
let(:host_network) { nil }
|
|
91
|
+
|
|
92
|
+
# Lifecycle
|
|
93
|
+
let(:readiness_gates) { nil }
|
|
94
|
+
let(:restart_policy) { 'Always' }
|
|
95
|
+
let(:active_deadline_seconds) { nil }
|
|
96
|
+
let(:termination_grace_period_seconds) { nil }
|
|
97
|
+
|
|
98
|
+
# Security and Execution Context
|
|
99
|
+
let(:host_pid) { nil }
|
|
100
|
+
let(:share_process_namespace) { nil }
|
|
101
|
+
let(:service_account_name) { nil }
|
|
102
|
+
let(:automount_service_account_token) { nil }
|
|
103
|
+
|
|
104
|
+
# https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#podsecuritycontext-v1-core
|
|
105
|
+
let(:pod_security_context) do
|
|
106
|
+
{
|
|
107
|
+
runAsUser: pod_run_as_user,
|
|
108
|
+
runAsGroup: pod_run_as_group,
|
|
109
|
+
runAsNonRoot: pod_run_as_non_root,
|
|
110
|
+
seLinuxOptions: pod_se_linux_options,
|
|
111
|
+
fsGroup: fs_group,
|
|
112
|
+
supplementalGroups: supplemental_groups,
|
|
113
|
+
sysctls: sysctls
|
|
114
|
+
}.compact
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
let(:pod_run_as_user) { nil }
|
|
118
|
+
let(:pod_run_as_group) { nil }
|
|
119
|
+
let(:pod_run_as_non_root) { nil }
|
|
120
|
+
let(:pod_se_linux_options) { nil }
|
|
121
|
+
let(:fs_group) { nil }
|
|
122
|
+
let(:supplemental_groups) { nil }
|
|
123
|
+
let(:sysctls) { nil }
|
|
124
|
+
|
|
125
|
+
# Default container
|
|
126
|
+
let(:container) { fail Matsuri::UndefinedLet, 'Must define let(:container)'}
|
|
127
|
+
let(:volume) { fail Matsuri::UndefinedLet, 'Must define let(:volume)' }
|
|
128
|
+
|
|
129
|
+
let(:primary_container) { containers.first[:name] }
|
|
130
|
+
let(:primary_image) { fail Matsuri::UndefinedLet, 'Must define let(:primary_image) for deployment or replication controller' }
|
|
131
|
+
|
|
132
|
+
# Resources for default container
|
|
133
|
+
let(:resources) { { requests: resource_requests, limits: resource_limits } }
|
|
134
|
+
let(:resource_requests) { { cpu: cpu_request, memory: mem_request }.compact }
|
|
135
|
+
let(:resource_limits) { { cpu: cpu_limit, memory: mem_limit }.compact }
|
|
136
|
+
|
|
137
|
+
# We want to make sure all limits are defined
|
|
138
|
+
let(:cpu_limit) { fail Matsuri::UndefinedLet, 'Must define let(:cpu_limit)' }
|
|
139
|
+
let(:mem_limit) { fail Matsuri::UndefinedLet, 'Must define let(:mem_limit)' }
|
|
140
|
+
let(:cpu_request) { cpu_limit }
|
|
141
|
+
let(:mem_request) { mem_limit }
|
|
142
|
+
|
|
143
|
+
# Default container security context
|
|
144
|
+
let(:security_context) do
|
|
145
|
+
{
|
|
146
|
+
runAsUser: run_as_user,
|
|
147
|
+
runAsGroup: run_as_group,
|
|
148
|
+
runAsNonRoot: run_as_non_root,
|
|
149
|
+
privileged: privileged,
|
|
150
|
+
allowPrivilegeEscalation: allow_privilege_escalation,
|
|
151
|
+
seLinuxOptions: se_linux_options,
|
|
152
|
+
capabilities: capabilities
|
|
153
|
+
}.compact
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
let(:run_as_user) { nil }
|
|
157
|
+
let(:run_as_group) { nil }
|
|
158
|
+
let(:run_as_non_root) { nil }
|
|
159
|
+
let(:privileged) { nil }
|
|
160
|
+
let(:allow_privilege_escalation) { nil }
|
|
161
|
+
let(:se_linux_options) { nil }
|
|
162
|
+
let(:capabilities) { nil }
|
|
163
|
+
|
|
164
|
+
### Helpers
|
|
165
|
+
|
|
166
|
+
# Pods are practically useless with using apply. When we converge, we want
|
|
167
|
+
# to recreate instead. Pods unmanaged by a workload controller are more
|
|
168
|
+
# useful in dev mode than anywhere else
|
|
169
|
+
let(:converge_via) { :recreate }
|
|
170
|
+
|
|
171
|
+
# Helper methods
|
|
172
|
+
def config_file(path)
|
|
173
|
+
File.join config.config_path, path
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def cache_path(path)
|
|
177
|
+
File.join config.cache_path, path
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def src_path(path)
|
|
181
|
+
File.join config.src_path, path
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def base_path(path)
|
|
185
|
+
File.join config.base_path, path
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def env_from_secret(name:, key:)
|
|
189
|
+
{ secretKeyRef: { name: name, key: key } }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def env_from_config_map(name:, key:)
|
|
193
|
+
{ configMapKeyRef: { name: name, key: key } }
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def env_from_field_ref(path:)
|
|
197
|
+
{ fieldRef: { fieldPath: path } }
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def env_from_resource(resource:, container: nil, divisor: nil)
|
|
201
|
+
{ resourceFieldRef: { resource: resource, containerName: container, divisor: divisor } }
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def sorted_env(hash)
|
|
205
|
+
expand_env(hash).sort { |a,b| a[:name] <=> b[:name] }
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def port(num, protocol: 'TCP', name: nil, host_ip: nil, host_port: nil)
|
|
209
|
+
{
|
|
210
|
+
name: name.try(:to_s),
|
|
211
|
+
containerPort: num,
|
|
212
|
+
protocol: protocol.to_s,
|
|
213
|
+
hostIP: host_ip,
|
|
214
|
+
hostPort: host_port
|
|
215
|
+
}.compact
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def mount(name, path, options = {})
|
|
219
|
+
{
|
|
220
|
+
name: name,
|
|
221
|
+
mountPath: path,
|
|
222
|
+
readOnly: options[:read_only] || false,
|
|
223
|
+
mountPropogation: options[:mount_propogation],
|
|
224
|
+
recursivelyReadOnly: options[:recursively_read_only],
|
|
225
|
+
subPath: options[:sub_path],
|
|
226
|
+
subPathExpr: options[:sub_path_expr]
|
|
227
|
+
}.compact
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def host_path_volume(name, host_path)
|
|
231
|
+
{ name: name, hostPath: { path: host_path } }
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def empty_dir_volume(name, medium: nil)
|
|
235
|
+
{ name: name, emptyDir: { medium: medium }.compact }
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def tmpfs_volume(name)
|
|
239
|
+
empty_dir_volume(name, medium: 'Memory')
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def secret_volume(name, secret_name:, items: nil, default_mode: nil)
|
|
243
|
+
{ name: name, secret: { secretName: secret_name, items: items, defaultMode: default_mode }.compact }
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def secret_item(key, path, mode: nil)
|
|
247
|
+
{ key: key, path: path, mode: mode }.compact
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def config_map_volume(name, config_map_name: nil, items: nil, default_mode: nil)
|
|
251
|
+
config_map_name ||= name
|
|
252
|
+
{ name: name, configMap: { name: config_map_name, items: items, defaultMode: default_mode }.compact }
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def gce_volume(name, pd_name:, fs_type: 'ext4')
|
|
256
|
+
{ name: name, gcePersistentDisk: { pdName: pd_name, fsType: fs_type } }
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def label_selector(match_labels: nil, match_expressions: nil)
|
|
260
|
+
{
|
|
261
|
+
matchLabels: match_labels,
|
|
262
|
+
matchExpressions: match_expressions.present? ? Array(match_expressions) : nil
|
|
263
|
+
}.compact
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def match_labels_selector(matcher)
|
|
267
|
+
label_selector(match_labels: matcher)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def match_expressions_selector(matcher)
|
|
271
|
+
label_selector(match_expressions: matcher)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def match_expression(key:, operator:, values: nil)
|
|
275
|
+
{ key: key, operator: operator, values: values }.compact
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def tolerate(key: nil, value: nil, operator: 'Equal', effect: nil, toleration_seconds: nil)
|
|
279
|
+
{
|
|
280
|
+
key: key,
|
|
281
|
+
value: value,
|
|
282
|
+
effect: effect,
|
|
283
|
+
operator: operator,
|
|
284
|
+
tolerationSeconds: toleration_seconds
|
|
285
|
+
}.compact
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Helper to generate pod affinity term
|
|
289
|
+
# Defaults to node name for topology key, and empty namespaces
|
|
290
|
+
def pod_affinity_term(match_labels: nil, match_expressions: nil, namespaces: [], topology_key: 'kubernetes.io/hostname')
|
|
291
|
+
{
|
|
292
|
+
labelSelector: label_selector(match_labels: match_labels, match_expressions: match_expressions),
|
|
293
|
+
namespaces: namespaces,
|
|
294
|
+
topologyKey: topology_key
|
|
295
|
+
}.compact
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
alias_method :affinity_term, :pod_affinity_term
|
|
299
|
+
|
|
300
|
+
def weighted_pod_affinity_term(weight: 100, match_labels: nil, match_expressions: nil, namespaces: [], topology_key: 'kubernetes.io/hostname')
|
|
301
|
+
{
|
|
302
|
+
weight: weight,
|
|
303
|
+
podAffinityTerm: pod_affinity_term(match_labels: match_labels, match_expressions: match_expressions, namespaces: namespaces, topology_key: topology_key)
|
|
304
|
+
}
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
alias_method :weighted_affinity_term, :weighted_pod_affinity_term
|
|
308
|
+
|
|
309
|
+
def preferred_during_scheduling_ignored_during_execution(*weighted_affinity_terms)
|
|
310
|
+
{ preferredDuringSchedulingIgnoredDuringExecution: weighted_affinity_terms }
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
alias_method :preferred_during_scheduling, :preferred_during_scheduling_ignored_during_execution
|
|
314
|
+
|
|
315
|
+
def required_during_scheduling_ignored_during_execution(*affinity_terms)
|
|
316
|
+
{ requiredDuringSchedulingIgnoredDuringExecution: affinity_terms }
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
alias_method :required_during_scheduling, :required_during_scheduling_ignored_during_execution
|
|
320
|
+
|
|
321
|
+
def topology_spread_constraint(opts = {})
|
|
322
|
+
{
|
|
323
|
+
maxSkew: opts[:max_skew],
|
|
324
|
+
topologyKey: opts[:topology_key],
|
|
325
|
+
whenUnsatisfiable: opts[:when_unsatisfiable],
|
|
326
|
+
labelSelector: opts[:label_selector]
|
|
327
|
+
}.compact
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def spread_by_node(opts = {})
|
|
331
|
+
topology_spread_constraint({topology_key: 'kubernetes.io/hostname'}.merge(opts))
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def spread_by_zone(opts = {})
|
|
335
|
+
topology_spread_constraint({topology_key: 'topology.kubernetes.io/zone'}.merge(opts))
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def spread_by_region(opts = {})
|
|
339
|
+
topology_spread_constraint({topology_key: 'topology.kubernetes.io/region'}.merge(opts))
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Helpers
|
|
343
|
+
|
|
344
|
+
class << self
|
|
345
|
+
def load_path
|
|
346
|
+
Matsuri::Config.pods_path
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def definition_module_name
|
|
350
|
+
'Pods'
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matsuri
|
|
4
|
+
module Kubernetes
|
|
5
|
+
class ReplicaSet < Matsuri::Kubernetes::Base
|
|
6
|
+
include Matsuri::Concerns::PodTemplate
|
|
7
|
+
|
|
8
|
+
let(:api_version) { 'apps/v1' } # K8S 1.10
|
|
9
|
+
let(:kind) { 'ReplicaSet' } # http://kubernetes.io/docs/user-guide/replicasets/
|
|
10
|
+
|
|
11
|
+
# Overridables
|
|
12
|
+
|
|
13
|
+
let(:spec) do
|
|
14
|
+
{
|
|
15
|
+
replicas: replicas,
|
|
16
|
+
selector: selector,
|
|
17
|
+
template: template
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
let(:selector) { { matchLabels: match_labels, matchExpressions: match_expressions } }
|
|
22
|
+
|
|
23
|
+
# Supplied by the owning stack through dep_options, or by the CLI
|
|
24
|
+
# positional; pod templates read it.
|
|
25
|
+
let(:image_tag) { options[:image_tag] }
|
|
26
|
+
|
|
27
|
+
# Explicitly define replicas
|
|
28
|
+
let(:replicas) { fail Matsuri::UndefinedLet, 'Must define let(:replicas)' }
|
|
29
|
+
let(:match_labels) { fail Matsuri::UndefinedLet, 'Must define let(:match_labels)' }
|
|
30
|
+
|
|
31
|
+
let(:match_expressions) { [] }
|
|
32
|
+
|
|
33
|
+
let(:primary_container_path) { ['spec', 'template', 'spec', 'containers', 0] }
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
def load_path
|
|
37
|
+
Matsuri::Config.replica_sets_path
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def definition_module_name
|
|
41
|
+
'ReplicaSets'
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
5
|
+
module Matsuri
|
|
6
|
+
module Kubernetes
|
|
7
|
+
class Secret < Matsuri::Kubernetes::Base
|
|
8
|
+
let(:api_version) { 'v1' } # K8S 1.10
|
|
9
|
+
let(:kind) { 'Secret' }
|
|
10
|
+
|
|
11
|
+
# Overridables
|
|
12
|
+
let(:manifest) do
|
|
13
|
+
{
|
|
14
|
+
apiVersion: api_version,
|
|
15
|
+
kind: kind,
|
|
16
|
+
metadata: final_metadata,
|
|
17
|
+
type: secret_type,
|
|
18
|
+
data: data
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Looks like Kubernetes 1.0.6 supports only Opaque secrets
|
|
23
|
+
let(:secret_type) { 'Opaque' }
|
|
24
|
+
let(:data) { { secret_key => secret_value } }
|
|
25
|
+
|
|
26
|
+
let(:secret_key) { fail Matsuri::UndefinedLet, 'Must define let(:secret_key)' }
|
|
27
|
+
let(:secret_value) { secret_from_path(full_secret_path) }
|
|
28
|
+
let(:secret_path) { fail Matsuri::UndefinedLet, 'Must define let(:secret_path)' }
|
|
29
|
+
|
|
30
|
+
let(:full_secret_path) { secret_file(secret_path) }
|
|
31
|
+
|
|
32
|
+
# Override let(:data) with yaml_data if the secret file is
|
|
33
|
+
# in yaml form
|
|
34
|
+
let(:yaml_data) { base64_hash(yaml_secret) }
|
|
35
|
+
let(:yaml_secret) { YAML.load_file(secret_file(secret_path)) }
|
|
36
|
+
|
|
37
|
+
# Helpers
|
|
38
|
+
|
|
39
|
+
# Secrets must be Base64 Encoded. This load the secret from
|
|
40
|
+
# disk (default: config/secrets/) base64 encode it.
|
|
41
|
+
def secret_from(path)
|
|
42
|
+
secret_from_path(secret_file(path))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Load secret file and base64 encode it from any file location
|
|
46
|
+
def secret_from_path(path)
|
|
47
|
+
base64(File.read(path))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Helper to load secrets file from default location
|
|
51
|
+
def secret_file(path)
|
|
52
|
+
File.join(Matsuri::Config.config_secrets_path, path)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Helper for base64 encoder. This allows us to pull
|
|
56
|
+
# secrets from elsewhere, such as from Hashicorp Vault
|
|
57
|
+
# or from an encrypted AWS S3 bucket item
|
|
58
|
+
def base64(secret)
|
|
59
|
+
Base64.encode64(secret).split("\n").join # Strip all newlines
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Helper to transform the values of a hash to base64
|
|
63
|
+
def base64_hash(hash)
|
|
64
|
+
Hash[hash.map { |k,v| [k, base64(v) ] }]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
### Overrides
|
|
68
|
+
|
|
69
|
+
# Secrets are not generally something passed around in developer copies
|
|
70
|
+
# These are things set up by the cluster administrator, so we can assume
|
|
71
|
+
# if it is present on the server, we can skip it. As such, better to use
|
|
72
|
+
# recreate than apply for secrets.
|
|
73
|
+
let(:converge_via) { :recreate }
|
|
74
|
+
|
|
75
|
+
class << self
|
|
76
|
+
def load_path
|
|
77
|
+
Matsuri::Config.secrets_path
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def definition_module_name
|
|
81
|
+
'Secrets'
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matsuri
|
|
4
|
+
module Kubernetes
|
|
5
|
+
class Service < Matsuri::Kubernetes::Base
|
|
6
|
+
let(:api_version) { 'v1' } # K8S 1.10
|
|
7
|
+
let(:kind) { 'Service' }
|
|
8
|
+
|
|
9
|
+
# Overridables
|
|
10
|
+
let(:spec) do
|
|
11
|
+
{
|
|
12
|
+
ports: ports,
|
|
13
|
+
selector: selector,
|
|
14
|
+
type: service_type,
|
|
15
|
+
clusterIP: cluster_ip
|
|
16
|
+
}.compact
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
let(:ports) { [port(port_num)] }
|
|
20
|
+
let(:port_num) { fail Matsuri::UndefinedLet, 'Must define let(:port_num)' }
|
|
21
|
+
let(:selector) { fail Matsuri::UndefinedLet, 'Must define let(:selector)' }
|
|
22
|
+
let(:service_type) { 'ClusterIP' }
|
|
23
|
+
let(:cluster_ip) { nil }
|
|
24
|
+
|
|
25
|
+
# Helpers
|
|
26
|
+
# Optional:
|
|
27
|
+
# target_port: the port exposed externally
|
|
28
|
+
# protocol: connection protocol (ex: tcp)
|
|
29
|
+
def port(num, target_port: nil, protocol: nil, name: nil, node_port: nil)
|
|
30
|
+
_port = { port: num }
|
|
31
|
+
_port[:targetPort] = target_port if target_port
|
|
32
|
+
_port[:nodePort] = node_port if node_port
|
|
33
|
+
_port[:protocol] = protocol.to_s if protocol
|
|
34
|
+
_port[:name] = name.to_s if name
|
|
35
|
+
return _port
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
def load_path
|
|
40
|
+
Matsuri::Config.services_path
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def definition_module_name
|
|
44
|
+
'Services'
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matsuri
|
|
4
|
+
module Kubernetes
|
|
5
|
+
class StatefulSet < Matsuri::Kubernetes::Base
|
|
6
|
+
include Matsuri::Concerns::PodTemplate
|
|
7
|
+
|
|
8
|
+
let(:api_version) { 'apps/v1' }
|
|
9
|
+
let(:kind) { 'StatefulSet' } # https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/
|
|
10
|
+
|
|
11
|
+
let(:spec) do
|
|
12
|
+
{
|
|
13
|
+
replicas: replicas,
|
|
14
|
+
selector: selector,
|
|
15
|
+
template: template,
|
|
16
|
+
serviceName: service_name,
|
|
17
|
+
podManagementPolicy: pod_management_policy,
|
|
18
|
+
updateStrategy: update_strategy,
|
|
19
|
+
revisionHistoryLimit: revision_history_limit,
|
|
20
|
+
volumeClaimTemplates: volume_claim_templates
|
|
21
|
+
}.compact
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Supplied by the owning stack through dep_options, or by the CLI
|
|
25
|
+
# positional; pod templates read it.
|
|
26
|
+
let(:image_tag) { options[:image_tag] }
|
|
27
|
+
|
|
28
|
+
let(:selector) { { matchLabels: match_labels, matchExpressions: match_expressions }.compact }
|
|
29
|
+
let(:service_name) { fail Matsuri::UndefinedLet, 'Must define let(:service_name). Will define "pod-specific-string.serviceName.default.svc.cluster.local"' }
|
|
30
|
+
|
|
31
|
+
# Explicitly define replicas
|
|
32
|
+
let(:replicas) { fail Matsuri::UndefinedLet, 'Must define let(:replicas)' }
|
|
33
|
+
let(:match_labels) { fail Matsuri::UndefinedLet, 'Must define let(:match_labels)' }
|
|
34
|
+
let(:match_expressions) { nil} # Default this to nil so it drops off unless defined
|
|
35
|
+
|
|
36
|
+
let(:revision_history_limit) { nil }
|
|
37
|
+
|
|
38
|
+
# Deployment Strategy. Defaults to Rolling Update. Recreate is the other one.
|
|
39
|
+
let(:pod_management_policy) { fail Matsuri::UndefinedLet, 'Must define let(:pod_management_policy) as OrderedReady or Parallel' }
|
|
40
|
+
let(:update_strategy) { fail Matsuri::UndefinedLet, 'Must define let(:update_strategy). See: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies' }
|
|
41
|
+
let(:on_delete_strategy) { { type: 'OnDelete' } }
|
|
42
|
+
let(:rolling_update_strategy) { { type: 'RollingUpdate', rollingUpdate: rolling_update } }
|
|
43
|
+
let(:rolling_update) { { partition: partition } }
|
|
44
|
+
let(:partition) { 0 }
|
|
45
|
+
|
|
46
|
+
let(:volume_claim_templates) { nil } # Make sure this is nil, otherwise stateful set cannot be updated with apply
|
|
47
|
+
|
|
48
|
+
let(:primary_container_path) { ['spec', 'template', 'spec', 'containers', 0] }
|
|
49
|
+
|
|
50
|
+
### Helpers
|
|
51
|
+
def volume_claim_template(name:, storage_class: nil, access_modes: 'ReadWriteOnce', storage_request:)
|
|
52
|
+
{
|
|
53
|
+
metadata: { name: name },
|
|
54
|
+
spec: {
|
|
55
|
+
accessModes: Array(access_modes),
|
|
56
|
+
storageClassName: storage_class,
|
|
57
|
+
resources: {
|
|
58
|
+
requests: { storage: storage_request }
|
|
59
|
+
}.compact
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class << self
|
|
65
|
+
def load_path
|
|
66
|
+
Matsuri::Config.stateful_sets_path
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def definition_module_name
|
|
70
|
+
'StatefulSets'
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matsuri
|
|
4
|
+
module Kubernetes
|
|
5
|
+
# Kubernetes Persistent Volume
|
|
6
|
+
# http://kubernetes.io/docs/user-guide/persistent-volumes/#storageclasses
|
|
7
|
+
# No official API definition found
|
|
8
|
+
class StorageClass < Matsuri::Kubernetes::Base
|
|
9
|
+
let(:kind) { 'StorageClass' }
|
|
10
|
+
let(:api_version) { 'storage.k8s.io/v1' }
|
|
11
|
+
let(:provisioner) { fail Matsuri::UndefinedLet, 'Must define let(:provisioner). Example: let(:provisoner) { gce_pd }' }
|
|
12
|
+
|
|
13
|
+
let(:gce_pd) { 'kubernetes.io/gce-pd' }
|
|
14
|
+
let(:aws_ebs) { 'kubernetes.io/aws-ebs' }
|
|
15
|
+
|
|
16
|
+
let(:parameters) { fail Matsuri::UndefinedLet, 'Must define let(:parameters)' }
|
|
17
|
+
|
|
18
|
+
### K8S default is "Immediate", but "WaitForFirstConsumer" is the sensible default
|
|
19
|
+
let(:volume_binding_mode) { 'WaitForFirstConsumer' }
|
|
20
|
+
let(:allowed_topologies) { nil }
|
|
21
|
+
|
|
22
|
+
let(:manifest) do
|
|
23
|
+
{
|
|
24
|
+
apiVersion: api_version,
|
|
25
|
+
kind: kind,
|
|
26
|
+
metadata: final_metadata,
|
|
27
|
+
provisioner: provisioner,
|
|
28
|
+
parameters: parameters,
|
|
29
|
+
volumeBindingMode: volume_binding_mode,
|
|
30
|
+
allowedTopologies: allowed_topologies
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class << self
|
|
35
|
+
def load_path
|
|
36
|
+
Matsuri::Config.storage_classes_path
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def definition_module_name
|
|
40
|
+
'StorageClasses'
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mixlib/config'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
|
|
6
|
+
module Matsuri
|
|
7
|
+
# This contains configs specific to the platform. Unlike Matsuri::Config, these
|
|
8
|
+
# is freeform. Think of it as a key-value store which can be loaded and layered
|
|
9
|
+
# from a set of files. You would define the list of platform config files to load,
|
|
10
|
+
# and Matsuri will load those values in here. Files loaded later will override values
|
|
11
|
+
# loaded from earlier files.
|
|
12
|
+
#
|
|
13
|
+
# Platform config is where you will put things like dev secrets, API test keys, to
|
|
14
|
+
# fill into the environment spec for Kubernetes pods.
|
|
15
|
+
module Platform
|
|
16
|
+
extend Mixlib::Config
|
|
17
|
+
config_strict_mode false # Allow users to extend the key space
|
|
18
|
+
|
|
19
|
+
# Repo-wide save-config policy for the normal apply path's create and
|
|
20
|
+
# recreate; stacks override per stack, resources per definition.
|
|
21
|
+
default :save_config, true
|
|
22
|
+
|
|
23
|
+
# Repo-wide field manager for server-side delivery. It declares the
|
|
24
|
+
# repo's ongoing-delivery identity: the conflict frontier forms
|
|
25
|
+
# exactly where manager names differ, and a shared name is a single
|
|
26
|
+
# manager — writes within it never conflict.
|
|
27
|
+
default :field_manager, 'matsuri'
|
|
28
|
+
|
|
29
|
+
# Loads a platform file if it exists. Each subsequent file loaded
|
|
30
|
+
# will override older ones.
|
|
31
|
+
def self.load_configuration(load_paths = [])
|
|
32
|
+
load_paths.each do |path|
|
|
33
|
+
next unless File.file?(path)
|
|
34
|
+
from_file(path)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|