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,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matsuri
4
+ module Transform
5
+ # Combinators are Element -> Element: they take a transform step and
6
+ # return a transform step, so selectivity lives in the wrapper and the
7
+ # wrapped element stays total. They are machinery, not presenters —
8
+ # never registry-named; names cross the declaration boundary, machinery
9
+ # does not.
10
+ module Combinators
11
+ # Applies its element only to manifests meeting the criteria —
12
+ # identity otherwise. Criteria are the conjunction of the declared
13
+ # {kind:, name:, namespace:} constraints, the same addressing grammar
14
+ # import lookup uses; an undeclared key constrains nothing, an unknown
15
+ # key is an error.
16
+ class Match
17
+ CRITERIA_KEYS = %w[kind name namespace].freeze
18
+
19
+ attr_reader :criteria, :element
20
+
21
+ def initialize(criteria, element)
22
+ @criteria = Map.new(criteria)
23
+ unknown = @criteria.keys - CRITERIA_KEYS
24
+ raise ArgumentError, "unknown match criteria #{unknown.inspect}; use #{CRITERIA_KEYS.join(', ')}" if unknown.any?
25
+
26
+ @element = Callable.new([element])
27
+ end
28
+
29
+ def call(manifest)
30
+ return manifest unless matches?(manifest)
31
+
32
+ element.call(manifest)
33
+ end
34
+
35
+ def matches?(manifest)
36
+ m = Map === manifest ? manifest : Map.new(manifest)
37
+ (criteria[:kind].nil? || m.get(:kind) == criteria[:kind]) &&
38
+ (criteria[:name].nil? || m.get(:metadata, :name) == criteria[:name]) &&
39
+ (criteria[:namespace].nil? || m.get(:metadata, :namespace) == criteria[:namespace])
40
+ end
41
+ end
42
+
43
+ # The declaration-surface vocabulary. Sugars grow on demand.
44
+ module Helpers
45
+ def match(criteria, element)
46
+ Match.new(criteria, element)
47
+ end
48
+
49
+ def match_kind(kind, element)
50
+ Match.new({ kind: kind }, element)
51
+ end
52
+
53
+ def match_deployments(element)
54
+ match_kind('Deployment', element)
55
+ end
56
+ end
57
+
58
+ extend Helpers
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matsuri
4
+ module Transform
5
+ # The array <-> map isomorphism for container env entries. Manifests
6
+ # carry env as an ordered array of {name:, value:|valueFrom:} entries;
7
+ # the map side exists so entries can be updated by key. The round trip
8
+ # preserves order, appends new keys at the end, and compacts duplicate
9
+ # names (last write wins), so it is not a 1-to-1 mapping.
10
+ module Env
11
+ extend self
12
+
13
+ def update_kv(kv, key, value)
14
+ updated_kv = env_to_map(kv).update(key, value)
15
+ expand_env(updated_kv)
16
+ end
17
+
18
+ def env_to_map(kv)
19
+ kv.inject(Map.new) do |acc, entry|
20
+ case
21
+ when entry.key?('value')
22
+ acc[entry['name']] = entry['value']
23
+ when entry.key?('valueFrom')
24
+ acc[entry['name']] = entry['valueFrom']
25
+ else
26
+ nil
27
+ end
28
+ acc
29
+ end
30
+ end
31
+
32
+ def expand_env(hash)
33
+ hash.map do |k, v|
34
+ if v.is_a?(Hash)
35
+ { name: k, valueFrom: v }
36
+ else
37
+ { name: k, value: v.to_s }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matsuri
4
+ module Transform
5
+ # The Kubernetes dialect: Algebra + Env ops addressed to the manifest
6
+ # shapes Kubernetes actually uses — container lists, env entries,
7
+ # labels, volumes, node affinity. Well-known paths are constants so a
8
+ # misspelled path cannot exist at a call site.
9
+ module Kubernetes
10
+ include Algebra
11
+ include Env
12
+ extend self
13
+
14
+ NODE_SELECTOR_TERMS_PATH = %i[
15
+ affinity
16
+ nodeAffinity
17
+ requiredDuringSchedulingIgnoredDuringExecution
18
+ nodeSelectorTerms
19
+ ].freeze
20
+
21
+ # The pod spec within a pod-template workload or a bare pod manifest.
22
+ def pod_spec_path(manifest)
23
+ manifest.get(:spec, :template, :spec) ? %i[spec template spec] : %i[spec]
24
+ end
25
+
26
+ # Set the env variable of a container, defaults to container 0
27
+ def set_env(spec, key, value, container: 0)
28
+ path = [:containers, container, :env]
29
+ spec.set(path, update_kv(spec.get(path), key, value))
30
+ end
31
+
32
+ # Set the env variables for all containers
33
+ def set_all_env(spec, key, value)
34
+ size = spec.get(:containers).length
35
+ if size > 0 then
36
+ (0..size - 1).inject(spec) do |spec, i|
37
+ set_env(spec, key, value, container: i)
38
+ end
39
+ else
40
+ # If there are no containers, there are other things wrong with the manifest.
41
+ # However, return the same spec to satisify the zero-point property of the transform
42
+ spec
43
+ end
44
+ end
45
+
46
+ def set_resources(spec, resources_spec, container_name:)
47
+ container_num = index_by_name!(spec.get(:containers), container_name)
48
+ spec.set([:containers, container_num, :resources], resources_spec)
49
+ end
50
+
51
+ # Curried form of set_resources: names the container and the resource
52
+ # spec up front, returns the spec -> spec step.
53
+ def with_resources(name, resource_spec)
54
+ ->(spec) { set_resources(spec, resource_spec, container_name: name) }
55
+ end
56
+
57
+ # Set a label
58
+ def set_label(metadata, key, value)
59
+ metadata.set(:labels, key, value)
60
+ end
61
+
62
+ # Append a volume mount to a container, defaults to container 0
63
+ def append_volume_mount(spec, volume_mount_spec, container: 0)
64
+ append_to(spec, [:containers, container, :volumeMounts], volume_mount_spec)
65
+ end
66
+
67
+ # Append a volume
68
+ def append_volume(spec, volume_spec)
69
+ append_to(spec, [:volumes], volume_spec)
70
+ end
71
+
72
+ def without_container(spec, name:)
73
+ update_in(spec, [:containers]) { |x| except_entry_by_name(x, name: name) }
74
+ end
75
+
76
+ # Helper for updating node selector terms
77
+ def update_in_node_selector_terms(spec, &f)
78
+ update_in(spec, NODE_SELECTOR_TERMS_PATH, &f)
79
+ end
80
+
81
+ # Helper for updating node affinity terms
82
+ def append_node_affinity(spec, match_expressions)
83
+ update_in(spec, NODE_SELECTOR_TERMS_PATH) do |terms|
84
+ terms + [match_expressions]
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matsuri
4
+ # Pure manifest transformation strata. Nothing under this namespace shells
5
+ # out or touches a cluster; everything operates on plain data (Map / Hash /
6
+ # Array) and composes as values.
7
+ module Transform
8
+ autoload :Algebra, 'matsuri/transform/algebra'
9
+ autoload :Callable, 'matsuri/transform/callable'
10
+ autoload :Env, 'matsuri/transform/env'
11
+ autoload :Kubernetes, 'matsuri/transform/kubernetes'
12
+ autoload :Combinators, 'matsuri/transform/combinators'
13
+
14
+ # Raised by strict by-name lookups when no entry matches.
15
+ class EntryNotFound < StandardError; end
16
+
17
+ # Raised by path-addressed ops when nothing exists at the path.
18
+ class PathNotFound < StandardError; end
19
+ end
20
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Matsuri
2
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
3
5
  end
