kube_cluster 1.5.0 → 1.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e6e0a31873f5a706d272e0ff37b06a8f3565b0e8d8629b92915f24c572825d1
4
- data.tar.gz: '08b20a4bb972d5b52eddd66577c79462a59611c8f84e692d092720c09bced0da'
3
+ metadata.gz: c8a1a110a7fc51055c3b0b6c7bed9c90ef99a7f5d3270aada0f9b89b7fba5eb0
4
+ data.tar.gz: 07a7eb71bc8a0d9e9197fb24a76729764de4e237c68110fc1a486d0df38eec74
5
5
  SHA512:
6
- metadata.gz: 676e83947ff878d10e2ff3e46e9dd880120e856f480e5ed9b26d27046f7d901d07d5d044d0a3358fa99c927768f4c0c54e8af1e13fa5ef96b5806caeefa476d2
7
- data.tar.gz: e8ade4344a74dd80bfaf8abec1bb43c5b0531b1130ab0c8584c4a0edb08e0a8d339dead8c35012cf517847d19cf066516717af9937890b70df744611f0809d5a
6
+ metadata.gz: 8bc59ca6435b4f4b9096bb7925605211289ee6a337015fc3eba64d747d673adce333a391585b5235c961f5a681f23474588d8391d88c12e51af5c5d5026fed06
7
+ data.tar.gz: 73c07cf7c0c85c0f998dc2b406da0d385502d24e414194eeb5f2e2dfbecbef3bedcef8401678e73a0b53665bc1d62465a6cd9ef77b962e07c761ecdca09b8620
data/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.6.0] - 2026-08-03
9
+
10
+ ### Added
11
+ - `Standard::JuiceFS::Sync` — a wrapper for the juicefs-operator's `Sync` CRD
12
+ (pinned to `juicefs.io/v1`). `from`/`to` are the CRD's sink hashes, passed
13
+ through as-is. Documents two operator behaviours worth knowing before use: the
14
+ unquoted metadata-URL export that breaks `juicefsCE` sinks whose metaurl
15
+ contains `&`, and the `ttlSecondsAfterFinished` deletion that makes a
16
+ reconciler-managed Sync re-run forever.
17
+ - `Standard::JuiceFS::PersistentVolume` — a statically-provisioned JuiceFS PV,
18
+ the only way to give a volume an exact, readable directory name (`subPath`)
19
+ rather than the `pvc-<uuid>` a StorageClass would assign.
20
+ - `PersistentVolume` pinned to `v1/PersistentVolume` in the resolve table.
21
+
22
+ ### Changed
23
+ - Requires kube_schema `~> 1.10.0`, which ships the `juicefs.io/v1` schemas.
24
+ These wrappers previously needed a local CRD-registration shim that downloaded
25
+ `dist/crd.yaml` at load time; that is no longer necessary.
26
+
8
27
  ## [1.4.0] - 2026-07-13
9
28
 
10
29
  ### Added
data/Gemfile.lock CHANGED
@@ -1,10 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kube_cluster (1.5.0)
4
+ kube_cluster (1.6.0)
5
5
  activesupport (~> 8.0)
6
6
  kube_kubectl (~> 2.0)
7
- kube_schema (~> 1.9.2)
7
+ kube_schema (~> 1.10.0)
8
8
 
9
9
  GEM
10
10
  remote: https://rubygems.org/
@@ -132,7 +132,7 @@ GEM
132
132
  rubyshell (~> 1.5)
133
133
  shellwords (~> 0.2.2)
134
134
  string_builder (~> 1.2.2)
135
- kube_schema (1.9.2)
135
+ kube_schema (1.10.0)
136
136
  json_schemer (~> 2.5.0)
137
137
  rubyshell (~> 1.5.0)
138
138
  lefthook (2.1.10)
data/kube_cluster.gemspec CHANGED
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
32
32
  spec.add_development_dependency "scampi", "~> 1.0"
33
33
  spec.add_development_dependency "lefthook", "~> 2.1"
34
34
 
35
- spec.add_dependency "kube_schema", "~> 1.9.2"
35
+ spec.add_dependency "kube_schema", "~> 1.10.0"
36
36
  spec.add_dependency "kube_kubectl", "~> 2.0"
37
37
  spec.add_dependency "activesupport", "~> 8.0"
