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 +4 -4
- data/CHANGELOG.md +19 -0
- data/Gemfile.lock +3 -3
- data/kube_cluster.gemspec +1 -1
- data/lib/kube/cluster/standard/juice_fs/persistent_volume.rb +81 -0
- data/lib/kube/cluster/standard/juice_fs/sync.rb +103 -0
- data/lib/kube/cluster/version.rb +1 -1
- data/lib/kube/cluster.rb +1 -0
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c8a1a110a7fc51055c3b0b6c7bed9c90ef99a7f5d3270aada0f9b89b7fba5eb0
|
|
4
|
+
data.tar.gz: 07a7eb71bc8a0d9e9197fb24a76729764de4e237c68110fc1a486d0df38eec74
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
4
|
+
kube_cluster (1.6.0)
|
|
5
5
|
activesupport (~> 8.0)
|
|
6
6
|
kube_kubectl (~> 2.0)
|
|
7
|
-
kube_schema (~> 1.
|
|
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.
|
|
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.
|
|
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
|
data/lib/kube/cluster/version.rb
CHANGED
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.
|
|
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.
|
|
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.
|
|
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
|