data/lib/matsuri.rb CHANGED
@@ -1,5 +1,161 @@
1
- require "matsuri/version"
1
+ # frozen_string_literal: true
2
+
3
+ require 'matsuri/core_ext'
2
4
 
3
5
  module Matsuri
4
- # Your code goes here...
6
+ # Raised when a declared hole — a let() with no sensible default — is read
7
+ # before a definition fills it. Inherits NoMethodError: the definition has
8
+ # no working version of that let. The raise lands in the code that expected
9
+ # the value, so the implementer can inspect that context and see what the
10
+ # definition needs.
11
+ class UndefinedLet < NoMethodError; end
12
+
13
+ autoload :Initializer, 'matsuri/initializer'
14
+ autoload :Config, 'matsuri/config'
15
+ autoload :Platform, 'matsuri/platform'
16
+ autoload :ShellOut, 'matsuri/shell_out'
17
+ autoload :Registry, 'matsuri/registry'
18
+
19
+ autoload :Task, 'matsuri/task'
20
+ autoload :Cmd, 'matsuri/cmd'
21
+ autoload :Stack, 'matsuri/stack'
22
+ autoload :Acceptor, 'matsuri/acceptor'
23
+ autoload :Presenter, 'matsuri/presenter'
24
+ autoload :Transform, 'matsuri/transform'
25
+
26
+ module Acceptors
27
+ autoload :ImageTag, 'matsuri/acceptors/image_tag'
28
+ end
29
+
30
+ module Presenters
31
+ autoload :SeedImage, 'matsuri/presenters/seed_image'
32
+ autoload :SeedReplicas, 'matsuri/presenters/seed_replicas'
33
+ autoload :SeedResources, 'matsuri/presenters/seed_resources'
34
+ autoload :PruneEmpty, 'matsuri/presenters/prune_empty'
35
+ autoload :Helpers, 'matsuri/presenters/helpers'
36
+ end
37
+
38
+ module Kubernetes
39
+ autoload :Base, 'matsuri/kubernetes/base'
40
+
41
+ # Core
42
+ autoload :Pod, 'matsuri/kubernetes/pod'
43
+ autoload :Service, 'matsuri/kubernetes/service'
44
+ autoload :Endpoints, 'matsuri/kubernetes/endpoints'
45
+ autoload :Ingress, 'matsuri/kubernetes/ingress'
46
+ autoload :Secret, 'matsuri/kubernetes/secret'
47
+ autoload :ConfigMap, 'matsuri/kubernetes/config_map'
48
+
49
+ # Apps
50
+ autoload :ReplicaSet, 'matsuri/kubernetes/replica_set'
51
+ autoload :StatefulSet, 'matsuri/kubernetes/stateful_set'
52
+ autoload :DaemonSet, 'matsuri/kubernetes/daemon_set'
53
+ autoload :Deployment, 'matsuri/kubernetes/deployment'
54
+ autoload :CronJob, 'matsuri/kubernetes/cron_job'
55
+
56
+ autoload :HorizontalPodAutoscaler, 'matsuri/kubernetes/horizontal_pod_autoscaler'
57
+
58
+ # Persistent Storage
59
+ autoload :PersistentVolume, 'matsuri/kubernetes/persistent_volume'
60
+ autoload :PersistentVolumeClaim, 'matsuri/kubernetes/persistent_volume_claim'
61
+ autoload :StorageClass, 'matsuri/kubernetes/storage_class'
62
+
63
+ # Other
64
+ autoload :ImportedManifest, 'matsuri/kubernetes/imported_manifest'
65
+ end
66
+
67
+ module Cmds
68
+ autoload :Cli, 'matsuri/cmds/cli'
69
+ autoload :Kubectl, 'matsuri/cmds/kubectl'
70
+ autoload :Show, 'matsuri/cmds/show'
71
+ autoload :Diff, 'matsuri/cmds/diff'
72
+ autoload :Status, 'matsuri/cmds/status'
73
+ autoload :Create, 'matsuri/cmds/create'
74
+ autoload :Delete, 'matsuri/cmds/delete'
75
+ autoload :Apply, 'matsuri/cmds/apply'
76
+ autoload :Recreate, 'matsuri/cmds/recreate'
77
+ autoload :Scale, 'matsuri/cmds/scale'
78
+
79
+ autoload :Generate, 'matsuri/cmds/generate'
80
+ end
81
+
82
+ module Tasks
83
+ autoload :Kubectl, 'matsuri/tasks/kubectl'
84
+ autoload :KubectlOps, 'matsuri/tasks/kubectl_ops'
85
+ autoload :Stacks, 'matsuri/tasks/stacks'
86
+ autoload :HashDiff, 'matsuri/tasks/hash_diff'
87
+ autoload :Docker, 'matsuri/tasks/docker'
88
+ autoload :Pod, 'matsuri/tasks/pod'
89
+ autoload :Cluster, 'matsuri/tasks/cluster'
90
+ end
91
+
92
+ module Concerns
93
+ autoload :ImportManifest, 'matsuri/concerns/import_manifest'
94
+ autoload :ExportManifest, 'matsuri/concerns/export_manifest'
95
+ autoload :Diffable, 'matsuri/concerns/diffable'
96
+
97
+ autoload :Awaiting, 'matsuri/concerns/awaiting'
98
+ autoload :RegistryHelpers, 'matsuri/concerns/registry_helpers'
99
+ autoload :PodTemplate, 'matsuri/concerns/pod_template'
100
+
101
+ module Pods
102
+ autoload :SpreadPods, 'matsuri/concerns/pods/spread_pods'
103
+ autoload :SpreadTopology, 'matsuri/concerns/pods/spread_topology'
104
+ end
105
+ end
106
+
107
+ module DSL
108
+ module Cluster
109
+ autoload :Scope, 'matsuri/dsl/cluster/scope'
110
+
111
+ autoload :Role, 'matsuri/dsl/cluster/role'
112
+ autoload :ClusterRole, 'matsuri/dsl/cluster/cluster_role'
113
+ autoload :AggregatedClusterRole, 'matsuri/dsl/cluster/aggregated_cluster_role'
114
+ autoload :Binding, 'matsuri/dsl/cluster/binding'
115
+
116
+ autoload :ServiceAccount, 'matsuri/dsl/cluster/service_account'
117
+ end
118
+
119
+ module Concerns
120
+ autoload :Metadata, 'matsuri/dsl/concerns/metadata'
121
+ autoload :RbacRules, 'matsuri/dsl/concerns/rbac_rules'
122
+ autoload :DefaultBinding, 'matsuri/dsl/concerns/default_binding'
123
+ autoload :ManifestSet, 'matsuri/dsl/concerns/manifest_set'
124
+ end
125
+ end
126
+
127
+ def self.define(type, name, inherits: nil, &blk)
128
+ Matsuri::Registry.define(type, name, inherits: inherits, &blk)
129
+ end
130
+
131
+ def self.environment
132
+ Matsuri::Config.environment
133
+ end
134
+
135
+ def self.dev?
136
+ Matsuri::Config.environment == 'dev'
137
+ end
138
+
139
+ def self.staging?
140
+ Matsuri::Config.environment == 'staging'
141
+ end
142
+
143
+ def self.production?
144
+ Matsuri::Config.environment == 'production'
145
+ end
146
+
147
+ def self.log(level, message = nil, &blk)
148
+ case level
149
+ when :fatal then
150
+ log_output! message, &blk
151
+ exit(1)
152
+ when :error, :warn then log_output! message, &blk
153
+ when :info then log_output! message, &blk if Matsuri::Config.verbose
154
+ when :debug then log_output! message, &blk if Matsuri::Config.debug
155
+ end
156
+ end
157
+
158
+ def self.log_output!(message)
159
+ puts(message || yield)
160
+ end
5
161
  end