38
38
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # A statically-provisioned JuiceFS PersistentVolume.
4
+ #
5
+ # Dynamic provisioning (a `juicefs-sc` StorageClass) names each volume's
6
+ # directory `pvc-<uuid>` at the root of the filesystem. Static provisioning lets
7
+ # the directory be named explicitly via the `subPath` volume attribute — the
8
+ # only way to get an exact, readable folder name.
9
+ #
10
+ # `secret`/`secret_namespace` point at the JuiceFS "volume credentials" Secret
11
+ # (name / metaurl / storage / bucket / keys / encrypt_rsa_key). PersistentVolume
12
+ # is cluster-scoped and nodePublishSecretRef carries its own namespace, so the
13
+ # PV, its PVC, and that Secret may all live in different namespaces. The mount
14
+ # pod always runs in the CSI driver's namespace regardless.
15
+ #
16
+ # `storage` is a label only — JuiceFS enforces no quota through this field.
17
+
18
+ require "bundler/setup"
19
+ require "kube/cluster"
20
+
21
+ module Kube
22
+ module Cluster
23
+ module Standard
24
+ module JuiceFS
25
+ class PersistentVolume < Kube::Cluster["PersistentVolume"]
26
+ def initialize(name:, storage:, sub_path:, secret:, secret_namespace:,
27
+ access_modes: ["ReadWriteMany"], reclaim_policy: "Retain", &block)
28
+ super() {
29
+ metadata.name = name
30
+
31
+ spec.capacity = { storage: storage }
32
+ spec.accessModes = access_modes
33
+ spec.persistentVolumeReclaimPolicy = reclaim_policy
34
+ # Empty (not absent) so no StorageClass can claim this PV and the
35
+ # matching PVC binds by volumeName alone.
36
+ spec.storageClassName = ""
37
+ spec.csi = {
38
+ driver: "csi.juicefs.com",
39
+ volumeHandle: name,
40
+ fsType: "juicefs",
41
+ nodePublishSecretRef: { name: secret, namespace: secret_namespace },
42
+ volumeAttributes: { subPath: sub_path },
43
+ }
44
+
45
+ instance_exec(&block) if block
46
+ }
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ __END__
55
+
56
+ describe "JuiceFS::PersistentVolume" do
57
+ pv = Kube::Cluster::Standard::JuiceFS::PersistentVolume
58
+
59
+ volume = pv.new(
60
+ name: "media",
61
+ storage: "1Ti",
62
+ sub_path: "media",
63
+ secret: "juicefs-secret",
64
+ secret_namespace: "kube-system",
65
+ )
66
+
67
+ it "mounts the named subPath through the JuiceFS CSI driver" do
68
+ yaml = volume.to_yaml
69
+
70
+ yaml.include?("driver: csi.juicefs.com").should == true
71
+ yaml.include?("subPath: media").should == true
72
+ end
73
+
74
+ it "defaults to a retained, ReadWriteMany volume with no StorageClass" do
75
+ yaml = volume.to_yaml
76
+
77
+ yaml.include?("persistentVolumeReclaimPolicy: Retain").should == true
78
+ yaml.include?("ReadWriteMany").should == true
79
+ yaml.include?("storageClassName: ''").should == true
80
+ end
81
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # juicefs.io/v1 Sync — a one-shot `juicefs sync` job run by the juicefs-operator
4
+ # (https://juicefs.com/docs/csi/guide/juicefs-operator/).
5
+ #
6
+ # The operator builds a manager pod (plus `replicas - 1` worker pods) from
7
+ # `image`, parses each sink into a URI, and runs
8
+ # `juicefs sync [--<options>] <from> <to>`.
9
+ #
10
+ # `from`/`to` are the CRD's sink hashes and are passed through as-is — they are
11
+ # the meaningful configuration, so wrapping them in a mini-DSL would only hide
12
+ # the CRD. Exactly one of these keys per sink:
13
+ #
14
+ # external: { uri:, accessKey:, secretKey:, extraVolumes: } any object store,
15
+ # a `file://` path, or a mounted PVC/hostPath
16
+ # juicefs: { volumeName:, token:, ... } enterprise edition
17
+ # juicefsCE: { metaURL:, metaPassWord:, path: } community edition
18
+ #
19
+ # NOTE ON juicefsCE: the operator emits its metadata-URL export UNQUOTED
20
+ # (pkg/utils/sync.go, `export %s=%s`). A metaurl containing a literal `&` (e.g.
21
+ # `?sslmode=require&search_path=juicefs`) is split by the pod's `sh -c`
22
+ # entrypoint — the export runs in a background subshell and the variable is
23
+ # empty in the shell that runs the sync. Use an `external` sink with a `pvc`
24
+ # extraVolume instead, and let the CSI mount pod do the JuiceFS work.
25
+ #
26
+ # NOTE ON ttlSecondsAfterFinished: DO NOT SET IT when the Sync is managed by a
27
+ # reconciler such as Flux or Argo CD. The controller DELETES the Sync object
28
+ # when the TTL expires (internal/controller/sync_controller.go); the reconciler
29
+ # then sees drift, recreates it, and the sync runs again — forever. Without a
30
+ # TTL the object simply stays `Completed`. To re-run, delete the object by hand
31
+ # or switch to a CronSync.
32
+
33
+ require "bundler/setup"
34
+ require "kube/cluster"
35
+
36
+ module Kube
37
+ module Cluster
38
+ module Standard
39
+ module JuiceFS
40
+ class Sync < Kube::Cluster["juicefs.io/v1/Sync"]
41
+ def initialize(name:, image:, from:, to:, replicas: 1, options: [], &block)
42
+ super() {
43
+ metadata.name = name
44
+ spec.image = image
45
+ spec.replicas = replicas
46
+ spec.options = options if options.any?
47
+ spec.from = from
48
+ spec.to = to
49
+
50
+ instance_exec(&block) if block
51
+ }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ __END__
60
+
61
+ describe "JuiceFS::Sync" do
62
+ sync = Kube::Cluster::Standard::JuiceFS::Sync
63
+
64
+ it "emits the juicefs.io/v1 apiVersion" do
65
+ sync
66
+ .new(
67
+ name: "media-backup",
68
+ image: "juicedata/mount:latest",
69
+ from: { external: { uri: "file:///mnt/media/" } },
70
+ to: { external: { uri: "s3://backup.example.com/media/" } },
71
+ )
72
+ .to_yaml
73
+ .include?("apiVersion: juicefs.io/v1")
74
+ .should == true
75
+ end
76
+
77
+ it "omits options when none are given" do
78
+ sync
79
+ .new(
80
+ name: "plain",
81
+ image: "juicedata/mount:latest",
82
+ from: { external: { uri: "file:///a/" } },
83
+ to: { external: { uri: "file:///b/" } },
84
+ )
85
+ .to_yaml
86
+ .include?("options:")
87
+ .should == false
88
+ end
89
+
90
+ it "carries options and replicas through" do
91
+ yaml = sync.new(
92
+ name: "parallel",
93
+ image: "juicedata/mount:latest",
94
+ from: { external: { uri: "file:///a/" } },
95
+ to: { external: { uri: "file:///b/" } },
96
+ replicas: 3,
97
+ options: ["--update"],
98
+ ).to_yaml
99
+
100
+ yaml.include?("replicas: 3").should == true
101
+ yaml.include?("--update").should == true
102
+ end
103
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Kube
4
4
  module Cluster
