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.
Files changed (157) hide show
  1. checksums.yaml +5 -5
  2. data/.envrc +1 -0
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +9 -0
  5. data/AGENTS.md +18 -0
  6. data/CHANGELOG.md +642 -0
  7. data/Gemfile +2 -0
  8. data/README.md +86 -8
  9. data/Rakefile +20 -0
  10. data/bin/console +1 -0
  11. data/bin/matsuri +6 -0
  12. data/bin/setup +2 -0
  13. data/docs/acceptors.md +181 -0
  14. data/docs/boot.md +101 -0
  15. data/docs/design/acceptors.md +157 -0
  16. data/docs/design/boot.md +131 -0
  17. data/docs/design/ssa.md +142 -0
  18. data/docs/design/stacks.md +153 -0
  19. data/docs/diff.md +85 -0
  20. data/docs/known-issues.md +19 -0
  21. data/docs/local-dev.md +173 -0
  22. data/docs/presenters.md +317 -0
  23. data/docs/resources.md +121 -0
  24. data/docs/setup.md +97 -0
  25. data/docs/ssa.md +149 -0
  26. data/docs/stacks.md +146 -0
  27. data/flake.lock +61 -0
  28. data/flake.nix +35 -0
  29. data/lib/matsuri/acceptor.rb +33 -0
  30. data/lib/matsuri/acceptors/image_tag.rb +15 -0
  31. data/lib/matsuri/cmd.rb +46 -0
  32. data/lib/matsuri/cmds/apply.rb +78 -0
  33. data/lib/matsuri/cmds/cli.rb +147 -0
  34. data/lib/matsuri/cmds/create.rb +87 -0
  35. data/lib/matsuri/cmds/delete.rb +86 -0
  36. data/lib/matsuri/cmds/diff.rb +105 -0
  37. data/lib/matsuri/cmds/generate.rb +24 -0
  38. data/lib/matsuri/cmds/kubectl.rb +16 -0
  39. data/lib/matsuri/cmds/recreate.rb +65 -0
  40. data/lib/matsuri/cmds/scale.rb +37 -0
  41. data/lib/matsuri/cmds/show.rb +123 -0
  42. data/lib/matsuri/cmds/status.rb +67 -0
  43. data/lib/matsuri/concerns/awaiting.rb +24 -0
  44. data/lib/matsuri/concerns/diffable.rb +135 -0
  45. data/lib/matsuri/concerns/export_manifest.rb +56 -0
  46. data/lib/matsuri/concerns/import_manifest.rb +64 -0
  47. data/lib/matsuri/concerns/pod_template.rb +37 -0
  48. data/lib/matsuri/concerns/pods/spread_pods.rb +47 -0
  49. data/lib/matsuri/concerns/pods/spread_topology.rb +63 -0
  50. data/lib/matsuri/concerns/registry_helpers.rb +59 -0
  51. data/lib/matsuri/config.rb +101 -0
  52. data/lib/matsuri/core_ext.rb +107 -0
  53. data/lib/matsuri/dsl/cluster/aggregated_cluster_role.rb +75 -0
  54. data/lib/matsuri/dsl/cluster/binding.rb +118 -0
  55. data/lib/matsuri/dsl/cluster/cluster_role.rb +41 -0
  56. data/lib/matsuri/dsl/cluster/role.rb +49 -0
  57. data/lib/matsuri/dsl/cluster/scope.rb +245 -0
  58. data/lib/matsuri/dsl/cluster/service_account.rb +76 -0
  59. data/lib/matsuri/dsl/concerns/default_binding.rb +34 -0
  60. data/lib/matsuri/dsl/concerns/manifest_set.rb +21 -0
  61. data/lib/matsuri/dsl/concerns/metadata.rb +56 -0
  62. data/lib/matsuri/dsl/concerns/rbac_rules.rb +39 -0
  63. data/lib/matsuri/initializer.rb +41 -0
  64. data/lib/matsuri/kubernetes/base.rb +107 -0
  65. data/lib/matsuri/kubernetes/config_map.rb +100 -0
  66. data/lib/matsuri/kubernetes/cron_job.rb +115 -0
  67. data/lib/matsuri/kubernetes/daemon_set.rb +62 -0
  68. data/lib/matsuri/kubernetes/deployment.rb +65 -0
  69. data/lib/matsuri/kubernetes/endpoints.rb +57 -0
  70. data/lib/matsuri/kubernetes/horizontal_pod_autoscaler.rb +116 -0
  71. data/lib/matsuri/kubernetes/imported_manifest.rb +61 -0
  72. data/lib/matsuri/kubernetes/ingress.rb +66 -0
  73. data/lib/matsuri/kubernetes/persistent_volume.rb +56 -0
  74. data/lib/matsuri/kubernetes/persistent_volume_claim.rb +41 -0
  75. data/lib/matsuri/kubernetes/pod.rb +355 -0
  76. data/lib/matsuri/kubernetes/replica_set.rb +46 -0
  77. data/lib/matsuri/kubernetes/secret.rb +86 -0
  78. data/lib/matsuri/kubernetes/service.rb +49 -0
  79. data/lib/matsuri/kubernetes/stateful_set.rb +75 -0
  80. data/lib/matsuri/kubernetes/storage_class.rb +45 -0
  81. data/lib/matsuri/platform.rb +38 -0
  82. data/lib/matsuri/presenter.rb +47 -0
  83. data/lib/matsuri/presenters/helpers.rb +39 -0
  84. data/lib/matsuri/presenters/prune_empty.rb +59 -0
  85. data/lib/matsuri/presenters/seed_image.rb +28 -0
  86. data/lib/matsuri/presenters/seed_replicas.rb +25 -0
  87. data/lib/matsuri/presenters/seed_resources.rb +29 -0
  88. data/lib/matsuri/registry.rb +267 -0
  89. data/lib/matsuri/shell_out.rb +119 -0
  90. data/lib/matsuri/stack.rb +276 -0
  91. data/lib/matsuri/task.rb +20 -0
  92. data/lib/matsuri/tasks/cluster.rb +79 -0
  93. data/lib/matsuri/tasks/docker.rb +25 -0
  94. data/lib/matsuri/tasks/hash_diff.rb +61 -0
  95. data/lib/matsuri/tasks/kubectl.rb +21 -0
  96. data/lib/matsuri/tasks/kubectl_ops/apply.rb +24 -0
  97. data/lib/matsuri/tasks/kubectl_ops/create.rb +27 -0
  98. data/lib/matsuri/tasks/kubectl_ops/delete.rb +21 -0
  99. data/lib/matsuri/tasks/kubectl_ops/diff.rb +47 -0
  100. data/lib/matsuri/tasks/kubectl_ops/get.rb +36 -0
  101. data/lib/matsuri/tasks/kubectl_ops/recreate.rb +35 -0
  102. data/lib/matsuri/tasks/kubectl_ops/replace.rb +29 -0
  103. data/lib/matsuri/tasks/kubectl_ops/rollout_status.rb +22 -0
  104. data/lib/matsuri/tasks/kubectl_ops/scale.rb +22 -0
  105. data/lib/matsuri/tasks/kubectl_ops/server_side_apply.rb +32 -0
  106. data/lib/matsuri/tasks/kubectl_ops/status.rb +20 -0
  107. data/lib/matsuri/tasks/kubectl_ops.rb +218 -0
  108. data/lib/matsuri/tasks/pod.rb +56 -0
  109. data/lib/matsuri/tasks/stacks/seed.rb +93 -0
  110. data/lib/matsuri/tasks/stacks.rb +31 -0
  111. data/lib/matsuri/transform/algebra.rb +52 -0
  112. data/lib/matsuri/transform/callable.rb +54 -0
  113. data/lib/matsuri/transform/combinators.rb +61 -0
  114. data/lib/matsuri/transform/env.rb +43 -0
  115. data/lib/matsuri/transform/kubernetes.rb +89 -0
  116. data/lib/matsuri/transform.rb +20 -0
  117. data/lib/matsuri/version.rb +3 -1
  118. data/lib/matsuri.rb +158 -2
  119. data/matsuri.gemspec +20 -7
  120. data/tests/bin/minikube-start.sh +27 -0
  121. data/tests/bin/probe-save-config.sh +46 -0
  122. data/tests/bin/probe-ssa.sh +206 -0
  123. data/tests/example/.gitignore +1 -0
  124. data/tests/example/Gemfile +7 -0
  125. data/tests/example/README.md +91 -0
  126. data/tests/example/bin/matsuri +14 -0
  127. data/tests/example/config/initializer.rb +12 -0
  128. data/tests/example/config/matsuri.rb +9 -0
  129. data/tests/example/config/platform.rb +39 -0
  130. data/tests/example/lib/acme/nodes/dedicated.rb +26 -0
  131. data/tests/example/lib/acme/nodes/preemptible.rb +17 -0
  132. data/tests/example/lib/acme/nodes/required_cores.rb +26 -0
  133. data/tests/example/lib/acme/nodes/resource_class.rb +26 -0
  134. data/tests/example/lib/acme/pods/cacerts.rb +15 -0
  135. data/tests/example/lib/acme/pods/healthcheck.rb +36 -0
  136. data/tests/example/lib/acme.rb +26 -0
  137. data/tests/example/platform/config_maps/billing-settings.rb +8 -0
  138. data/tests/example/platform/config_maps/cacerts.rb +9 -0
  139. data/tests/example/platform/deployments/billing-web.rb +5 -0
  140. data/tests/example/platform/deployments/checkout-web.rb +6 -0
  141. data/tests/example/platform/namespaces/billing-dev.yaml +7 -0
  142. data/tests/example/platform/namespaces/checkout-dev.yaml +7 -0
  143. data/tests/example/platform/pods/billing.rb +31 -0
  144. data/tests/example/platform/pods/checkout.rb +27 -0
  145. data/tests/example/platform/services/billing-web.rb +6 -0
  146. data/tests/example/platform/services/checkout-web.rb +4 -0
  147. data/tests/example/platform/stacks/billing.rb +8 -0
  148. data/tests/example/platform/stacks/checkout.rb +19 -0
  149. data/tests/k8s/save-config/deployment-declared.yaml +19 -0
  150. data/tests/k8s/save-config/deployment-seeded.yaml +20 -0
  151. data/tests/k8s/save-config/namespace.yaml +4 -0
  152. data/tests/k8s/ssa/declared-bumped.yaml +22 -0
  153. data/tests/k8s/ssa/declared.yaml +22 -0
  154. data/tests/k8s/ssa/namespace.yaml +4 -0
  155. data/tests/k8s/ssa/seed-via-create.yaml +24 -0
  156. metadata +288 -20
  157. 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