data/matsuri.gemspec CHANGED
@@ -1,4 +1,5 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'matsuri/version'
@@ -7,18 +8,30 @@ Gem::Specification.new do |spec|
7
8
  spec.name = "matsuri"
8
9
  spec.version = Matsuri::VERSION
9
10
  spec.authors = ["Ho-Sheng Hsiao"]
10
- spec.email = ["hosh@shopapps.co"]
11
+ spec.email = ["ho-sheng.hsiao@solace.health"]
11
12
 
12
13
  spec.summary = %q{Build a dev environment using Docker and Kubernetes}
13
14
  spec.description = %q{Framework and toolkit to build a dev environment using Docker and Kubernetes}
14
- spec.homepage = "https://github.com/shopappsio/matsuri"
15
+ spec.homepage = "https://github.com/matsuri-rb/matsuri"
15
16
 
16
17
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- spec.bindir = "exe"
18
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.bindir = "bin"
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
20
  spec.require_paths = ["lib"]
20
21
 
21
- spec.add_development_dependency "bundler", "~> 1.10"
22
- spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.required_ruby_version = ">= 3.2"
23
+
24
+ spec.add_dependency 'base64'
25
+ spec.add_dependency 'thor'
26
+ spec.add_dependency 'mixlib-shellout'
27
+ spec.add_dependency 'mixlib-config'
28
+ spec.add_dependency 'rlet', '~> 0.7.0'
29
+ spec.add_dependency 'map'
30
+ spec.add_dependency 'hashdiff'
31
+ spec.add_dependency 'rainbow'
32
+ spec.add_dependency 'unit', '~> 0.5.0'
33
+
34
+ spec.add_development_dependency "bundler", ">= 2.4"
35
+ spec.add_development_dependency "rake", "~> 13.0"
23
36
  spec.add_development_dependency "rspec"
