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,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rlet'
4
+
5
+ module Matsuri
6
+ module DSL
7
+ module Cluster
8
+ # Cluster resources here in Matsuri are a bit different as the are meant to
9
+ # be driven by the cluster dsl
10
+ class ServiceAccount
11
+ include Let
12
+ include Matsuri::Concerns::ExportManifest
13
+ include Matsuri::DSL::Concerns::Metadata
14
+ include Matsuri::DSL::Concerns::ManifestSet
15
+
16
+ attr_accessor :manifest_config
17
+
18
+ let(:api_version) { 'v1' }
19
+ let(:kind) { 'ServiceAccount' }
20
+
21
+ ### Manifest Set
22
+ let(:cluster_manifests) { manifest }
23
+
24
+ ### Manifest
25
+
26
+ let(:manifest) do
27
+ {
28
+ apiVersion: api_version,
29
+ kind: kind,
30
+ metadata: final_metadata,
31
+
32
+ automountServiceAccountToken: manifest_config[:automount_token],
33
+ imagePullSecrets: manifest_config[:image_pull_secrets].presence,
34
+ secrets: manifest_config[:secrets].presence
35
+ }.compact
36
+ end
37
+
38
+ def initialize(options = {}, &block)
39
+ initialize_metadata_dsl(options)
40
+ self.manifest_config = {
41
+ automount_token: nil,
42
+ image_pull_secrets: [],
43
+ secrets: []
44
+ }
45
+
46
+ configure(&block) if block
47
+ end
48
+
49
+ def configure(&block)
50
+ instance_eval(&block)
51
+ end
52
+
53
+ ### DSL Helpers
54
+ def automount_token(flag = true)
55
+ manifest_config[:automount_token] = flag
56
+ end
57
+
58
+ def image_pull_secret(name)
59
+ manifest_config[:image_pull_secrets] << { name: name }
60
+ end
61
+
62
+ # Usually the name is sufficient.
63
+ def secret(name, options = {})
64
+ manifest_config[:secrets] << {
65
+ name: name,
66
+ namespace: options[:namespace],
67
+ apiVersion: options[:api_version],
68
+ kind: options[:kind],
69
+ resourceVersion: options[:version],
70
+ uid: options[:uid]
71
+ }.compact
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rlet/concern'
4
+
5
+ module Matsuri
6
+ module DSL
7
+ module Concerns
8
+ module DefaultBinding
9
+ extend Concern
10
+
11
+ included do
12
+ let(:default_binding) { Matsuri::DSL::Cluster::Binding.new(default_binding_options) }
13
+
14
+ let(:default_binding_name) { name } # Use the name of the role or cluster_role
15
+ let(:default_binding_namespace) { namespace } # Use the namespace of the role
16
+ let(:default_binding_type) { kind }
17
+
18
+ let(:default_binding_options) do
19
+ {
20
+ name: default_binding_name,
21
+ namespace: default_binding_namespace,
22
+ type: default_binding_type,
23
+ scoped_options: scoped_options
24
+ }
25
+ end
26
+ end
27
+
28
+ def bind_to(name, kind:, namespace: nil)
29
+ default_binding.subject(name, kind: kind, namespace: namespace || self.namespace)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rlet/concern'
4
+
5
+ module Matsuri
6
+ module DSL
7
+ module Concerns
8
+ # Defines the callbacks for being able to generate a ManifestSet
9
+ module ManifestSet
10
+ extend Concern
11
+
12
+ included do
13
+ # By default, return nil. This will exclude the manifest from being generated
14
+ # To include a manifest, override it with something like let(:rbac_manifest) { manifest }
15
+ let(:rbac_manifests) { nil }
16
+ let(:cluster_manifests) { nil }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rlet/concern'
4
+
5
+ module Matsuri
6
+ module DSL
7
+ module Concerns
8
+ module Metadata
9
+ extend Concern
10
+
11
+ included do
12
+ attr_accessor :labels, :annotations, :options
13
+
14
+ let(:dsl_version) { Matsuri::Config.cluster_dsl_version }
15
+
16
+ let(:name) { options[:name] }
17
+ let(:namespace) { (options[:namespace] || parent_scope[:namespace])&.to_s }
18
+ let(:source_file) { parent_scope[:source_file] }
19
+ let(:skip?) { options[:skip] || parent_scope[:skip] }
20
+
21
+ let(:parent_scope) { options[:scoped_options] || {} }
22
+
23
+ let(:scoped_options) do
24
+ {
25
+ namespace: namespace,
26
+ source_file: source_file,
27
+ skip: skip?
28
+ }
29
+ end
30
+
31
+ let(:final_metadata) { default_metadata.merge(metadata) }
32
+ let(:default_metadata) { { name: name, namespace: namespace, labels: final_labels, annotations: final_annotations }.compact }
33
+ let(:metadata) { { } }
34
+ let(:default_labels) { { 'matsuri/dsl' => dsl_version } }
35
+ let(:default_annotations) { { 'matsuri/source_file' => source_file }.compact } # Added for easier debugging
36
+ let(:final_labels) { default_labels.merge(labels.to_h) }
37
+ let(:final_annotations) { default_annotations.merge(annotations.to_h) }
38
+ end
39
+
40
+ def initialize_metadata_dsl(options = {})
41
+ self.options = options
42
+ self.labels = []
43
+ self.annotations = []
44
+ end
45
+
46
+ def label(key, value)
47
+ labels << [key, value]
48
+ end
49
+
50
+ def annotate(key, value)
51
+ annotations << [key, value]
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rlet/concern'
4
+
5
+ module Matsuri
6
+ module DSL
7
+ module Concerns
8
+ module RbacRules
9
+ extend Concern
10
+
11
+ included do
12
+ attr_accessor :rules
13
+ end
14
+
15
+ def initialize_rbac_rules_dsl
16
+ self.rules = []
17
+ end
18
+
19
+ def rule(api_groups: '', urls: nil, resources: nil, names: nil, verbs: nil)
20
+ rules << {
21
+ 'apiGroups' => api_groups,
22
+ 'nonResourceURLs' => urls,
23
+ 'resourceNames' => names,
24
+ 'resources' => resources,
25
+ 'verbs' => verbs
26
+ }.compact.map(&method(:normalize_string_array)).to_h
27
+ end
28
+
29
+ def resources(resources, names: nil, verbs: nil, api_groups: '')
30
+ rule api_groups: api_groups, resources: resources, names: names, verbs: verbs
31
+ end
32
+
33
+ def normalize_string_array(k, v)
34
+ [k, Array(v).map(&:to_s)]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'json'
5
+
6
+ module Matsuri
7
+ module Initializer
8
+ def self.start(args)
9
+ # Flush stdout to keep CI harnesses updated
10
+ STDOUT.sync = true
11
+
12
+ # Look for the matsuri repo by walking up the parent directories for
13
+ # config/matsuri.rb. ENV['PWD'] preserves the path as the shell
14
+ # presented it; non-shell execs may not set it, hence the fallback.
15
+ matsuri_base_path = find_matsuri_base(ENV['PWD'] || Dir.pwd)
16
+ Matsuri.log :fatal, 'Unable to find the Matsuri repo. Looking for a config/matsuri.rb file.' unless matsuri_base_path
17
+ Matsuri::Config.base_path = matsuri_base_path
18
+
19
+ # The repo contributes code through matsuri, not through its launcher;
20
+ # every repo-authored file from here on may assume lib/ is loadable.
21
+ # See docs/design/boot.md
22
+ $LOAD_PATH.unshift(Matsuri::Config.lib_path) if File.directory?(Matsuri::Config.lib_path)
23
+
24
+ # The environment has exactly one source: the first positional
25
+ # argument (see docs/design/boot.md). Set before loading configuration
26
+ # so config files and the custom initializer can read it.
27
+ Matsuri::Config.environment = args.first
28
+ Matsuri::Config.load_configuration(Matsuri::Config.repo_defaults_path)
29
+ Matsuri::Platform.load_configuration(Matsuri::Config.platform_load_paths)
30
+
31
+ require Matsuri::Config.initializer_path if File.file?(Matsuri::Config.initializer_path)
32
+ Matsuri::Cmds::Cli.start(args.drop(1))
33
+ end
34
+
35
+ # Wish we have tail recursion here ...
36
+ def self.find_matsuri_base(cwd)
37
+ return cwd if File.file?(File.join(cwd, 'config/matsuri.rb'))
38
+ find_matsuri_base(File.expand_path('..', cwd)) unless cwd == '/'
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rlet'
4
+
5
+ require 'matsuri/core_ext'
6
+
7
+ require 'rainbow/ext/string'
8
+ require 'rlet/lazy_options'
9
+
10
+ module Matsuri
11
+ module Kubernetes
12
+ class Base
13
+ include Let
14
+ include RLet::LazyOptions
15
+ include Matsuri::Concerns::ExportManifest
16
+ include Matsuri::Concerns::RegistryHelpers
17
+ include Matsuri::Concerns::Diffable
18
+
19
+ class_attribute :presenters
20
+
21
+ # Declaration-surface vocabulary, as on stacks.
22
+ extend Matsuri::Transform::Combinators::Helpers
23
+ extend Matsuri::Presenters::Helpers
24
+
25
+ # Appends transform members to a scenario's pipeline, as on stacks:
26
+ # a scenario accumulates, declaration order is member order. Default
27
+ # is none. A resource's own pipeline completes its self-knowledge
28
+ # and runs before any stack-declared members — the stack's steps
29
+ # admit the finished manifest.
30
+ def self.present(scenario, *steps)
31
+ key = scenario.to_sym
32
+ current = presenters || {}
33
+ self.presenters = current.merge(key => Array(current[key]) + steps)
34
+ end
35
+
36
+ # Namespace resolution
37
+ let(:namespace) { namespace_from_option || default_namespace }
38
+
39
+ # Override default namespace instead of namespace in order to override namespace
40
+ # from the command line
41
+ let(:default_namespace) { namespace_from_config || 'default' }
42
+ let(:namespace_from_option) { options[:namespace] }
43
+ let(:namespace_from_config) { env_config.namespace }
44
+ let(:env_config) { Matsuri::Platform.send(Matsuri.environment) }
45
+
46
+ # Kubernetes manifest
47
+ let(:manifest) do
48
+ {
49
+ apiVersion: api_version,
50
+ kind: kind,
51
+ metadata: final_metadata,
52
+ spec: spec
53
+ }
54
+ end
55
+
56
+ let(:final_metadata) { default_metadata.merge(metadata) }
57
+ let(:default_metadata) { { name: name, namespace: namespace, labels: final_labels, annotations: final_annotations } }
58
+ # Needed for autodetecting 'current' for rolling-updates. However, this is obsolete with Deployments
59
+ let(:default_labels) { { 'matsuri-name' => name, 'matsuri-env' => matsuri_env, 'namespace' => namespace } }
60
+ let(:default_annotations) { { } }
61
+ let(:final_labels) { default_labels.merge(labels) }
62
+ let(:final_annotations) { default_annotations.merge(annotations) }
63
+ let(:resource_type) { kind.to_s.downcase }
64
+ let(:labels) { { } }
65
+ let(:annotations) { { } }
66
+ let(:matsuri_env) { Matsuri.environment }
67
+
68
+ # Optional parameters
69
+ # The acceptor instance the owning stack bound for this converge; nil
70
+ # when the definition is invoked standalone.
71
+ let(:stack_config) { options[:stack_config] }
72
+
73
+ # Delivery policy, read by the Tasks::KubectlOps boundary. Resources
74
+ # declare what delivery should do; the mechanism lives in the tasks.
75
+ # save_config resolves most-specific-first: a definition's own let,
76
+ # the owning stack's declaration (arriving as an option), then the
77
+ # repo-wide Matsuri::Platform.save_config (default true). It governs
78
+ # the normal apply path's create/recreate only. field_manager names
79
+ # the identity server-side delivery writes under; repo-wide
80
+ # Matsuri::Platform.field_manager (default 'matsuri'), overridable
81
+ # per definition.
82
+ let(:converge_via) { :apply }
83
+ let(:apply_via) { :apply }
84
+ let(:save_config) { options.key?(:save_config) ? options[:save_config] : Matsuri::Platform.save_config }
85
+ let(:field_manager) { Matsuri::Platform.field_manager }
86
+
87
+ # Diff policy, read by Tasks::HashDiff. The path to the primary
88
+ # container within the manifest; resources with a pod template
89
+ # declare it so the focused diff (--primary-container) can project
90
+ # both manifests to the same container.
91
+ let(:primary_container_path) { nil }
92
+
93
+ # Overridables
94
+ let(:spec) { fail Matsuri::UndefinedLet, 'Must define let(:spec)' }
95
+
96
+ # Helper functions
97
+ # Conditionally returns block or nil
98
+ # Useful for conditionally adding evironmental variables or list of volumes
99
+ # Example:
100
+ # let(:use_keyfile?) { false }
101
+ # let(:volumes) { [maybe(use_keyfile?) { mongo-keyfile-volume }].compact }
102
+ def maybe(cond)
103
+ yield if cond
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module Matsuri
6
+ module Kubernetes
7
+ class ConfigMap < Matsuri::Kubernetes::Base
8
+ let(:api_version) { 'v1' } # K8S 1.10
9
+ let(:kind) { 'ConfigMap' }
10
+
11
+ # Overridables
12
+ let(:manifest) do
13
+ {
14
+ apiVersion: api_version,
15
+ kind: kind,
16
+ metadata: final_metadata,
17
+ data: data,
18
+ binaryData: binary_data
19
+ }
20
+ end
21
+
22
+ let(:data) { nil }
23
+ let(:binary_data) { nil }
24
+
25
+ # Helpers
26
+
27
+ # Binary Files must be Base64 Encoded. This load the secret from
28
+ # disk (default: config/secrets/) base64 encode it.
29
+ def binary_from(path)
30
+ binary_from_path(config_overlays_path(path))
31
+ end
32
+
33
+ # Load secret file and base64 encode it from any file location
34
+ def binary_from_path(path)
35
+ base64(File.read(path))
36
+ end
37
+
38
+ def text_from(path)
39
+ text_from_path(config_overlays_path(path))
40
+ end
41
+
42
+ def text_from_path(path)
43
+ File.read(path)
44
+ end
45
+
46
+ def all_text_from(dir)
47
+ all_from(dir, &method(:text_from_path))
48
+ end
49
+
50
+ def all_bin_from(dir)
51
+ all_from(dir, &method(:bin_from_path))
52
+ end
53
+
54
+ def all_from(dir, &block)
55
+ all_from_dir(config_overlays_path(dir), &block)
56
+ end
57
+
58
+ # This will only look at the first-level and will not recursively descend
59
+ def all_from_dir(dir, &block)
60
+ Dir.glob(File.join(dir, '*')).
61
+ map { |path| [File.basename(path), yield(path)] }.
62
+ to_h
63
+ end
64
+
65
+ # Helper to load secrets file from default location
66
+ def config_overlays_path(path)
67
+ File.join(Matsuri::Config.config_overlays_path, path)
68
+ end
69
+
70
+ # Helper for base64 encoder. This allows us to pull
71
+ # secrets from elsewhere, such as from Hashicorp Vault
72
+ # or from an encrypted AWS S3 bucket item
73
+ def base64(file)
74
+ Base64.encode64(file).split("\n").join # Strip all newlines
75
+ end
76
+
77
+ # Helper to transform the values of a hash to base64
78
+ def base64_hash(hash)
79
+ Hash[hash.map { |k,v| [k, base64(v) ] }]
80
+ end
81
+
82
+ ### Delivery policy
83
+
84
+ # Like secrets, config maps are set up by the cluster administrator:
85
+ # if one is present on the server, we can skip it. Better to use
86
+ # recreate than apply.
87
+ let(:converge_via) { :recreate }
88
+
89
+ class << self
90
+ def load_path
91
+ Matsuri::Config.config_maps_path
92
+ end
93
+
94
+ def definition_module_name
95
+ 'Configmaps'
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matsuri
4
+ module Kubernetes
5
+ class CronJob < Matsuri::Kubernetes::Base
6
+ include Matsuri::Concerns::PodTemplate
7
+
8
+ let(:api_version) { 'batch/v1' } # k8s v1.21
9
+ let(:kind) { 'CronJob' } # https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/
10
+
11
+
12
+ # Parameters passed from command line
13
+ let(:image_tag) { options[:image_tag] }
14
+
15
+ # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#cronjobspec-v1-batch
16
+ let(:spec) do
17
+ {
18
+ schedule: schedule,
19
+ timeZone: time_zone,
20
+ suspend: suspend,
21
+ concurrencyPolicy: concurrency_policy,
22
+ successfulJobsHistoryLimit: successful_jobs_history_limit,
23
+ failedJobsHistoryLimit: failed_jobs_history_limit,
24
+ startingDeadlineSeconds: starting_deadline_seconds,
25
+
26
+ jobTemplate: {
27
+ metadata: job_template_metadata,
28
+ spec: job_template_spec
29
+ }.compact,
30
+ }.compact
31
+ end
32
+
33
+ let(:concurrency_policy) { nil } # 'Allow' | 'Forbid' | 'Replace', default: 'Allow'
34
+ let(:failed_jobs_history_limit) { nil } # non-negative integer, default: 1
35
+ let(:schedule) { fail 'Must define let(:schedule)' } # string, cron schedule format
36
+ let(:starting_deadline_seconds) { nil } # seconds, integer, optional
37
+ let(:successful_jobs_history_limit) { nil } # non-negative integer, default: 3
38
+ let(:suspend) { nil } # boolean, default: false
39
+
40
+ # k8s v1.27
41
+ # See: https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#time-zones
42
+ # See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
43
+ let(:time_zone) { nil } # string, TZ info, optional
44
+
45
+ let(:job_template_metadata) { { labels: pod_labels, annotations: pod_annotations } }
46
+
47
+
48
+ # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#jobspec-v1-batch
49
+ let(:job_template_spec) do
50
+ {
51
+ selector: selector,
52
+ completions: completions,
53
+ parallelism: parallelism,
54
+ activeDeadlineSeconds: active_deadline_seconds,
55
+ ttlSecondsAfterFinished: ttl_seconds_after_finished,
56
+ backoffLimit: backoff_limit,
57
+ managedBy: managed_by,
58
+ manualSelector: manual_selector,
59
+ podReplacementPolicy: pod_replacement_policy,
60
+ suspend: job_template_suspend,
61
+
62
+ template: template, # defined in Matsuri::Concerns::PodTemplate
63
+
64
+ # Indexed mode (for partitioning jobs), beta as of 1.31
65
+ completionMode: completion_mode,
66
+ backoffLimitPerIndex: backoff_limit_per_index,
67
+ maxFailedIndexes: max_failed_indexes,
68
+ podFailurePolicy: pod_failure_policy,
69
+ successPolicy: success_policy,
70
+ }.compact
71
+ end
72
+
73
+ let(:active_deadline_seconds) { nil } # integer
74
+ let(:backoff_limit) { nil } # integer, default: 6
75
+ let(:completions) { nil } # null | integer
76
+ let(:managed_by) { nil } # null | string
77
+ let(:manual_selector) { nil } # null | boolean
78
+ let(:parallelism) { nil } # null | integer
79
+ let(:ttl_seconds_after_finished) { nil } # null | integer
80
+
81
+ let(:selector) { nil } # This will be autopopulated by cronjob
82
+ #let(:selector) { { matchLabels: match_labels, matchExpressions: match_expressions } }
83
+ let(:match_labels) { fail Matsuri::UndefinedLet, 'Must define let(:match_labels)' }
84
+ let(:match_expressions) { [] }
85
+
86
+ let(:job_template_suspend) { nil } # boolean, default: false
87
+
88
+
89
+ let(:pod_replacement_policy) { nil } # 'TerminatingOrFailed' | 'Failed'
90
+
91
+ let(:completion_mode) { nil } # 'NonIndexed' | 'Indexed', default: 'Indexed'
92
+ let(:backoff_limit_per_index) { nil } # integer
93
+ let(:max_failed_indexes) { nil } # null | integer
94
+
95
+ # See: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#podfailurepolicy-v1-batch
96
+ let(:pod_failure_policy) { nil } # null | PodFailurePolicySpec
97
+
98
+ # See: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#successpolicy-v1-batch
99
+ let(:success_policy) { nil } # null | SuccessPolicySpec
100
+
101
+ let(:primary_container_path) { ['spec', 'jobTemplate', 'spec', 'template', 'spec', 'containers', 0] }
102
+
103
+
104
+ class << self
105
+ def load_path
106
+ Matsuri::Config.cron_jobs_path
107
+ end
108
+
109
+ def definition_module_name
110
+ 'CronJobs'
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matsuri
4
+ module Kubernetes
5
+ class DaemonSet < Matsuri::Kubernetes::Base
6
+ include Matsuri::Concerns::PodTemplate
7
+
8
+ let(:api_version) { 'apps/v1' } # K8S 1.10
9
+ let(:kind) { 'DaemonSet' } # http://kubernetes.io/docs/user-guide/deployments/
10
+
11
+ # Overridables
12
+ let(:spec) do
13
+ {
14
+ selector: selector,
15
+ template: template,
16
+ minReadySeconds: min_ready_seconds,
17
+ revisionHistoryLimit: revision_history_limit,
18
+ updateStrategy: update_strategy
19
+ }.compact
20
+ end
21
+
22
+ let(:selector) { { matchLabels: match_labels, matchExpressions: match_expressions } }
23
+
24
+ # Parameters
25
+ let(:image_tag) { options[:image_tag] }
26
+
27
+ # Explicitly define replicas
28
+ let(:match_labels) { fail Matsuri::UndefinedLet, 'Must define let(:match_labels)' }
29
+ let(:match_expressions) { [] }
30
+
31
+ # Update strategy
32
+ # See: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#rollingupdatedaemonset-v1-app
33
+ let(:update_strategy) { { type: 'RollingUpdate', rollingUpdate: rolling_update } }
34
+ let(:rolling_update) { { maxUnavailable: max_unavailable } }
35
+ let(:max_unavailable) { 1 }
36
+
37
+ # Minimum number of seconds for which a newly created pod should be ready without
38
+ # any of its container crashing, for it to be considered available.
39
+ # Defaults to 0 (pod will be considered available as soon as it is ready)
40
+ let(:min_ready_seconds) { 0 }
41
+
42
+ # The number of old ReplicaSets to retain to allow rollback. This is a pointer to
43
+ # distinguish between explicit zero and not specified.
44
+ let(:revision_history_limit) { nil }
45
+
46
+ let(:primary_container_path) { ['spec', 'template', 'spec', 'containers', 0] }
47
+
48
+ ### Helpers
49
+
50
+ ### Matsuri Registry
51
+ class << self
52
+ def load_path
53
+ Matsuri::Config.daemon_sets_path
54
+ end
55
+
56
+ def definition_module_name
57
+ 'DaemonSets'
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end