5
- VERSION = "1.5.0"
5
+ VERSION = "1.6.0"
6
6
  end
7
7
  end
data/lib/kube/cluster.rb CHANGED
@@ -92,6 +92,7 @@ require "kube/cluster/resource/persistence"
92
92
  Kube::Cluster.config do
93
93
  # Built-in Kubernetes kinds.
94
94
  resolve "ConfigMap", to: "v1/ConfigMap"
95
+ resolve "PersistentVolume", to: "v1/PersistentVolume"
95
96
  resolve "PersistentVolumeClaim", to: "v1/PersistentVolumeClaim"
96
97
  resolve "Secret", to: "v1/Secret"
97
98
  resolve "Service", to: "v1/Service"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kube_cluster
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan K
@@ -57,14 +57,14 @@ dependencies:
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: 1.9.2
60
+ version: 1.10.0
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: 1.9.2
67
+ version: 1.10.0
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: kube_kubectl
70
70
  requirement: !ruby/object:Gem::Requirement
@@ -231,6 +231,8 @@ files:
231
231
  - lib/kube/cluster/standard/gateway_api/http_route.rb
232
232
  - lib/kube/cluster/standard/job.rb
233
233
  - lib/kube/cluster/standard/job_with_service_account.rb
234
+ - lib/kube/cluster/standard/juice_fs/persistent_volume.rb
235
+ - lib/kube/cluster/standard/juice_fs/sync.rb
234
236
  - lib/kube/cluster/standard/kube_virt/virtual_machine.rb
235
237
  - lib/kube/cluster/standard/meta_controller/composite_controller.rb
236
238
  - lib/kube/cluster/standard/meta_controller/decorator_controller.rb