24
37
  end
@@ -0,0 +1,27 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # Starts the matsuri test cluster under its own profile. --keep-context
5
+ # leaves the user's kubectl context alone; scripts address the cluster
6
+ # with --context matsuri. Pass minikube flags through, e.g.
7
+ # bin/minikube-start.sh --kubernetes-version=v1.34.0.
8
+ # kubectl and minikube dial the VM over the local network: on macOS, run
9
+ # from a Local Network-granted context (not tmux).
10
+ PROFILE="${PROFILE:-matsuri}"
11
+
12
+ # vfkit is the darwin driver; the flake only ships it there.
13
+ case "$(uname -s)" in
14
+ Darwin) DRIVER="${DRIVER:-vfkit}" ;;
15
+ *) DRIVER="${DRIVER:-podman}" ;;
16
+ esac
17
+
18
+ minikube start \
19
+ -p "${PROFILE}" \
20
+ --driver="${DRIVER}" \
21
+ --keep-context \
22
+ "$@"
23
+
24
+ # The example repo's namespaces precede anything matsuri converges;
25
+ # they are raw manifests, applied outside matsuri.
26
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ kubectl --context "${PROFILE}" apply -f "${SCRIPT_DIR}/../example/platform/namespaces"
@@ -0,0 +1,46 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # Probes the mechanics the seed model rests on: a create WITHOUT
5
+ # --save-config (the kubectl default) keeps seeded fields out of
6
+ # last-applied, so ongoing applies of a manifest that omits them never
7
+ # prune them, and another controller (an HPA) takes ownership
8
+ # uncontested.
9
+ #
10
+ # Sequence:
11
+ # 1. create from the seeded manifest (replicas: 3), no --save-config
12
+ # 2. apply the declared manifest (no replicas) -> replicas must stay 3
13
+ # 3. scale to 5 (stands in for the HPA taking ownership)
14
+ # 4. apply the declared manifest again -> replicas must stay 5
15
+ #
16
+ # Start the cluster with bin/minikube-start.sh first. kubectl dials the
17
+ # VM: on macOS run from a Local Network-granted context (not tmux).
18
+
19
+ TESTS_DIR="$(cd "$(dirname "$0")/.." && pwd)"
20
+ CONTEXT="${CONTEXT:-matsuri}"
21
+ K8S="${TESTS_DIR}/k8s/save-config"
22
+ KUBECTL=(kubectl --context "${CONTEXT}" --namespace matsuri-probes)
23
+
24
+ fail() { echo "FAIL: $*" >&2; exit 1; }
25
+
26
+ replicas() {
27
+ "${KUBECTL[@]}" get deployment save-config-probe -o jsonpath='{.spec.replicas}'
28
+ }
29
+
30
+ kubectl --context "${CONTEXT}" apply -f "${K8S}/namespace.yaml"
31
+ "${KUBECTL[@]}" delete deployment save-config-probe --ignore-not-found --wait
32
+
33
+ "${KUBECTL[@]}" create -f "${K8S}/deployment-seeded.yaml"
34
+ [ "$(replicas)" = "3" ] || fail "create did not land the seed (replicas=$(replicas))"
35
+
36
+ "${KUBECTL[@]}" apply -f "${K8S}/deployment-declared.yaml"
37
+ [ "$(replicas)" = "3" ] || fail "first apply pruned the seeded replicas (replicas=$(replicas))"
38
+
39
+ "${KUBECTL[@]}" scale deployment save-config-probe --replicas=5
40
+ "${KUBECTL[@]}" apply -f "${K8S}/deployment-declared.yaml"
41
+ [ "$(replicas)" = "5" ] || fail "apply clobbered controller-owned replicas (replicas=$(replicas))"
42
+
43
+ echo "last-applied for the record (replicas must be absent):"
44
+ "${KUBECTL[@]}" apply view-last-applied deployment save-config-probe
45
+
46
+ echo "PASS: seeded create + ongoing applies leave replicas controller-owned"