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,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
|