├── deploy ├── docker-compose │ ├── ganesha.conf.d │ │ ├── local.conf │ │ └── exports.conf │ ├── docker-compose.local.yml │ ├── docker-compose.test.yml │ ├── README.md │ ├── Makefile │ └── docker-compose.yml ├── podman │ ├── .gitignore │ ├── README.md │ ├── Makefile │ └── hack │ │ └── mkmanifest.sh └── kubernetes │ ├── .gitignore │ ├── base │ ├── kustomization.yml │ └── configmap.yml │ ├── overlays │ ├── local │ │ └── image-pull-policy-patch.yml │ └── docker │ │ └── kustomization.yml │ ├── hack │ ├── mkservice.sh │ ├── mkkustomization.sh │ └── mkstatefulset.sh │ ├── README.md │ └── Makefile ├── images ├── rpcbind │ ├── entrypoint.sh │ ├── healthcheck.sh │ └── README.md ├── dbus-daemon │ ├── entrypoint.sh │ ├── healthcheck.sh │ └── README.md ├── rpc.statd │ ├── healthcheck.sh │ ├── README.md │ └── entrypoint.sh └── nfs-ganesha │ ├── healthcheck.sh │ ├── ganesha.conf.sh │ ├── entrypoint.sh │ └── README.md ├── test ├── go.mod ├── go.sum ├── Dockerfile ├── status_test.go ├── nlockmgr_test.go ├── mountd_test.go ├── rquotad_test.go ├── portmapper_test.go ├── main_test.go ├── utils.go └── nfs_test.go ├── ganesha-config-reload ├── package_test.go ├── watchmode.go ├── README.md ├── watchmode_test.go ├── signalhandler.go ├── signalhandler_test.go ├── reloader_test.go ├── watcher.go ├── reloader.go ├── Dockerfile ├── main.go ├── watcher_test.go ├── go.mod └── go.sum ├── include.mk ├── .github ├── dependabot.yml └── workflows │ ├── dependabot.yml │ └── cicd.yaml ├── .env ├── README.md ├── Makefile ├── Dockerfile └── LICENSE /deploy/docker-compose/ganesha.conf.d/local.conf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deploy/podman/.gitignore: -------------------------------------------------------------------------------- 1 | contained-ganesha.yml 2 | -------------------------------------------------------------------------------- /deploy/kubernetes/.gitignore: -------------------------------------------------------------------------------- 1 | base/service.yml 2 | base/service-headless.yml 3 | base/statefulset.yml 4 | overlays/local/kustomization.yml 5 | -------------------------------------------------------------------------------- /images/rpcbind/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xue -o pipefail 4 | 5 | /usr/bin/systemd-tmpfiles --create /usr/lib/tmpfiles.d/rpcbind.conf 6 | ulimit -n 1024 7 | 8 | exec /usr/bin/rpcbind "$@" -w -f 9 | -------------------------------------------------------------------------------- /deploy/kubernetes/base/kustomization.yml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - configmap.yml 5 | - service.yml 6 | - service-headless.yml 7 | - statefulset.yml 8 | -------------------------------------------------------------------------------- /deploy/docker-compose/ganesha.conf.d/exports.conf: -------------------------------------------------------------------------------- 1 | EXPORT 2 | { 3 | Export_ID=1; 4 | Path = "/mem"; 5 | Pseudo = "/mem"; 6 | Access_Type = RW; 7 | FSAL { 8 | Name = MEM; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/NicolasT/contained-ganesha/test 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 // indirect 7 | github.com/vmware/go-nfs-client v0.0.0-20190605212624-d43b92724c1b 8 | ) 9 | -------------------------------------------------------------------------------- /ganesha-config-reload/package_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestGaneshaConfigReload(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "ganesha-config-reload") 13 | } 14 | -------------------------------------------------------------------------------- /images/dbus-daemon/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xue -o pipefail 4 | 5 | /usr/bin/install --directory --owner=root --group=root --mode=0755 /run/dbus 6 | /usr/bin/systemd-tmpfiles --create /usr/lib/tmpfiles.d/dbus.conf 7 | 8 | /usr/bin/dbus-uuidgen --ensure 9 | 10 | exec /usr/bin/dbus-daemon --nofork --nopidfile --nosyslog "$@" 11 | -------------------------------------------------------------------------------- /include.mk: -------------------------------------------------------------------------------- 1 | AWK ?= awk 2 | GREP ?= grep 3 | PRINTF ?= printf 4 | SORT ?= sort 5 | 6 | ENV_FILE = $(TOP_SRCDIR)/.env 7 | 8 | PROJECT_NAME = contained-ganesha 9 | 10 | help: 11 | @$(PRINTF) "\033[94m%s\n%s\n\033[0m" "Targets" "-------" 12 | @$(GREP) '^[a-zA-Z]' Makefile | \ 13 | $(AWK) -F ':.*?## ' 'NF==2 {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}' 14 | .PHONY: help 15 | -------------------------------------------------------------------------------- /images/rpc.statd/healthcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ue -o pipefail 4 | 5 | TRANSPORT=tcp 6 | HOST=127.0.0.1 7 | PROGRAM=100024 8 | VERSION=1 9 | 10 | ulimit -n 1024 11 | 12 | exec /usr/bin/timeout \ 13 | --kill-after=1s \ 14 | 8s \ 15 | /usr/bin/rpcinfo \ 16 | -T ${TRANSPORT} \ 17 | ${HOST} \ 18 | ${PROGRAM} \ 19 | ${VERSION} 20 | -------------------------------------------------------------------------------- /images/rpcbind/healthcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ue -o pipefail 4 | 5 | TRANSPORT=tcp 6 | HOST=127.0.0.1 7 | PROGRAM=100000 8 | VERSION=4 9 | 10 | ulimit -n 1024 11 | 12 | exec /usr/bin/timeout \ 13 | --kill-after=1s \ 14 | 8s \ 15 | /usr/bin/rpcinfo \ 16 | -T ${TRANSPORT} \ 17 | ${HOST} \ 18 | ${PROGRAM} \ 19 | ${VERSION} 20 | -------------------------------------------------------------------------------- /images/nfs-ganesha/healthcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ue -o pipefail 4 | 5 | TRANSPORT=tcp 6 | HOST=127.0.0.1 7 | PROGRAM=100003 8 | VERSION=4 9 | 10 | ulimit -n 1024 11 | 12 | exec /usr/bin/timeout \ 13 | --kill-after=1s \ 14 | 8s \ 15 | /usr/bin/rpcinfo \ 16 | -T ${TRANSPORT} \ 17 | ${HOST} \ 18 | ${PROGRAM} \ 19 | ${VERSION} 20 | -------------------------------------------------------------------------------- /images/nfs-ganesha/ganesha.conf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ue -o pipefail 4 | 5 | cat << EOF 6 | NFS_Core_Param 7 | { 8 | NLM_Port = ${NLOCKMGR_PORT}; 9 | Rquota_Port = ${RQUOTAD_PORT}; 10 | NFS_Port = ${NFS_PORT}; 11 | MNT_Port = ${MOUNTD_PORT}; 12 | 13 | mount_path_pseudo = true; 14 | } 15 | 16 | %include /etc/ganesha/ganesha.conf.d/local.conf 17 | %include /etc/ganesha/ganesha.conf.d/exports.conf 18 | EOF 19 | -------------------------------------------------------------------------------- /deploy/kubernetes/base/configmap.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: nfs-ganesha 5 | labels: 6 | app: contained-ganesha 7 | component: nfs-ganesha 8 | data: 9 | local.conf: '' 10 | exports.conf: | 11 | EXPORT 12 | { 13 | Export_ID=1; 14 | Path = "/mem"; 15 | Pseudo = "/mem"; 16 | Access_Type = RW; 17 | FSAL { 18 | Name = MEM; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /images/dbus-daemon/healthcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ue -o pipefail 4 | 5 | exec /usr/bin/timeout \ 6 | --kill-after=1s \ 7 | 8s \ 8 | /usr/bin/dbus-send \ 9 | --system \ 10 | --type=method_call \ 11 | --print-reply \ 12 | --reply-timeout=7500 \ 13 | --dest=org.freedesktop.DBus \ 14 | /org/freedesktop/DBus \ 15 | org.freedesktop.DBus.GetId 16 | -------------------------------------------------------------------------------- /test/go.sum: -------------------------------------------------------------------------------- 1 | github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 h1:UVArwN/wkKjMVhh2EQGC0tEc1+FqiLlvYXY5mQ2f8Wg= 2 | github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93/go.mod h1:Nfe4efndBz4TibWycNE+lqyJZiMX4ycx+QKV8Ta0f/o= 3 | github.com/vmware/go-nfs-client v0.0.0-20190605212624-d43b92724c1b h1:RUrsc0B9xF8iC8WXrva+ULeOwN/X+zqe0FdWcDxPt/M= 4 | github.com/vmware/go-nfs-client v0.0.0-20190605212624-d43b92724c1b/go.mod h1:psQdhrCc+fimC/8/U+PboPiIMcdmKgRdAtcMnhXhjzI= 5 | -------------------------------------------------------------------------------- /deploy/docker-compose/docker-compose.local.yml: -------------------------------------------------------------------------------- 1 | services: 2 | rpcbind: 3 | image: ${LOCAL_RPCBIND_IMAGE}:${LOCAL_RPCBIND_TAG} 4 | 5 | rpc.statd: 6 | image: ${LOCAL_RPC_STATD_IMAGE}:${LOCAL_RPC_STATD_TAG} 7 | 8 | dbus-daemon: 9 | image: ${LOCAL_DBUS_DAEMON_IMAGE}:${LOCAL_DBUS_DAEMON_TAG} 10 | 11 | nfs-ganesha: 12 | image: ${LOCAL_NFS_GANESHA_IMAGE}:${LOCAL_NFS_GANESHA_TAG} 13 | 14 | ganesha-config-reload: 15 | image: ${LOCAL_GANESHA_CONFIG_RELOAD_IMAGE}:${LOCAL_GANESHA_CONFIG_RELOAD_TAG} 16 | -------------------------------------------------------------------------------- /deploy/kubernetes/overlays/local/image-pull-policy-patch.yml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /spec/template/spec/containers/0/imagePullPolicy 3 | value: Never 4 | - op: add 5 | path: /spec/template/spec/containers/1/imagePullPolicy 6 | value: Never 7 | - op: add 8 | path: /spec/template/spec/containers/2/imagePullPolicy 9 | value: Never 10 | - op: add 11 | path: /spec/template/spec/containers/3/imagePullPolicy 12 | value: Never 13 | - op: add 14 | path: /spec/template/spec/containers/4/imagePullPolicy 15 | value: Never 16 | -------------------------------------------------------------------------------- /ganesha-config-reload/watchmode.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "flag" 4 | 5 | type watchMode string 6 | 7 | const ( 8 | WatchModeConfigMap watchMode = "configmap" 9 | WatchModeFile watchMode = "file" 10 | ) 11 | 12 | func (w *watchMode) String() string { 13 | return string(*w) 14 | } 15 | 16 | func (w *watchMode) Set(s string) error { 17 | switch s { 18 | case string(WatchModeConfigMap): 19 | *w = WatchModeConfigMap 20 | return nil 21 | case string(WatchModeFile): 22 | *w = WatchModeFile 23 | return nil 24 | default: 25 | return flag.ErrHelp 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/golang:1-alpine as build 2 | 3 | WORKDIR ${GOPATH}/src/contained-ganesha-test 4 | 5 | ENV GO111MODULES=on 6 | 7 | COPY go.mod go.sum . 8 | RUN go mod download 9 | 10 | COPY . . 11 | 12 | ENV CGO_ENABLED=0 13 | ENV GOOS=${GOOS:-linux} 14 | ENV GOARCH=${GOARCH:-amd64} 15 | RUN go test -v -c -o bin/contained-ganesha-test ./... 16 | 17 | FROM scratch as contained-ganesha-test 18 | COPY --from=build /go/src/contained-ganesha-test/bin/contained-ganesha-test /contained-ganesha-test 19 | 20 | ENTRYPOINT ["/contained-ganesha-test"] 21 | CMD ["-test.v"] 22 | -------------------------------------------------------------------------------- /images/rpcbind/README.md: -------------------------------------------------------------------------------- 1 | rpcbind 2 | ======= 3 | This container image will run the [rpcbind](http://git.linux-nfs.org/?p=steved/rpcbind.git;a=summary) *portmapper* daemon. 4 | 5 | Volumes 6 | ------- 7 | | Path | Type | Description | 8 | | ---- | ---- | ----------- | 9 | | */run* | tmpfs | Directory in which `rpcbind.sock` will be created | 10 | 11 | Ports 12 | ----- 13 | | Port | Protocol | Description | 14 | | ---- | -------- | ----------- | 15 | | 111 | TCP | *portmapper* service | 16 | | 111 | UDP | *portmapper* service | 17 | | */run/rpcbind.sock* | unix | *portmapper* service | 18 | -------------------------------------------------------------------------------- /deploy/docker-compose/docker-compose.test.yml: -------------------------------------------------------------------------------- 1 | services: 2 | sut: 3 | image: ${LOCAL_CONTAINED_GANESHA_TEST_IMAGE}:${LOCAL_CONTAINED_GANESHA_TEST_TAG} 4 | command: 5 | - -test.v 6 | - -host=pod 7 | - -portmapper-port=${PORTMAPPER_PORT} 8 | - -status-port=${STATUS_PORT} 9 | - -nlockmgr-port=${NLOCKMGR_PORT} 10 | - -rquotad-port=${RQUOTAD_PORT} 11 | - -nfs-port=${NFS_PORT} 12 | - -mountd-port=${MOUNTD_PORT} 13 | read_only: true 14 | cap_drop: 15 | - ALL 16 | cap_add: 17 | - NET_BIND_SERVICE 18 | labels: 19 | app: contained-ganesha 20 | component: sut 21 | depends_on: 22 | - rpcbind 23 | - rpc.statd 24 | - dbus-daemon 25 | - nfs-ganesha 26 | networks: 27 | - default 28 | -------------------------------------------------------------------------------- /ganesha-config-reload/README.md: -------------------------------------------------------------------------------- 1 | ganesha-config-reload 2 | ===================== 3 | The `ganesha-config-reload` sidecar detects configuration file updates and 4 | sends the config reload signal (`SIGHUP`) to the NFS-Ganesha process to 5 | reload its settings. 6 | 7 | Volumes 8 | ------- 9 | | Path | Type | Description | 10 | | ---- | ---- | ----------- | 11 | | */run/ganesha/ganesha.pid* | bind | NFS-Ganesha PID-file | 12 | | */etc/ganesha/ganesha.conf.d* | bind | NFS-Ganesha configuration files (monitored in Kubernetes ConfigMap mode) | 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | - package-ecosystem: docker 9 | directory: "/" 10 | schedule: 11 | interval: daily 12 | - package-ecosystem: docker 13 | directory: "test/" 14 | schedule: 15 | interval: daily 16 | - package-ecosystem: gomod 17 | directory: "test/" 18 | schedule: 19 | interval: daily 20 | allow: 21 | - dependency-type: "all" 22 | - package-ecosystem: docker 23 | directory: "ganesha-config-reload/" 24 | schedule: 25 | interval: daily 26 | - package-ecosystem: gomod 27 | directory: "ganesha-config-reload/" 28 | schedule: 29 | interval: daily 30 | allow: 31 | - dependency-type: "all" 32 | -------------------------------------------------------------------------------- /images/dbus-daemon/README.md: -------------------------------------------------------------------------------- 1 | dbus-daemon 2 | =========== 3 | This container image will run a [DBus](https://freedesktop.org/wiki/Software/dbus) daemon. 4 | The default `CMD` will run a systemwide message bus. 5 | 6 | Volumes 7 | ------- 8 | | Path | Type | Description | 9 | | ---- | ---- | ----------- | 10 | | */run/dbus* | tmpfs | Directory in which the bus socket will be created | 11 | | */var/lib/dbus* | persistent | Directory in which the generated machine UUID will be stored | 12 | 13 | Ports 14 | ----- 15 | | Port | Protocol | Description | 16 | | ---- | -------- | ----------- | 17 | | */run/dbus/system_bus_socket* | unix | DBus system bus socket | 18 | -------------------------------------------------------------------------------- /deploy/kubernetes/hack/mkservice.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ue -o pipefail 4 | 5 | source $1 6 | NAME=$2 7 | CLUSTERIP=$3 8 | 9 | cat << EOF 10 | apiVersion: v1 11 | kind: Service 12 | metadata: 13 | name: ${NAME} 14 | labels: 15 | app: contained-ganesha 16 | component: nfs-ganesha 17 | spec: 18 | selector: 19 | app: contained-ganesha 20 | component: nfs-ganesha 21 | ${CLUSTERIP} 22 | sessionAffinity: ClientIP 23 | ports: 24 | EOF 25 | 26 | for port in portmapper status nlockmgr rquotad nfs mountd; do 27 | PORT_UPPER=$(echo $port | tr a-z A-Z) 28 | PORT_VAR=$(printf "%s_PORT" $PORT_UPPER) 29 | PORT_NUM="${!PORT_VAR}" 30 | for proto in tcp udp; do 31 | cat << EOF 32 | - name: ${port}-${proto} 33 | port: ${PORT_NUM} 34 | protocol: $(echo $proto | tr a-z A-Z) 35 | targetPort: ${port}-${proto} 36 | EOF 37 | done 38 | done 39 | -------------------------------------------------------------------------------- /test/status_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/vmware/go-nfs-client/nfs/rpc" 9 | ) 10 | 11 | func TestStatus(t *testing.T) { 12 | ctx, cancel := context.WithTimeout(context.Background(), Timeout) 13 | defer cancel() 14 | 15 | err := WaitForSocket(ctx, "tcp", fmt.Sprintf("%s:%d", Host, PortmapperPort)) 16 | if err != nil { 17 | t.Fatalf("Portmap service not available: %v", err) 18 | } 19 | err = WaitForSocket(ctx, "tcp", fmt.Sprintf("%s:%d", Host, StatusPort)) 20 | if err != nil { 21 | t.Fatalf("Status service not available: %v", err) 22 | } 23 | 24 | tests := []Mapping{ 25 | { 26 | Name: "v1/TCP", 27 | Prot: rpc.IPProtoTCP, 28 | Vers: 1, 29 | }, 30 | { 31 | Name: "v1/UDP", 32 | Prot: rpc.IPProtoUDP, 33 | Vers: 1, 34 | }, 35 | } 36 | 37 | PortmapTestHelper(t, Host, 100024, tests, StatusPort) 38 | } 39 | -------------------------------------------------------------------------------- /test/nlockmgr_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/vmware/go-nfs-client/nfs/rpc" 9 | ) 10 | 11 | func TestNlockmgr(t *testing.T) { 12 | ctx, cancel := context.WithTimeout(context.Background(), Timeout) 13 | defer cancel() 14 | 15 | err := WaitForSocket(ctx, "tcp", fmt.Sprintf("%s:%d", Host, PortmapperPort)) 16 | if err != nil { 17 | t.Fatalf("Portmap service not available: %v", err) 18 | } 19 | err = WaitForSocket(ctx, "tcp", fmt.Sprintf("%s:%d", Host, NlockmgrPort)) 20 | if err != nil { 21 | t.Fatalf("Nlockmgr service not available: %v", err) 22 | } 23 | 24 | tests := []Mapping{ 25 | { 26 | Name: "v4/TCP", 27 | Prot: rpc.IPProtoTCP, 28 | Vers: 4, 29 | }, 30 | { 31 | Name: "v4/UDP", 32 | Prot: rpc.IPProtoUDP, 33 | Vers: 4, 34 | }, 35 | } 36 | 37 | PortmapTestHelper(t, Host, 100021, tests, NlockmgrPort) 38 | } 39 | -------------------------------------------------------------------------------- /deploy/kubernetes/overlays/docker/kustomization.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | 5 | bases: 6 | - ../../base 7 | 8 | images: 9 | - name: docker.pkg.github.com/nicolast/contained-ganesha/rpcbind 10 | newName: docker.io/nicolast/contained-ganesha-rpcbind 11 | newTag: latest 12 | - name: docker.pkg.github.com/nicolast/contained-ganesha/rpc.statd 13 | newName: docker.io/nicolast/contained-ganesha-rpc.statd 14 | newTag: latest 15 | - name: docker.pkg.github.com/nicolast/contained-ganesha/dbus-daemon 16 | newName: docker.io/nicolast/contained-ganesha-dbus-daemon 17 | newTag: latest 18 | - name: docker.pkg.github.com/nicolast/contained-ganesha/nfs-ganesha 19 | newName: docker.io/nicolast/contained-ganesha-nfs-ganesha 20 | newTag: latest 21 | - name: docker.pkg.github.com/nicolast/contained-ganesha/ganesha-config-reload 22 | newName: docker.io/nicolast/contained-ganesha-ganesha-config-reload 23 | newTag: latest 24 | -------------------------------------------------------------------------------- /deploy/kubernetes/hack/mkkustomization.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ue -o pipefail 4 | 5 | source $1 6 | 7 | cat << EOF 8 | apiVersion: kustomize.config.k8s.io/v1beta1 9 | kind: Kustomization 10 | 11 | resources: 12 | - ../../base 13 | 14 | images: 15 | - name: ${RPCBIND_IMAGE} 16 | newName: ${LOCAL_RPCBIND_IMAGE} 17 | newTag: ${LOCAL_RPCBIND_TAG} 18 | - name: ${RPC_STATD_IMAGE} 19 | newName: ${LOCAL_RPC_STATD_IMAGE} 20 | newTag: ${LOCAL_RPC_STATD_TAG} 21 | - name: ${DBUS_DAEMON_IMAGE} 22 | newName: ${LOCAL_DBUS_DAEMON_IMAGE} 23 | newTag: ${LOCAL_DBUS_DAEMON_TAG} 24 | - name: ${NFS_GANESHA_IMAGE} 25 | newName: ${LOCAL_NFS_GANESHA_IMAGE} 26 | newTag: ${LOCAL_NFS_GANESHA_TAG} 27 | - name: ${GANESHA_CONFIG_RELOAD_IMAGE} 28 | newName: ${LOCAL_GANESHA_CONFIG_RELOAD_IMAGE} 29 | newTag: ${LOCAL_GANESHA_CONFIG_RELOAD_TAG} 30 | 31 | patches: 32 | - path: image-pull-policy-patch.yml 33 | target: 34 | group: apps 35 | version: v1 36 | kind: StatefulSet 37 | name: nfs-ganesha 38 | EOF 39 | -------------------------------------------------------------------------------- /images/rpc.statd/README.md: -------------------------------------------------------------------------------- 1 | rpc.statd 2 | --------- 3 | This container image will run the [rpc.statd](http://git.linux-nfs.org/?p=steved/nfs-utils.git;a=summary) *NLM status* daemon. 4 | 5 | Volumes 6 | ------- 7 | | Path | Type | Description | 8 | | ---- | ---- | ----------- | 9 | | */run* | bind | Directory where `rpcbind.sock` resides (see *rpcbind* image) | 10 | | */var/lib/nfs* | persistent | Service state directory | 11 | 12 | Ports 13 | ----- 14 | | Port | Protocol | Description | 15 | | ---- | -------- | ----------- | 16 | | 865 | TCP | *status* service | 17 | | 865 | UDP | *status* service | 18 | 19 | Environment 20 | ----------- 21 | | Name | Default | Description | 22 | | ---- | ------- | ----------- | 23 | | *STATUS_PORT* | 865 | Port for the *status* service to bind to | 24 | -------------------------------------------------------------------------------- /test/mountd_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/vmware/go-nfs-client/nfs/rpc" 9 | ) 10 | 11 | func TestMountd(t *testing.T) { 12 | ctx, cancel := context.WithTimeout(context.Background(), Timeout) 13 | defer cancel() 14 | 15 | err := WaitForSocket(ctx, "tcp", fmt.Sprintf("%s:%d", Host, PortmapperPort)) 16 | if err != nil { 17 | t.Fatalf("Portmap service not available: %v", err) 18 | } 19 | err = WaitForSocket(ctx, "tcp", fmt.Sprintf("%s:%d", Host, MountdPort)) 20 | if err != nil { 21 | t.Fatalf("Mountd service not available: %v", err) 22 | } 23 | 24 | tests := []Mapping{ 25 | { 26 | Name: "v1/TCP", 27 | Prot: rpc.IPProtoTCP, 28 | Vers: 1, 29 | }, 30 | { 31 | Name: "v1/UDP", 32 | Prot: rpc.IPProtoUDP, 33 | Vers: 1, 34 | }, 35 | { 36 | Name: "v3/TCP", 37 | Prot: rpc.IPProtoTCP, 38 | Vers: 3, 39 | }, 40 | { 41 | Name: "v3/UDP", 42 | Prot: rpc.IPProtoUDP, 43 | Vers: 3, 44 | }, 45 | } 46 | 47 | PortmapTestHelper(t, Host, 100005, tests, MountdPort) 48 | } 49 | -------------------------------------------------------------------------------- /test/rquotad_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/vmware/go-nfs-client/nfs/rpc" 9 | ) 10 | 11 | func TestRquotad(t *testing.T) { 12 | ctx, cancel := context.WithTimeout(context.Background(), Timeout) 13 | defer cancel() 14 | 15 | err := WaitForSocket(ctx, "tcp", fmt.Sprintf("%s:%d", Host, PortmapperPort)) 16 | if err != nil { 17 | t.Fatalf("Portmap service not available: %v", err) 18 | } 19 | err = WaitForSocket(ctx, "tcp", fmt.Sprintf("%s:%d", Host, RquotadPort)) 20 | if err != nil { 21 | t.Fatalf("Rquotad service not available: %v", err) 22 | } 23 | 24 | tests := []Mapping{ 25 | { 26 | Name: "v1/TCP", 27 | Prot: rpc.IPProtoTCP, 28 | Vers: 1, 29 | }, 30 | { 31 | Name: "v1/UDP", 32 | Prot: rpc.IPProtoUDP, 33 | Vers: 1, 34 | }, 35 | { 36 | Name: "v2/TCP", 37 | Prot: rpc.IPProtoTCP, 38 | Vers: 2, 39 | }, 40 | { 41 | Name: "v2/UDP", 42 | Prot: rpc.IPProtoUDP, 43 | Vers: 2, 44 | }, 45 | } 46 | 47 | PortmapTestHelper(t, Host, 100011, tests, RquotadPort) 48 | } 49 | -------------------------------------------------------------------------------- /deploy/podman/README.md: -------------------------------------------------------------------------------- 1 | contained-ganesha on Podman 2 | =========================== 3 | Deploy `contained-ganesha` using [podman](https://podman.io), using `podman 4 | play kube`. 5 | 6 | Note: `podman play kube` won't work as-is, so wrappers using `make` are 7 | provided. 8 | 9 | Getting Started 10 | --------------- 11 | The easiest way to get started is to run `make up-local`, which will build the 12 | required container images locally, render the manifest file, then run the 13 | containers in a `podman` `Pod` using the local images. 14 | 15 | To render the manifest file only, run `make manifest`. 16 | 17 | The default configuration will export an in-memory filesystem (the `mem` FSAL) 18 | as `/mem`. 19 | 20 | Eventually, `make down` will bring down the environment like 21 | `podman play kube --down` does. 22 | 23 | Tests 24 | ----- 25 | Run `make check` to run a test-suite in a deployed environment. 26 | 27 | Design 28 | ------ 29 | This deployment runs all containers in a `Pod` and launches this using 30 | `podman play kube`. The `nfs-ganesha` configuration is deployed using 31 | a `ConfigMap`. 32 | -------------------------------------------------------------------------------- /test/portmapper_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/vmware/go-nfs-client/nfs/rpc" 9 | ) 10 | 11 | func TestPortmap(t *testing.T) { 12 | ctx, cancel := context.WithTimeout(context.Background(), Timeout) 13 | defer cancel() 14 | 15 | err := WaitForSocket(ctx, "tcp", fmt.Sprintf("%s:%d", Host, PortmapperPort)) 16 | if err != nil { 17 | t.Fatalf("Portmap service not available: %v", err) 18 | } 19 | 20 | tests := []Mapping{ 21 | { 22 | Name: "v2/TCP", 23 | Prot: rpc.IPProtoTCP, 24 | Vers: 2, 25 | }, 26 | { 27 | Name: "v2/UDP", 28 | Prot: rpc.IPProtoUDP, 29 | Vers: 2, 30 | }, 31 | { 32 | Name: "v3/TCP", 33 | Prot: rpc.IPProtoTCP, 34 | Vers: 3, 35 | }, 36 | { 37 | Name: "v3/UDP", 38 | Prot: rpc.IPProtoUDP, 39 | Vers: 3, 40 | }, 41 | { 42 | Name: "v4/TCP", 43 | Prot: rpc.IPProtoTCP, 44 | Vers: 4, 45 | }, 46 | { 47 | Name: "v4/UDP", 48 | Prot: rpc.IPProtoUDP, 49 | Vers: 4, 50 | }, 51 | } 52 | 53 | PortmapTestHelper(t, Host, rpc.PmapProg, tests, PortmapperPort) 54 | } 55 | -------------------------------------------------------------------------------- /ganesha-config-reload/watchmode_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("watchMode", func() { 9 | Context("when parsed as a flag", func() { 10 | It("should parse 'configmap' into WatchModeConfigMap", func() { 11 | var w watchMode 12 | Expect(w.Set("configmap")).To(Succeed()) 13 | Expect(w).To(Equal(WatchModeConfigMap)) 14 | }) 15 | 16 | It("should parse 'file' into WatchModeFile", func() { 17 | var w watchMode 18 | Expect(w.Set("file")).To(Succeed()) 19 | Expect(w).To(Equal(WatchModeFile)) 20 | }) 21 | 22 | It("should fail on other values", func() { 23 | var w watchMode 24 | Expect(w.Set("other")).NotTo(Succeed()) 25 | }) 26 | }) 27 | 28 | Context("when turned into a string", func() { 29 | It("turns WatchModeConfigMap into 'configmap'", func() { 30 | w := WatchModeConfigMap 31 | Expect(w.String()).To(Equal("configmap")) 32 | }) 33 | 34 | It("turns WatchModeFile into 'file'", func() { 35 | w := WatchModeFile 36 | Expect(w.String()).To(Equal("file")) 37 | }) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /images/rpc.statd/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xue -o pipefail 4 | 5 | NFS_LIBDIR=/var/lib/nfs 6 | NFS_USER=root 7 | NFS_GROUP=root 8 | STATD_LIBDIR=/var/lib/nfs/statd 9 | STATD_USER=rpcuser 10 | STATD_GROUP=rpcuser 11 | 12 | if test -n "${STATUS_PORT:-}"; then 13 | PORT_ARG="--port ${STATUS_PORT}" 14 | else 15 | PORT_ARG="" 16 | fi 17 | 18 | test -d /var/lib/nfs 19 | 20 | /usr/bin/install -v --directory --group=${STATD_GROUP} --mode 0700 --owner=${STATD_USER} ${STATD_LIBDIR} ${STATD_LIBDIR}/sm ${STATD_LIBDIR}/sm.bak 21 | 22 | TIMEOUT=10 23 | 24 | for i in `seq 1 $TIMEOUT`; do 25 | echo "Waiting for rpcbind to be up ($i/$TIMEOUT)..." 26 | set +e 27 | ( ulimit -n 1024 && exec /usr/bin/rpcinfo -T tcp 127.0.0.1 100000 4 ) 28 | result=$? 29 | set -e 30 | 31 | if [ $result -eq 0 ]; then 32 | echo "rpcbind listening, starting rpc.statd" 33 | ulimit -n 1024 34 | exec /usr/sbin/rpc.statd --no-syslog --foreground --state-directory-path ${STATD_LIBDIR} ${PORT_ARG} "$@" 35 | fi 36 | 37 | sleep 1 38 | done 39 | 40 | echo "Timeout while waiting for rpcbind to be up" > /dev/stderr 41 | exit 1 42 | -------------------------------------------------------------------------------- /ganesha-config-reload/signalhandler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | 8 | "github.com/go-logr/logr" 9 | ) 10 | 11 | type signalHandler struct { 12 | cancel context.CancelFunc 13 | log logr.Logger 14 | sigs []os.Signal 15 | c chan os.Signal 16 | } 17 | 18 | func NewSignalHandler(log logr.Logger, cancel context.CancelFunc, sigs ...os.Signal) (*signalHandler, error) { 19 | return &signalHandler{ 20 | cancel: cancel, 21 | log: log, 22 | sigs: sigs, 23 | c: make(chan os.Signal, 1), 24 | }, nil 25 | } 26 | 27 | func (s *signalHandler) Register() error { 28 | s.log.V(0).Info("Registering signal handler") 29 | signal.Notify(s.c, s.sigs...) 30 | return nil 31 | } 32 | 33 | func (s *signalHandler) Run(ctx context.Context) error { 34 | defer signal.Stop(s.c) 35 | 36 | s.log.V(1).Info("Waiting for events") 37 | 38 | select { 39 | case sig := <-s.c: 40 | s.log.V(0).Info("Received signal, canceling context", "signal", sig) 41 | s.cancel() 42 | case <-ctx.Done(): 43 | s.log.V(1).Info("Context done") 44 | } 45 | 46 | return nil 47 | } 48 | 49 | func (s *signalHandler) Close() { 50 | s.log.V(0).Info("Resetting signal handlers") 51 | signal.Reset(s.sigs...) 52 | } 53 | -------------------------------------------------------------------------------- /test/main_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | "github.com/vmware/go-nfs-client/nfs/util" 10 | ) 11 | 12 | var ( 13 | Host string 14 | PortmapperPort int 15 | StatusPort int 16 | NlockmgrPort int 17 | RquotadPort int 18 | NfsPort int 19 | MountdPort int 20 | 21 | timeoutS int 22 | Timeout time.Duration 23 | ) 24 | 25 | func init() { 26 | flag.StringVar(&Host, "host", "127.0.0.1", "host against which to run the tests") 27 | flag.IntVar(&PortmapperPort, "portmapper-port", 111, "port of the portmapper service") 28 | flag.IntVar(&StatusPort, "status-port", 865, "poprt of the status service") 29 | flag.IntVar(&NlockmgrPort, "nlockmgr-port", 866, "port of the nlockmgr service") 30 | flag.IntVar(&RquotadPort, "rquotad-port", 875, "port of the rquotad service") 31 | flag.IntVar(&NfsPort, "nfs-port", 2049, "port of the NFS service") 32 | flag.IntVar(&MountdPort, "mountd-port", 20048, "port of the mountd service") 33 | flag.IntVar(&timeoutS, "service-timeout", 120, "timeout to wait for services to appear") 34 | } 35 | 36 | func TestMain(m *testing.M) { 37 | flag.Parse() 38 | 39 | Timeout = time.Duration(timeoutS) * time.Second 40 | 41 | util.DefaultLogger.SetDebug(testing.Verbose()) 42 | 43 | os.Exit(m.Run()) 44 | } 45 | -------------------------------------------------------------------------------- /test/utils.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "testing" 7 | "time" 8 | 9 | "github.com/vmware/go-nfs-client/nfs/rpc" 10 | ) 11 | 12 | func WaitForSocket(ctx context.Context, proto string, address string) error { 13 | d := net.Dialer{} 14 | 15 | for { 16 | conn, err := d.DialContext(ctx, proto, address) 17 | if err != nil { 18 | ctxErr := ctx.Err() 19 | if ctxErr != nil { 20 | return err 21 | } 22 | 23 | time.Sleep(500 * time.Millisecond) 24 | continue 25 | } 26 | defer conn.Close() 27 | break 28 | } 29 | 30 | return nil 31 | } 32 | 33 | type Mapping struct { 34 | Name string 35 | Prot uint32 36 | Vers uint32 37 | } 38 | 39 | func PortmapTestHelper(t *testing.T, host string, prog uint32, mappings []Mapping, expectedPort int) { 40 | pm, err := rpc.DialPortmapper("tcp", host) 41 | if err != nil { 42 | t.Fatalf("Unable to dial portmapper service: %v", err) 43 | } 44 | 45 | for _, tc := range mappings { 46 | tc := tc 47 | t.Run(tc.Name, func(t *testing.T) { 48 | port, err := pm.Getport(rpc.Mapping{ 49 | Prog: prog, 50 | Vers: tc.Vers, 51 | Prot: tc.Prot, 52 | }) 53 | if err != nil { 54 | t.Fatalf("Unable to get port: %v", err) 55 | } 56 | 57 | if port != expectedPort { 58 | t.Errorf("Unexpected port, got %d, want %d", port, expectedPort) 59 | } 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.github/workflows/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Dependabot 3 | 4 | "on": 5 | pull_request_target: 6 | 7 | permissions: 8 | pull-requests: write 9 | contents: write 10 | 11 | jobs: 12 | dependabot-automerge: 13 | runs-on: "ubuntu-latest" 14 | if: "${{ github.actor == 'dependabot[bot]' }}" 15 | steps: 16 | - name: Fetch Dependabot metadata 17 | id: dependabot-metadata 18 | uses: dependabot/fetch-metadata@v2.4.0 19 | with: 20 | github-token: "${{ secrets.GITHUB_TOKEN }}" 21 | 22 | - name: Enable auto-merge for Dependabot PRs 23 | run: gh pr merge --auto --rebase "$PR_URL" 24 | env: 25 | PR_URL: "${{ github.event.pull_request.html_url }}" 26 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 27 | 28 | dependabot-approve: 29 | runs-on: "ubuntu-latest" 30 | if: "${{ github.actor == 'dependabot[bot]' }}" 31 | steps: 32 | - name: Fetch Dependabot metadata 33 | id: dependabot-metadata 34 | uses: dependabot/fetch-metadata@v2.4.0 35 | with: 36 | github-token: "${{ secrets.GITHUB_TOKEN }}" 37 | 38 | - name: Approve Dependabot PRs for patch version changes 39 | if: "${{ steps.dependabot-metadata.outputs.update-type == 'version-update:semver-patch' }}" 40 | run: gh pr review --approve "$PR_URL" 41 | env: 42 | PR_URL: "${{ github.event.pull_request.html_url }}" 43 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 44 | -------------------------------------------------------------------------------- /deploy/kubernetes/README.md: -------------------------------------------------------------------------------- 1 | contained-ganesha on Kubernetes 2 | =============================== 3 | Deploy `contained-ganesha` using [Kubernetes](https://kubernetes.io). 4 | 5 | Getting Started 6 | --------------- 7 | First, generate the Kubernetes manifest files by running `make manifests`. 8 | 9 | The manifests use [Kustomize](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/). 10 | To deploy the service in your cluster using remote images, run 11 | `kubectl apply -k ./base`. However, this is likely not to work, since pulling 12 | the default images requires authentication. Instead, publish them somewhere, 13 | then create a Kustomize 'overlay' to point at them. Use the `local` overlay 14 | for inspiration. 15 | 16 | Alternatively, if the local images are accessible from your cluster, run 17 | `make kubectl-local-apply` (and later, `make kubectl-local-delete`). 18 | 19 | The default configuration will export an in-memory filesystem (the `mem` FSAL) 20 | as `/mem`. The service is exposed using the `nfs-ganesha` *Service*. 21 | 22 | Using Kind 23 | ---------- 24 | When using a [Kind](https://kind.sigs.k8s.io/) cluster, run 25 | `make kind-load-docker-images` to build all container images and load them 26 | into your Kind cluster. Then, `make kubectl-local-apply` will work. 27 | 28 | Tests 29 | ----- 30 | Run `make check` to run a test-suite against a deployed environment. 31 | This implies the services must be previously deployed in your cluster. 32 | 33 | The tests require the `contained-ganesha-test` image to be available. 34 | Again, when using Kind, run `make kind-load-cgt-image` to build and load this 35 | test image. 36 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | BASE_IMAGE=docker.io/almalinux 2 | BASE_IMAGE_TAG=9.5-20250307 3 | 4 | POD_IMAGE=k8s.gcr.io/pause 5 | POD_TAG=3.10 6 | 7 | RPCBIND_IMAGE=docker.pkg.github.com/nicolast/contained-ganesha/rpcbind 8 | RPCBIND_TAG=latest 9 | 10 | LOCAL_RPCBIND_IMAGE=contained-ganesha/rpcbind 11 | LOCAL_RPCBIND_TAG=latest 12 | 13 | RPC_STATD_IMAGE=docker.pkg.github.com/nicolast/contained-ganesha/rpc.statd 14 | RPC_STATD_TAG=latest 15 | 16 | LOCAL_RPC_STATD_IMAGE=contained-ganesha/rpc.statd 17 | LOCAL_RPC_STATD_TAG=latest 18 | 19 | DBUS_DAEMON_IMAGE=docker.pkg.github.com/nicolast/contained-ganesha/dbus-daemon 20 | DBUS_DAEMON_TAG=latest 21 | 22 | LOCAL_DBUS_DAEMON_IMAGE=contained-ganesha/dbus-daemon 23 | LOCAL_DBUS_DAEMON_TAG=latest 24 | 25 | NFS_GANESHA_IMAGE=docker.pkg.github.com/nicolast/contained-ganesha/nfs-ganesha 26 | NFS_GANESHA_TAG=latest 27 | 28 | LOCAL_NFS_GANESHA_IMAGE=contained-ganesha/nfs-ganesha 29 | LOCAL_NFS_GANESHA_TAG=latest 30 | 31 | GANESHA_CONFIG_RELOAD_IMAGE=docker.pkg.github.com/nicolast/contained-ganesha/ganesha-config-reload 32 | GANESHA_CONFIG_RELOAD_TAG=latest 33 | 34 | LOCAL_GANESHA_CONFIG_RELOAD_IMAGE=contained-ganesha/ganesha-config-reload 35 | LOCAL_GANESHA_CONFIG_RELOAD_TAG=latest 36 | 37 | CONTAINED_GANESHA_TEST_IMAGE=docker.pkg.github.com/nicolast/contained-ganesha/contained-ganesha-test 38 | CONTAINED_GANESHA_TEST_TAG=latest 39 | 40 | LOCAL_CONTAINED_GANESHA_TEST_IMAGE=contained-ganesha/contained-ganesha-test 41 | LOCAL_CONTAINED_GANESHA_TEST_TAG=latest 42 | 43 | # Note: this one can't be changed 44 | PORTMAPPER_PORT=111 45 | STATUS_PORT=865 46 | NLOCKMGR_PORT=866 47 | RQUOTAD_PORT=875 48 | NFS_PORT=2049 49 | MOUNTD_PORT=20048 50 | -------------------------------------------------------------------------------- /deploy/docker-compose/README.md: -------------------------------------------------------------------------------- 1 | contained-ganesha on docker-compose 2 | =================================== 3 | Deploy `contained-ganesha` using [docker-compose](https://docs.docker.com/compose/). 4 | 5 | Note: `docker-compose up` won't work as-is, so wrappers using `make` are 6 | provided. Alternatively, setting the required environment variables or passing 7 | the path to a suitable `.env` file would work. 8 | 9 | Getting Started 10 | --------------- 11 | The easiest way to get started is to run `make up-local`, which will build the 12 | required container images locally, then run the containers using these images. 13 | 14 | To access the NFS server (e.g., using `rpcinfo` and `showmount` for initial 15 | testing), run `make show-ip`. 16 | 17 | The default configuration will export an in-memory filesystem (the `mem` FSAL) 18 | as `/mem`. 19 | 20 | Eventually, `make down` will bring down the environment like 21 | `docker-compose down` does. 22 | 23 | Tests 24 | ----- 25 | Run `make check` to run a test-suite in a deployed environment. 26 | 27 | Design 28 | ------ 29 | This deployment includes a `pod` container which runs the `pause` image as used 30 | in Kubernetes Pods. After start, this container is used as the keeper of a 31 | network namespace which is then shared (using Docker's 32 | `--network "service:pod"` feature) by all other containers (except for 33 | `dbus-daemon` which doesn't require network access), so all services appear to 34 | run on the same host, as is intended between `rpcbind`, `rpc.statd` and 35 | `nfs-ganesha`. 36 | 37 | The `dbus-daemon` and `nfs-ganesha` containers interact through a shared 38 | `tmpfs` volume mounted at `/run/dbus`. 39 | -------------------------------------------------------------------------------- /ganesha-config-reload/signalhandler_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | "sync" 8 | "syscall" 9 | 10 | "github.com/go-logr/logr" 11 | "github.com/go-logr/stdr" 12 | 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | 16 | . "github.com/NicolasT/contained-ganesha/ganesha-config-reload" 17 | ) 18 | 19 | var _ = Describe("signalHandler", func() { 20 | var ( 21 | logger logr.Logger 22 | ) 23 | 24 | BeforeEach(func() { 25 | logger = stdr.New(log.New(GinkgoWriter, "", log.LstdFlags)) 26 | }) 27 | 28 | Context("when handling a single signal", func() { 29 | It("cancels a context when signaled", func(done Done) { 30 | ctx, cancel := context.WithCancel(context.Background()) 31 | defer cancel() 32 | 33 | By("Setting up a signal handler") 34 | s, err := NewSignalHandler(logger, cancel, syscall.SIGUSR1) 35 | Expect(err).NotTo(HaveOccurred()) 36 | defer func() { 37 | // Ensure signal handlers are reset *before* signaling end-of-test 38 | s.Close() 39 | close(done) 40 | }() 41 | err = s.Register() 42 | Expect(err).NotTo(HaveOccurred()) 43 | 44 | By("Launching the signal handler") 45 | wg := &sync.WaitGroup{} 46 | wg.Add(1) 47 | go func() { 48 | defer GinkgoRecover() 49 | defer wg.Done() 50 | err := s.Run(ctx) 51 | if err != nil { 52 | logger.Error(err, "Error in signal handler") 53 | } 54 | }() 55 | 56 | By("Sending the signal") 57 | proc, err := os.FindProcess(os.Getpid()) 58 | Expect(err).NotTo(HaveOccurred()) 59 | err = proc.Signal(syscall.SIGUSR1) 60 | Expect(err).NotTo(HaveOccurred()) 61 | 62 | By("Waiting for the signal handler to return") 63 | wg.Wait() 64 | 65 | Expect(ctx.Done()).To(BeClosed()) 66 | }) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/nfs_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/vmware/go-nfs-client/nfs" 9 | "github.com/vmware/go-nfs-client/nfs/rpc" 10 | ) 11 | 12 | func TestNfs(t *testing.T) { 13 | ctx, cancel := context.WithTimeout(context.Background(), Timeout) 14 | defer cancel() 15 | 16 | err := WaitForSocket(ctx, "tcp", fmt.Sprintf("%s:%d", Host, PortmapperPort)) 17 | if err != nil { 18 | t.Fatalf("Portmap service not available: %v", err) 19 | } 20 | err = WaitForSocket(ctx, "tcp", fmt.Sprintf("%s:%d", Host, NfsPort)) 21 | if err != nil { 22 | t.Fatalf("NFS service not available: %v", err) 23 | } 24 | 25 | tests := []Mapping{ 26 | { 27 | Name: "v3/TCP", 28 | Prot: rpc.IPProtoTCP, 29 | Vers: 3, 30 | }, 31 | { 32 | Name: "v3/UDP", 33 | Prot: rpc.IPProtoUDP, 34 | Vers: 3, 35 | }, 36 | { 37 | Name: "v4/TCP", 38 | Prot: rpc.IPProtoTCP, 39 | Vers: 4, 40 | }, 41 | } 42 | 43 | PortmapTestHelper(t, Host, 100003, tests, NfsPort) 44 | 45 | t.Run("ops", testOps) 46 | } 47 | 48 | func testOps(t *testing.T) { 49 | ctx, cancel := context.WithTimeout(context.Background(), Timeout) 50 | defer cancel() 51 | 52 | err := WaitForSocket(ctx, "tcp", fmt.Sprintf("%s:%d", Host, MountdPort)) 53 | if err != nil { 54 | t.Fatalf("Mount service not available: %v", err) 55 | } 56 | 57 | mount, err := nfs.DialMount(Host) 58 | if err != nil { 59 | t.Fatalf("Unable to dial mount: %v", err) 60 | } 61 | defer mount.Close() 62 | 63 | auth := rpc.NewAuthUnix("testsuite", 1234, 1234) 64 | 65 | fs, err := mount.Mount("/mem", auth.Auth()) 66 | if err != nil { 67 | t.Fatalf("Unable to mount: %v", err) 68 | } 69 | defer fs.Close() 70 | 71 | _, err = fs.ReadDirPlus(".") 72 | if err != nil { 73 | t.Errorf("ReadDirPlus failed: %v", err) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ganesha-config-reload/reloader_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "os/signal" 10 | "sync" 11 | "syscall" 12 | 13 | "github.com/go-logr/logr" 14 | "github.com/go-logr/stdr" 15 | 16 | . "github.com/onsi/ginkgo" 17 | . "github.com/onsi/gomega" 18 | 19 | . "github.com/NicolasT/contained-ganesha/ganesha-config-reload" 20 | ) 21 | 22 | var _ = Describe("reloader", func() { 23 | var ( 24 | logger logr.Logger 25 | pidFile string 26 | ) 27 | 28 | BeforeEach(func() { 29 | logger = stdr.New(log.New(GinkgoWriter, "", log.LstdFlags)) 30 | 31 | fh, err := ioutil.TempFile("", "gcr-test.pid") 32 | Expect(err).NotTo(HaveOccurred()) 33 | pidFile = fh.Name() 34 | Expect(fh.Close()).To(Succeed()) 35 | 36 | data := []byte(fmt.Sprintf("%d\n", os.Getpid())) 37 | Expect(ioutil.WriteFile(pidFile, data, 0600)).To(Succeed()) 38 | }) 39 | 40 | AfterEach(func() { 41 | Expect(os.Remove(pidFile)).To(Succeed()) 42 | }) 43 | 44 | It("sends a signal upon trigger, eventually", func(done Done) { 45 | By("Setting up a signal handler") 46 | usr1 := syscall.SIGUSR1 47 | sig := make(chan os.Signal, 1) 48 | defer close(sig) 49 | 50 | signal.Notify(sig, usr1) 51 | defer func() { 52 | // Ensure signal handlers are reset *before* signaling 'done' 53 | signal.Stop(sig) 54 | signal.Reset(usr1) 55 | close(done) 56 | }() 57 | 58 | By("Creating a reloader") 59 | c := make(chan interface{}) 60 | defer close(c) 61 | r := NewReloader(logger, c, pidFile, usr1) 62 | defer r.Close() 63 | 64 | By("Running the reloader") 65 | ctx, cancel := context.WithCancel(context.Background()) 66 | defer cancel() 67 | 68 | wg := &sync.WaitGroup{} 69 | wg.Add(1) 70 | go func() { 71 | defer GinkgoRecover() 72 | defer wg.Done() 73 | Expect(r.Run(ctx)).To(Succeed()) 74 | }() 75 | 76 | By("Triggering a reload") 77 | c <- nil 78 | 79 | By("Waiting for a signal to be received") 80 | Eventually(sig, 1.5 /* Reloader dampens events to 1/s */).Should(Receive()) 81 | 82 | By("Waiting for everything to be cleaned up") 83 | cancel() 84 | wg.Wait() 85 | }, 2 /* Reloader dampens events to 1/s */) 86 | }) 87 | -------------------------------------------------------------------------------- /images/nfs-ganesha/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xue -o pipefail 4 | 5 | GANESHA_CONFIG_SCRIPT=/etc/ganesha/ganesha.conf.sh 6 | GANESHA_CONFIG=/run/ganesha/ganesha.conf 7 | 8 | /usr/bin/install -v --directory --group=root --mode 0755 --owner=root /run/ganesha 9 | 10 | rm -f ${GANESHA_CONFIG} 11 | if test -f /etc/ganesha/ganesha.conf; then 12 | cp /etc/ganesha/ganesha.conf ${GANESHA_CONFIG} 13 | else 14 | ${GANESHA_CONFIG_SCRIPT} > ${GANESHA_CONFIG} 15 | fi 16 | test -f ${GANESHA_CONFIG} 17 | 18 | TIMEOUT=10 19 | RPCBIND_UP=0 20 | RPC_STATD_UP=0 21 | DBUS_DAEMON_UP=0 22 | 23 | for i in `seq 1 $TIMEOUT`; do 24 | echo "Waiting for rpcbind to be up ($i/$TIMEOUT)..." 25 | set +e 26 | ( ulimit -n 1024 && exec /usr/sbin/rpcinfo -T tcp 127.0.0.1 100000 4 ) 27 | result=$? 28 | set -e 29 | 30 | if [ $result -eq 0 ]; then 31 | echo "rpcbind listening" 32 | RPCBIND_UP=1 33 | break 34 | fi 35 | 36 | sleep 1 37 | done 38 | 39 | if [ $RPCBIND_UP -ne 1 ]; then 40 | echo "Timeout while waiting for rpcbind to be up" > /dev/stderr 41 | exit 1 42 | fi 43 | 44 | for i in `seq 1 $TIMEOUT`; do 45 | echo "Waiting for rpc.statd to be up ($i/$TIMEOUT)..." 46 | set +e 47 | ( ulimit -n 1024 && exec /usr/sbin/rpcinfo -T tcp 127.0.0.1 100024 1 ) 48 | result=$? 49 | set -e 50 | 51 | if [ $result -eq 0 ]; then 52 | echo "rpc.statd listening" 53 | RPC_STATD_UP=1 54 | break 55 | fi 56 | 57 | sleep 1 58 | done 59 | 60 | if [ $RPC_STATD_UP -ne 1 ]; then 61 | echo "Timeout while waiting for rpc.statd to be up" > /dev/stderr 62 | exit 1 63 | fi 64 | 65 | for i in `seq 1 $TIMEOUT`; do 66 | echo "Waiting for dbus-daemon to be up ($i/$TIMEOUT)..." 67 | set +e 68 | test -S /run/dbus/system_bus_socket 69 | result=$? 70 | set -e 71 | 72 | if [ $result -eq 0 ]; then 73 | echo "dbus-daemon listening" 74 | DBUS_DAEMON_UP=1 75 | break 76 | fi 77 | 78 | sleep 1 79 | done 80 | 81 | if [ $DBUS_DAEMON_UP -ne 1 ]; then 82 | echo "Timeout while waiting for dbus-daemon to be up" > /dev/stderr 83 | exit 1 84 | fi 85 | 86 | exec /usr/bin/ganesha.nfsd -F -L /dev/stderr -f ${GANESHA_CONFIG} -p /run/ganesha/ganesha.pid "$@" 87 | -------------------------------------------------------------------------------- /deploy/docker-compose/Makefile: -------------------------------------------------------------------------------- 1 | DOCKER_COMPOSE ?= docker compose 2 | 3 | DOCKER_COMPOSE_YML = docker-compose.yml 4 | DOCKER_COMPOSE_LOCAL_YML = docker-compose.local.yml 5 | DOCKER_COMPOSE_TEST_YML = docker-compose.test.yml 6 | 7 | TOP_SRCDIR := ../.. 8 | 9 | TEST_SERVICE = sut 10 | 11 | default: help 12 | .PHONY: default 13 | 14 | include $(TOP_SRCDIR)/include.mk 15 | 16 | docker-compose = $(DOCKER_COMPOSE) \ 17 | --project-name $(PROJECT_NAME) \ 18 | --env-file $(ENV_FILE) \ 19 | $(foreach f,$^,--file $(f)) 20 | 21 | up: $(DOCKER_COMPOSE_YML) ## Run 'docker-compose up' using public images 22 | $(call docker-compose) up --no-build 23 | .PHONY: up 24 | 25 | up-local: $(DOCKER_COMPOSE_YML) $(DOCKER_COMPOSE_LOCAL_YML) ## Build container images, then run 'docker-compose up' using local images 26 | $(MAKE) -C $(TOP_SRCDIR) containers 27 | $(call docker-compose) up --no-build 28 | .PHONY: up-local 29 | 30 | down: $(DOCKER_COMPOSE_YML) ## Run 'docker-compose down' 31 | $(call docker-compose) down 32 | .PHONY: down 33 | 34 | ps: $(DOCKER_COMPOSE_YML) ## Run 'docker-compose ps' 35 | @$(call docker-compose) ps --all 36 | .PHONY: ps 37 | 38 | show-ip: $(DOCKER_COMPOSE_YML) ## Show the IP address of the NFS server 39 | @$(call docker-compose) \ 40 | exec -T rpcbind \ 41 | /usr/sbin/ip -4 addr show eth0 \ 42 | | $(GREP) -oP '(?<=inet\s)\d+(\.\d+){3}' || (echo "Failed to retrieve 'rpcbind' container address" > /dev/stderr; false) 43 | .PHONY: show-ip 44 | 45 | check: | containers container-contained-ganesha-test ## Build container images, then run 'docker-compose up' using local images and run a test-suite 46 | $(MAKE) check-nobuild 47 | .PHONY: check 48 | 49 | check-nobuild: $(DOCKER_COMPOSE_YML) $(DOCKER_COMPOSE_LOCAL_YML) $(DOCKER_COMPOSE_TEST_YML) ## Run 'docker-compose up' using local images and run a test suite. Like 'check' without building. 50 | $(call docker-compose) up --no-build --abort-on-container-exit --exit-code-from $(TEST_SERVICE) $(TEST_SERVICE) 51 | .PHONY: check-nobuild 52 | 53 | containers: 54 | $(MAKE) -C $(TOP_SRCDIR) containers 55 | .PHONY: containers 56 | 57 | container-contained-ganesha-test: 58 | $(MAKE) -C $(TOP_SRCDIR) container-contained-ganesha-test 59 | .PHONY: container-contained-ganesha-test 60 | -------------------------------------------------------------------------------- /ganesha-config-reload/watcher.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "path/filepath" 6 | 7 | "github.com/go-logr/logr" 8 | 9 | "github.com/fsnotify/fsnotify" 10 | ) 11 | 12 | type Checker = func(fsnotify.Event) (bool, error) 13 | 14 | type watcher struct { 15 | log logr.Logger 16 | w *fsnotify.Watcher 17 | paths []string 18 | Events chan interface{} 19 | check func(fsnotify.Event) (bool, error) 20 | } 21 | 22 | func NewWatcher(log logr.Logger, check Checker, paths []string) (*watcher, error) { 23 | w, err := fsnotify.NewWatcher() 24 | if err != nil { 25 | log.Error(err, "Unable to create watcher") 26 | return nil, err 27 | } 28 | 29 | return &watcher{ 30 | log: log, 31 | w: w, 32 | paths: paths, 33 | Events: make(chan interface{}), 34 | check: check, 35 | }, nil 36 | } 37 | 38 | func (w *watcher) Register() error { 39 | for _, path := range w.paths { 40 | log := w.log.WithValues("path", path) 41 | 42 | log.Info("Adding watch on path") 43 | err := w.w.Add(path) 44 | if err != nil { 45 | w.log.Error(err, "Unable to add path to watchlist") 46 | return err 47 | } 48 | } 49 | 50 | return nil 51 | } 52 | 53 | func (w *watcher) Run(ctx context.Context) error { 54 | for { 55 | w.log.V(1).Info("Waiting for events") 56 | 57 | select { 58 | case evt := <-w.w.Events: 59 | log := w.log.WithValues("path", evt.Name, "op", evt.Op) 60 | log.V(1).Info("Received event") 61 | isEvent, err := w.check(evt) 62 | if err != nil { 63 | log.Error(err, "Failed to check event") 64 | } else { 65 | if isEvent { 66 | log.Info("Detected config update") 67 | w.Events <- nil 68 | } 69 | } 70 | case err := <-w.w.Errors: 71 | w.log.Error(err, "Watcher error") 72 | case <-ctx.Done(): 73 | w.log.V(1).Info("Context done") 74 | return nil 75 | } 76 | } 77 | } 78 | 79 | func (w *watcher) Close() { 80 | w.log.V(1).Info("Closing events channel") 81 | close(w.Events) 82 | 83 | w.log.V(1).Info("Closing watcher") 84 | err := w.w.Close() 85 | if err != nil { 86 | w.log.Error(err, "Error while closing fswatcher") 87 | } 88 | } 89 | 90 | func ConfigMapChecker(evt fsnotify.Event) (bool, error) { 91 | return (filepath.Base(evt.Name) == "..data") && (evt.Op&fsnotify.Create != 0), nil 92 | } 93 | 94 | func FileChecker(evt fsnotify.Event) (bool, error) { 95 | return true, nil 96 | } 97 | -------------------------------------------------------------------------------- /deploy/podman/Makefile: -------------------------------------------------------------------------------- 1 | PODMAN ?= podman 2 | 3 | POD := nfs-ganesha 4 | 5 | TOP_SRCDIR := ../.. 6 | 7 | default: help 8 | .PHONY: default 9 | 10 | include $(TOP_SRCDIR)/include.mk 11 | include $(ENV_FILE) 12 | 13 | podman-play-kube = $(PODMAN) \ 14 | play \ 15 | kube \ 16 | --build=false \ 17 | --replace 18 | 19 | MANIFEST = contained-ganesha.yml 20 | 21 | manifest: $(MANIFEST) ## Render the manifest files 22 | .PHONY: manifest 23 | 24 | $(MANIFEST): $(ENV_FILE) hack/mkmanifest.sh 25 | hack/mkmanifest.sh $< > $@ || (rm -f $@; false) 26 | 27 | clean: ## Remove all generated files 28 | rm -f $(MANIFEST) 29 | .PHONY: clean 30 | 31 | up-local: $(MANIFEST) | containers ## Build container images, then run 'podman play kube' using local images. 32 | $(podman-play-kube) $< 33 | .PHONY: podman-play 34 | 35 | up-local-nobuild: $(MANIFEST) ## Run 'podman play kube' using local images. Like 'podman-play' without building. 36 | $(podman-play-kube) $< 37 | .PHONY: podman-play-nobuild 38 | 39 | down: $(MANIFEST) ## Run 'podman play kube --down' 40 | $(podman-play-kube) --down $< 41 | .PHONY: down 42 | 43 | ps: ## Run 'podman ps' for the pod 44 | @$(PODMAN) ps --filter pod=$(POD) 45 | .PHONY: ps 46 | 47 | check: | containers container-contained-ganesha-test ## Build container images, then run 'podman play kube' using local images and run a test-suite 48 | $(MAKE) check-nobuild 49 | .PHONY: check 50 | 51 | check-nobuild: $(MANIFEST) | up-local-nobuild ## Run 'podman play kube' using local images and run a test suite. Like 'check' without building. 52 | $(PODMAN) \ 53 | run \ 54 | --rm \ 55 | --pull=never \ 56 | --read-only \ 57 | --restart=no \ 58 | --cap-drop=ALL \ 59 | --cap-add=NET_BIND_SERVICE \ 60 | --pod=$(POD) \ 61 | localhost/$(LOCAL_CONTAINED_GANESHA_TEST_IMAGE):$(LOCAL_CONTAINED_GANESHA_TEST_TAG) \ 62 | -test.v \ 63 | -host=$(POD) \ 64 | -portmapper-port=$(PORTMAPPER_PORT) \ 65 | -status-port=$(STATUS_PORT) \ 66 | -nlockmgr-port=$(NLOCKMGR_PORT) \ 67 | -rquotad-port=$(RQUOTAD_PORT) \ 68 | -nfs-port=$(NFS_PORT) \ 69 | -mountd-port=$(MOUNTD_PORT) 70 | .PHONY: check-nobuild 71 | 72 | containers: 73 | $(MAKE) -C $(TOP_SRCDIR) containers DOCKER=podman 74 | .PHONY: containers 75 | 76 | container-contained-ganesha-test: 77 | $(MAKE) -C $(TOP_SRCDIR) container-contained-ganesha-test DOCKER=podman 78 | .PHONY: container-contained-ganesha-test 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | contained-ganesha 2 | ================= 3 | [NFS-Ganesha](https://nfs-ganesha.github.io) running in container environments. Properly. 4 | 5 | This repository contains Docker container image build files for all services 6 | requird to run an NFS-Ganesha server in a containerized environment, as well 7 | as deployment files for the following container environments: 8 | 9 | - [docker-compose](https://docs.docker.com/compose/) 10 | - [Kubernetes](https://kubernetes.io) 11 | - [Podman](https://podman.io) 12 | 13 | To build the container images, run `make containers`. See `deploy/` for more 14 | information on the supported deployment mechanisms. 15 | 16 | Rationale 17 | --------- 18 | An NFS server requires several services to be running (at least to support 19 | NFS3): 20 | 21 | - A *portmapper* daemon (`rpcbind`) 22 | - An NLM status daemon (`rpc.statd`) 23 | - In case of NFS-Ganesha, `dbus-daemon` for configuration 24 | - The NFS server, which in case of NFS-Ganesha includes `mountd`, `nlockmgr` 25 | and `rquotad` 26 | 27 | Some projects provide a Docker container image which includes and starts all 28 | these services in a single container. This goes against the principes of 29 | containerized deployments, where a single container should ideally run only a 30 | single service, and each container image should only contain the files required 31 | to run this service. 32 | 33 | As such, this projects provides 4 container images (one for each service), and 34 | runs all of them in a single Pod (think 'network namespace'), which is a 35 | standard concept in Kubernetes, or emulated when using `docker-compose` (see 36 | the design section in the documentation). 37 | 38 | The container images apply as much sharing of layers as possible (by 39 | construction of the various `Dockerfile`s). They each contain a healthcheck 40 | script. 41 | 42 | Security 43 | -------- 44 | Service deployment using containers can increase security of the system, e.g., 45 | by restricting the *capabilities* of a containerized process. The services 46 | deployed by this project are confined using the following mechanisms: 47 | 48 | - The container image (root filesystem) is made read-only. Locations where the 49 | services require write access are mounted as a volume (either *tmpfs* or 50 | persistent). 51 | - All Linux *capabilities* are dropped by default (`--cap-drop ALL` or 52 | similar). Required capabilities are added when needed. 53 | - Services run as non-root user, where possible. However, this is (for now) not 54 | enforced by the container engine: the services start as `root`, then `setuid` 55 | and `setgid` themselves. 56 | - When using `docker-compose`, the `dbus-daemon` container is not connected to 57 | the network, since this is not required. 58 | -------------------------------------------------------------------------------- /ganesha-config-reload/reloader.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "io/ioutil" 7 | "os" 8 | "strconv" 9 | "strings" 10 | "time" 11 | 12 | "github.com/go-logr/logr" 13 | ) 14 | 15 | type reloader struct { 16 | log logr.Logger 17 | pidFile string 18 | events chan interface{} 19 | signal os.Signal 20 | act bool 21 | ticker *time.Ticker 22 | } 23 | 24 | func NewReloader(log logr.Logger, events chan interface{}, pidFile string, signal os.Signal) *reloader { 25 | return &reloader{ 26 | log: log.WithValues("pidfile", pidFile), 27 | events: events, 28 | pidFile: pidFile, 29 | signal: signal, 30 | act: false, 31 | ticker: time.NewTicker(time.Second), 32 | } 33 | } 34 | 35 | func (r *reloader) Run(ctx context.Context) error { 36 | lastEventWasTick := false 37 | 38 | for { 39 | // Don't spam logs with loop iterations caused by the ticker 40 | if !lastEventWasTick { 41 | r.log.V(1).Info("Waiting for events") 42 | } 43 | lastEventWasTick = false 44 | 45 | select { 46 | case _, ok := <-r.events: 47 | if !ok { 48 | r.log.Info("Events channel closed") 49 | return nil 50 | } 51 | 52 | r.log.Info("Detected configuration change") 53 | r.act = true 54 | 55 | case _, ok := <-r.ticker.C: 56 | lastEventWasTick = true 57 | 58 | if !ok { 59 | r.log.Info("Ticker stopped") 60 | return errors.New("Ticker channel stopped unexpectedly") 61 | } 62 | 63 | if r.act { 64 | r.act = false 65 | 66 | r.log.Info("Tick with configuration change, triggering reload") 67 | if err := r.reload(); err != nil { 68 | r.log.Error(err, "Error while triggering reload") 69 | return err 70 | } 71 | } 72 | 73 | case <-ctx.Done(): 74 | r.log.V(1).Info("Context done") 75 | return nil 76 | } 77 | } 78 | } 79 | 80 | func (r *reloader) reload() error { 81 | dat, err := ioutil.ReadFile(r.pidFile) 82 | if err != nil { 83 | r.log.Error(err, "Failed to read Ganesha PID file") 84 | return err 85 | } 86 | str := strings.TrimSuffix(string(dat[:]), "\n") 87 | pid, err := strconv.ParseUint(str, 10, 64) 88 | if err != nil { 89 | r.log.Error(err, "Failed to parse Ganesha PID", "data", dat) 90 | return err 91 | } 92 | pid2 := int(pid) 93 | 94 | proc, err := os.FindProcess(pid2) 95 | if err != nil { 96 | r.log.Error(err, "Failed to find process", "pid", pid2) 97 | return err 98 | } 99 | 100 | err = proc.Signal(r.signal) 101 | if err != nil { 102 | r.log.Error(err, "Failed to signal process", "pid", pid2, "signal", r.signal) 103 | return err 104 | } 105 | r.log.Info("Sent signal to process", "pid", pid2, "signal", r.signal) 106 | 107 | return nil 108 | } 109 | 110 | func (r *reloader) Close() { 111 | r.ticker.Stop() 112 | } 113 | -------------------------------------------------------------------------------- /ganesha-config-reload/Dockerfile: -------------------------------------------------------------------------------- 1 | #{{{ Build target 2 | FROM docker.io/golang:1-alpine as ganesha-config-reload-builder 3 | 4 | WORKDIR ${GOPATH}/src/ganesha-config-reload 5 | ENV GO111MODULES=on 6 | 7 | COPY go.mod go.sum ./ 8 | RUN go mod download && \ 9 | go mod verify 10 | 11 | COPY . ./ 12 | 13 | ENV CGO_ENABLED=0 14 | ENV GOOS=${GOOS:-linux} 15 | ENV GOARCH=${GOARCH:-amd64} 16 | RUN go test \ 17 | -v \ 18 | -mod readonly \ 19 | ./... \ 20 | && \ 21 | go build \ 22 | -a \ 23 | -ldflags '-w -extldflags "-static"' \ 24 | -mod readonly \ 25 | -tags netgo \ 26 | -trimpath \ 27 | -o bin/ganesha-config-reload \ 28 | . 29 | #}}} 30 | 31 | #{{{ The `ganesha-config-reload` image 32 | FROM scratch as ganesha-config-reload 33 | 34 | LABEL org.opencontainers.image.authors="Nicolas Trangez " \ 35 | org.opencontainers.image.description="The ganesha-config-reload sidecar detects configuration file updates and sends the config reload signal (SIGHUP) to the NFS-Ganesha process to reload its settings." \ 36 | org.opencontainers.image.documentation="https://github.com/NicolasT/contained-ganesha/blob/master/ganesha-config-reload/README.md" \ 37 | org.opencontainers.image.licenses="Apache-2.0" \ 38 | org.opencontainers.image.url="https://github.com/NicolasT/contained-ganesha" \ 39 | org.opencontainers.image.source="https://github.com/NicolasT/contained-ganesha.git" \ 40 | org.opencontainers.image.title="ganesha-config-reload" \ 41 | org.opencontainers.image.vendor="Nicolas Trangez " \ 42 | \ 43 | org.label-schema.schema-version="1.0" \ 44 | org.label-schema.description="The ganesha-config-reload sidecar detects configuration file updates and sends the config reload signal (SIGHUP) to the NFS-Ganesha process to reload its settings." \ 45 | org.label-schema.license="Apache-2.0" \ 46 | org.label-schema.name="ganesha-config-reload" \ 47 | org.label-schema.url="https://github.com/NicolasT/contained-ganesha" \ 48 | org.label-schema.usage="https://github.com/NicolasT/contained-ganesha/blob/master/ganesha-config-reload/README.md" \ 49 | org.label-schema.vcs-url="https://github.com/NicolasT/contained-ganesha.git" \ 50 | org.label-schema.vendor="Nicolas Trangez " \ 51 | org.label-schema.docker.cmd="docker run -d --cap-drop ALL --read-only --volumes-from nfs-ganesha:ro contained-ganesha/ganesha-config-reload" \ 52 | org.label-schema.docker.cmd.debug="docker exec -it \$CONTAINER /bin/bash" \ 53 | org.label-schema.docker.params="" \ 54 | \ 55 | app.kubernetes.io/component="ganesha-config-reload" \ 56 | app.kubernetes.io/name="nfs-ganesha" \ 57 | app.kubernetes.io/part-of="contained-ganesha" 58 | 59 | COPY README.md /README.md 60 | 61 | COPY --from=ganesha-config-reload-builder /go/src/ganesha-config-reload/bin/ganesha-config-reload /ganesha-config-reload 62 | 63 | ENTRYPOINT ["/ganesha-config-reload"] 64 | #}}} 65 | -------------------------------------------------------------------------------- /images/nfs-ganesha/README.md: -------------------------------------------------------------------------------- 1 | nfs-ganesha 2 | =========== 3 | This container image will run the [nfs-ganesha](http://nfs-ganesha.github.io/) NFS server. 4 | 5 | Volumes 6 | ------- 7 | | Path | Type | Description | 8 | | ---- | ---- | ----------- | 9 | | */tmp* | tmpfs | Temporary storage | 10 | | */run* | bind | Directory where `rpcbind.sock` resides (see *rpcbind* image) | 11 | | */run/dbus* | bind | Directory where *system_bus_socket* resides (see *dbus-daemon* image) | 12 | | */var/lib/nfs/ganesha* | persistent | Service state directory | 13 | | */etc/ganesha/ganesha.conf.d/local.conf* | bind | File included in main configuration, for overrides | 14 | | */etc/ganesha/ganesha.conf.d/exports.conf* | bind | File included in main configuration, for export definitions | 15 | | */etc/ganesha/ganesha.conf* | bind | Optional configuration file. If provided, the configuration files above won't be used | 16 | | */etc/ganesha/ganesha.conf.sh* | bind | Optional configuration script to generate configuration. Not used when `/etc/ganesha/ganesha.conf` exists. If provided, the configuration files above won't be used. | 17 | 18 | Ports 19 | ----- 20 | | Port | Protocol | Description | 21 | | ---- | -------- | ----------- | 22 | | 866 | TCP | *nlockmgr* service | 23 | | 866 | UDP | *nlockmgr* service | 24 | | 875 | TCP | *rquotad* service | 25 | | 875 | UDP | *rquotad* service | 26 | | 2049 | TCP | NFS service | 27 | | 2049 | UDP | NFS service | 28 | | 20048 | TCP | *mountd* service | 29 | | 20048 | UDP | *mountd* service | 30 | 31 | Environment 32 | ----------- 33 | | Name | Default | Description | 34 | | ---- | ------- | ----------- | 35 | | *MOUNTD_PORT* | 20048 | Port for the *mountd* service to bind to | 36 | | *NFS_PORT* | 2049 | Port for the NFS service to bind to | 37 | | *NLOCKMGR_PORT* | 866 | Port for the *nlockmgr* service to bind to | 38 | | *RQUOTAD_PORT* | 875 | Port for the *rquotad* service to bind to | 39 | -------------------------------------------------------------------------------- /ganesha-config-reload/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "syscall" 9 | 10 | "golang.org/x/sync/errgroup" 11 | 12 | zap "sigs.k8s.io/controller-runtime/pkg/log/zap" 13 | ) 14 | 15 | const ( 16 | defaultPidFile = "/run/ganesha/ganesha.pid" 17 | defaultSignal = syscall.SIGHUP 18 | ) 19 | 20 | var ( 21 | pidFile string 22 | mode watchMode 23 | logOpts zap.Options 24 | ) 25 | 26 | func init() { 27 | flag.StringVar(&pidFile, "pid", defaultPidFile, "path of the NFS-Ganesha PID file") 28 | flag.Var(&mode, "mode", "watch mode ('configmap' or 'file')") 29 | err := flag.Set("mode", "file") 30 | if err != nil { 31 | panic("Unable to set mode flag default value") 32 | } 33 | logOpts.BindFlags(flag.CommandLine) 34 | } 35 | 36 | func main() { 37 | flag.Parse() 38 | if flag.NArg() < 1 { 39 | fmt.Fprintf(flag.CommandLine.Output(), "Missing path argument(s)\n") 40 | os.Exit(1) 41 | } 42 | paths := flag.Args() 43 | 44 | logger := zap.New(zap.UseFlagOptions(&logOpts)) 45 | 46 | for _, path := range paths { 47 | st, err := os.Stat(path) 48 | isNotExist := false 49 | 50 | if err != nil { 51 | if !os.IsNotExist(err) { 52 | logger.Error(err, "Error during stat", "path", path) 53 | os.Exit(1) 54 | } else { 55 | isNotExist = true 56 | } 57 | } 58 | if isNotExist || !st.IsDir() { 59 | logger.V(0).Info("Not a directory", "path", path) 60 | os.Exit(1) 61 | } 62 | } 63 | 64 | ctx := context.Background() 65 | ctx, cancel := context.WithCancel(ctx) 66 | 67 | var checker Checker 68 | switch mode { 69 | case WatchModeConfigMap: 70 | checker = ConfigMapChecker 71 | case WatchModeFile: 72 | checker = FileChecker 73 | default: 74 | panic(fmt.Sprintf("Unknown mode: %v", mode)) 75 | } 76 | 77 | w, err := NewWatcher(logger.WithName("watcher"), checker, paths) 78 | if err != nil { 79 | logger.Error(err, "Unable to construct watcher") 80 | os.Exit(1) 81 | } 82 | defer w.Close() 83 | 84 | err = w.Register() 85 | if err != nil { 86 | logger.Error(err, "Unable to register watcher") 87 | os.Exit(1) 88 | } 89 | 90 | sigHandler, err := NewSignalHandler(logger.WithName("signalhandler"), cancel, os.Interrupt, syscall.SIGTERM) 91 | if err != nil { 92 | logger.Error(err, "Unable to construct signal handler") 93 | os.Exit(1) 94 | } 95 | defer sigHandler.Close() 96 | 97 | err = sigHandler.Register() 98 | if err != nil { 99 | logger.Error(err, "Failed to register signal handler") 100 | os.Exit(1) 101 | } 102 | 103 | r := NewReloader(logger.WithName("reloader"), w.Events, pidFile, defaultSignal) 104 | defer r.Close() 105 | 106 | g, ctx := errgroup.WithContext(ctx) 107 | 108 | g.Go(func() error { 109 | err = sigHandler.Run(ctx) 110 | if err != nil { 111 | logger.Error(err, "Error in signal handler") 112 | return err 113 | } 114 | 115 | return nil 116 | }) 117 | 118 | g.Go(func() error { 119 | err := w.Run(ctx) 120 | if err != nil { 121 | logger.Error(err, "Error in watcher") 122 | } 123 | return err 124 | }) 125 | 126 | g.Go(func() error { 127 | err := r.Run(ctx) 128 | if err != nil { 129 | logger.Error(err, "Error in reloader") 130 | } 131 | return err 132 | }) 133 | 134 | logger.V(1).Info("Waiting for goroutines") 135 | <-ctx.Done() 136 | logger.V(1).Info("Context done") 137 | cancel() 138 | 139 | err = g.Wait() 140 | if err != nil { 141 | logger.Error(err, "Error in errgroup") 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /deploy/kubernetes/Makefile: -------------------------------------------------------------------------------- 1 | KIND ?= kind 2 | KUBECTL ?= kubectl 3 | 4 | TOP_SRCDIR := ../.. 5 | 6 | default: help 7 | .PHONY: default 8 | 9 | include $(TOP_SRCDIR)/include.mk 10 | 11 | MANIFESTS = \ 12 | base/service.yml \ 13 | base/service-headless.yml \ 14 | base/statefulset.yml \ 15 | overlays/local/kustomization.yml 16 | 17 | manifests: $(MANIFESTS) ## Render all manifest files 18 | .PHONY: manifests 19 | 20 | base/service.yml: $(ENV_FILE) hack/mkservice.sh 21 | hack/mkservice.sh $< "nfs-ganesha" "" > $@ || (rm -f $@; false) 22 | 23 | base/service-headless.yml: $(ENV_FILE) hack/mkservice.sh 24 | hack/mkservice.sh $< "nfs-ganesha-headless" "clusterIP: None" > $@ || (rm -f $@; false) 25 | 26 | base/statefulset.yml: $(ENV_FILE) hack/mkstatefulset.sh 27 | hack/mkstatefulset.sh $< > $@ || (rm -f $@; false) 28 | 29 | overlays/local/kustomization.yml: $(ENV_FILE) hack/mkkustomization.sh 30 | hack/mkkustomization.sh $< > $@ || (rm -f $@; false) 31 | 32 | clean: ## Remove all generated files 33 | rm -f $(MANIFESTS) 34 | .PHONY: clean 35 | 36 | kubectl-local-apply: ## Run 'kubectl apply' using the 'local' overlay 37 | $(KUBECTL) apply -k ./overlays/local/ 38 | .PHONY: kubectl-local-apply 39 | 40 | kubectl-local-delete: ## Run 'kubectl delete' using the 'local' overlay 41 | $(KUBECTL) delete -k ./overlays/local/ 42 | .PHONY: kubectl-local-delete 43 | 44 | check: kubectl-local-apply ## Run a test-suite 45 | . $(ENV_FILE) && \ 46 | $(KUBECTL) run \ 47 | --attach=true \ 48 | --image=$${LOCAL_CONTAINED_GANESHA_TEST_IMAGE}:$${LOCAL_CONTAINED_GANESHA_TEST_TAG} \ 49 | --image-pull-policy=Never \ 50 | --restart=Never \ 51 | --rm=true \ 52 | --wait=true \ 53 | contained-ganesha-test -- \ 54 | -test.v \ 55 | -host=nfs-ganesha \ 56 | -portmapper-port=$${PORTMAPPER_PORT} \ 57 | -status-port=$${STATUS_PORT} \ 58 | -nlockmgr-port=$${NLOCKMGR_PORT} \ 59 | -rquotad-port=$${RQUOTAD_PORT} \ 60 | -nfs-port=$${NFS_PORT} \ 61 | -mountd-port=$${MOUNTD_PORT} 62 | 63 | .PHONY: check 64 | 65 | kind-load-docker-images: | containers ## Build container images, then load local container images into a Kind cluster 66 | $(MAKE) kind-load-docker-images-nobuild 67 | .PHONY: kind-load-docker-images 68 | 69 | kind-load-docker-images-nobuild: $(ENV_FILE) 70 | @. $<; \ 71 | set -ue; \ 72 | for image in \ 73 | $${LOCAL_RPCBIND_IMAGE}:$${LOCAL_RPCBIND_TAG} \ 74 | $${LOCAL_RPC_STATD_IMAGE}:$${LOCAL_RPC_STATD_TAG} \ 75 | $${LOCAL_DBUS_DAEMON_IMAGE}:$${LOCAL_DBUS_DAEMON_TAG} \ 76 | $${LOCAL_NFS_GANESHA_IMAGE}:$${LOCAL_NFS_GANESHA_TAG} \ 77 | $${LOCAL_GANESHA_CONFIG_RELOAD_IMAGE}:$${LOCAL_GANESHA_CONFIG_RELOAD_TAG} \ 78 | ; do \ 79 | echo $(KIND) load docker-image $${image}; \ 80 | $(KIND) load docker-image $${image}; \ 81 | done 82 | .PHONY: kind-load-docker-images-nobuild 83 | 84 | kind-load-cgt-image: | container-contained-ganesha-test ## Build 'contained-ganesha-test' image, then load local container image into a Kind cluster 85 | $(MAKE) kind-load-cgt-image-nobuild 86 | .PHONY: kind-load-cgt-image 87 | 88 | kind-load-cgt-image-nobuild: $(ENV_FILE) 89 | @. $<; \ 90 | set -ue; \ 91 | echo $(KIND) load docker-image $${LOCAL_CONTAINED_GANESHA_TEST_IMAGE}:$${LOCAL_CONTAINED_GANESHA_TEST_TAG}; \ 92 | $(KIND) load docker-image $${LOCAL_CONTAINED_GANESHA_TEST_IMAGE}:$${LOCAL_CONTAINED_GANESHA_TEST_TAG} 93 | .PHONY: kind-load-cgt-image-nobuild 94 | 95 | containers: 96 | $(MAKE) -C $(TOP_SRCDIR) containers 97 | .PHONY: containers 98 | 99 | container-contained-ganesha-test: 100 | $(MAKE) -C $(TOP_SRCDIR) container-contained-ganesha-test 101 | .PHONY: container-contained-ganesha-test 102 | -------------------------------------------------------------------------------- /ganesha-config-reload/watcher_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | "context" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "sync" 9 | 10 | "k8s.io/kubernetes/pkg/volume/util" 11 | 12 | "github.com/fsnotify/fsnotify" 13 | "github.com/go-logr/logr" 14 | "github.com/go-logr/stdr" 15 | 16 | . "github.com/onsi/ginkgo" 17 | . "github.com/onsi/gomega" 18 | 19 | . "github.com/NicolasT/contained-ganesha/ganesha-config-reload" 20 | ) 21 | 22 | var _ = Describe("Watcher", func() { 23 | var ( 24 | logger logr.Logger 25 | configPath string 26 | ) 27 | 28 | BeforeEach(func() { 29 | logger = stdr.New(log.New(GinkgoWriter, "", log.LstdFlags)) 30 | 31 | By("Creating a temporary directory") 32 | var err error 33 | configPath, err = ioutil.TempDir("", "ganesha-config-reload-test") 34 | Expect(err).NotTo(HaveOccurred()) 35 | }) 36 | 37 | AfterEach(func() { 38 | By("Cleaning up the temporary directory") 39 | os.RemoveAll(configPath) 40 | }) 41 | 42 | Context("when used with a ConfigMap", func() { 43 | payload1 := map[string]util.FileProjection{ 44 | "local.conf": util.FileProjection{ 45 | Data: []byte{}, 46 | Mode: 0400, 47 | }, 48 | "exports.conf": util.FileProjection{ 49 | Data: []byte{}, 50 | Mode: 0400, 51 | }, 52 | } 53 | 54 | payload2 := map[string]util.FileProjection{ 55 | "local.conf": util.FileProjection{ 56 | Data: []byte{}, 57 | Mode: 0400, 58 | }, 59 | "exports.conf": util.FileProjection{ 60 | Data: []byte{'a', 'b', 'c'}, 61 | Mode: 0400, 62 | }, 63 | } 64 | 65 | It("emits an event as expected", func(done Done) { 66 | aw, err := util.NewAtomicWriter(configPath, "gcr-test") 67 | Expect(err).NotTo(HaveOccurred()) 68 | 69 | By("Creating a first version of the ConfigMap") 70 | Expect(aw.Write(payload1, nil)).To(Succeed()) 71 | 72 | By("Constructing a watcher") 73 | w, err := NewWatcher(logger, ConfigMapChecker, []string{configPath}) 74 | Expect(err).NotTo(HaveOccurred()) 75 | defer w.Close() 76 | 77 | By("Registering the watcher") 78 | Expect(w.Register()).To(Succeed()) 79 | 80 | By("Running the watcher") 81 | ctx, cancel := context.WithCancel(context.Background()) 82 | 83 | wg := &sync.WaitGroup{} 84 | wg.Add(1) 85 | go func() { 86 | defer GinkgoRecover() 87 | defer wg.Done() 88 | Expect(w.Run(ctx)).To(Succeed()) 89 | }() 90 | 91 | By("Replacing the ConfigMap content") 92 | Expect(aw.Write(payload2, nil)).To(Succeed()) 93 | 94 | By("Waiting for an event to trigger") 95 | Eventually(w.Events).Should(Receive()) 96 | 97 | By("Waiting for everything to clean up") 98 | cancel() 99 | wg.Wait() 100 | 101 | close(done) 102 | }) 103 | }) 104 | }) 105 | 106 | var _ = Describe("ConfigMapChecker", func() { 107 | It("returns 'true' when creating '..data'", func() { 108 | evt := fsnotify.Event{ 109 | Name: "..data", 110 | Op: fsnotify.Create, 111 | } 112 | Expect(ConfigMapChecker(evt)).To(BeTrue()) 113 | }) 114 | 115 | It("returns 'true' when '..data' is prefixed", func() { 116 | evt := fsnotify.Event{ 117 | Name: "config/..data", 118 | Op: fsnotify.Create, 119 | } 120 | Expect(ConfigMapChecker(evt)).To(BeTrue()) 121 | }) 122 | 123 | It("returns 'true' when Create is only one of the operations", func() { 124 | evt := fsnotify.Event{ 125 | Name: "..data", 126 | Op: fsnotify.Create | fsnotify.Chmod, 127 | } 128 | Expect(ConfigMapChecker(evt)).To(BeTrue()) 129 | }) 130 | 131 | It("returns 'false' when another name is created", func() { 132 | evt := fsnotify.Event{ 133 | Name: ".data", 134 | Op: fsnotify.Create, 135 | } 136 | Expect(ConfigMapChecker(evt)).To(BeFalse()) 137 | }) 138 | }) 139 | 140 | var _ = Describe("FileChecker", func() { 141 | It("returns 'true' when creating a file", func() { 142 | evt := fsnotify.Event{ 143 | Name: "exports.conf", 144 | Op: fsnotify.Create, 145 | } 146 | Expect(FileChecker(evt)).To(BeTrue()) 147 | }) 148 | 149 | It("returns 'true' when writing to a file", func() { 150 | evt := fsnotify.Event{ 151 | Name: "exports.conf", 152 | Op: fsnotify.Write, 153 | } 154 | Expect(FileChecker(evt)).To(BeTrue()) 155 | }) 156 | }) 157 | -------------------------------------------------------------------------------- /ganesha-config-reload/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/NicolasT/contained-ganesha/ganesha-config-reload 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/fsnotify/fsnotify v1.9.0 9 | github.com/go-logr/logr v1.4.3 10 | github.com/go-logr/stdr v1.2.2 11 | github.com/onsi/ginkgo v1.16.5 12 | github.com/onsi/gomega v1.38.2 13 | golang.org/x/sync v0.17.0 14 | k8s.io/kubernetes v1.33.4 15 | sigs.k8s.io/controller-runtime v0.21.0 16 | ) 17 | 18 | require ( 19 | github.com/beorn7/perks v1.0.1 // indirect 20 | github.com/blang/semver/v4 v4.0.0 // indirect 21 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 22 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 23 | github.com/emicklei/go-restful/v3 v3.13.0 // indirect 24 | github.com/fxamacker/cbor/v2 v2.9.0 // indirect 25 | github.com/go-logr/zapr v1.3.0 // indirect 26 | github.com/go-openapi/jsonpointer v0.22.0 // indirect 27 | github.com/go-openapi/jsonreference v0.21.0 // indirect 28 | github.com/go-openapi/swag v0.24.1 // indirect 29 | github.com/go-openapi/swag/cmdutils v0.24.0 // indirect 30 | github.com/go-openapi/swag/conv v0.24.0 // indirect 31 | github.com/go-openapi/swag/fileutils v0.24.0 // indirect 32 | github.com/go-openapi/swag/jsonname v0.24.0 // indirect 33 | github.com/go-openapi/swag/jsonutils v0.24.0 // indirect 34 | github.com/go-openapi/swag/loading v0.24.0 // indirect 35 | github.com/go-openapi/swag/mangling v0.24.0 // indirect 36 | github.com/go-openapi/swag/netutils v0.24.0 // indirect 37 | github.com/go-openapi/swag/stringutils v0.24.0 // indirect 38 | github.com/go-openapi/swag/typeutils v0.24.0 // indirect 39 | github.com/go-openapi/swag/yamlutils v0.24.0 // indirect 40 | github.com/gogo/protobuf v1.3.2 // indirect 41 | github.com/google/gnostic-models v0.7.0 // indirect 42 | github.com/google/go-cmp v0.7.0 // indirect 43 | github.com/google/uuid v1.6.0 // indirect 44 | github.com/josharian/intern v1.0.0 // indirect 45 | github.com/json-iterator/go v1.1.12 // indirect 46 | github.com/mailru/easyjson v0.9.0 // indirect 47 | github.com/moby/sys/mountinfo v0.7.2 // indirect 48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 49 | github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect 50 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 51 | github.com/nxadm/tail v1.4.11 // indirect 52 | github.com/opencontainers/selinux v1.12.0 // indirect 53 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 54 | github.com/prometheus/client_golang v1.23.0 // indirect 55 | github.com/prometheus/client_model v0.6.2 // indirect 56 | github.com/prometheus/common v0.66.1 // indirect 57 | github.com/prometheus/procfs v0.17.0 // indirect 58 | github.com/rogpeppe/go-internal v1.14.1 // indirect 59 | github.com/spf13/pflag v1.0.7 // indirect 60 | github.com/x448/float16 v0.8.4 // indirect 61 | go.opentelemetry.io/otel v1.37.0 // indirect 62 | go.opentelemetry.io/otel/trace v1.37.0 // indirect 63 | go.uber.org/multierr v1.11.0 // indirect 64 | go.uber.org/zap v1.27.0 // indirect 65 | go.yaml.in/yaml/v2 v2.4.3 // indirect 66 | go.yaml.in/yaml/v3 v3.0.4 // indirect 67 | golang.org/x/net v0.44.0 // indirect 68 | golang.org/x/oauth2 v0.30.0 // indirect 69 | golang.org/x/sys v0.36.0 // indirect 70 | golang.org/x/term v0.35.0 // indirect 71 | golang.org/x/text v0.29.0 // indirect 72 | golang.org/x/time v0.12.0 // indirect 73 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect 74 | google.golang.org/grpc v1.75.0 // indirect 75 | google.golang.org/protobuf v1.36.8 // indirect 76 | gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect 77 | gopkg.in/inf.v0 v0.9.1 // indirect 78 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 79 | gopkg.in/yaml.v3 v3.0.1 // indirect 80 | k8s.io/api v0.34.1 // indirect 81 | k8s.io/apiextensions-apiserver v0.33.4 // indirect 82 | k8s.io/apimachinery v0.34.1 // indirect 83 | k8s.io/apiserver v0.33.4 // indirect 84 | k8s.io/client-go v0.34.1 // indirect 85 | k8s.io/component-base v0.33.4 // indirect 86 | k8s.io/component-helpers v0.34.1 // indirect 87 | k8s.io/controller-manager v0.33.4 // indirect 88 | k8s.io/csi-translation-lib v0.33.4 // indirect 89 | k8s.io/klog/v2 v2.130.1 // indirect 90 | k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect 91 | k8s.io/kubelet v0.33.4 // indirect 92 | k8s.io/mount-utils v0.34.0 // indirect 93 | k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect 94 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 95 | sigs.k8s.io/randfill v1.0.0 // indirect 96 | sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect 97 | sigs.k8s.io/yaml v1.6.0 // indirect 98 | ) 99 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DATE ?= date 2 | DOCKER ?= docker 3 | GIT ?= git 4 | 5 | TOP_SRCDIR := . 6 | 7 | default: help 8 | .PHONY: default 9 | 10 | include $(TOP_SRCDIR)/include.mk 11 | 12 | CONTAINERS = \ 13 | dbus-daemon \ 14 | ganesha-config-reload \ 15 | nfs-ganesha \ 16 | rpc.statd \ 17 | rpcbind 18 | 19 | containers: $(foreach c,$(CONTAINERS),container-$(c)) ## Build all container images 20 | .PHONY: containers 21 | 22 | include $(ENV_FILE) 23 | 24 | docker-build = \ 25 | DOCKER_BUILDKIT=1 \ 26 | $(DOCKER) build \ 27 | --build-arg BASE_IMAGE=$(BASE_IMAGE) \ 28 | --build-arg BASE_IMAGE_TAG=$(BASE_IMAGE_TAG) \ 29 | --build-arg PORTMAPPER_PORT=$(PORTMAPPER_PORT) \ 30 | --build-arg STATUS_PORT=$(STATUS_PORT) \ 31 | --build-arg NLOCKMGR_PORT=$(NLOCKMGR_PORT) \ 32 | --build-arg RQUOTAD_PORT=$(RQUOTAD_PORT) \ 33 | --build-arg NFS_PORT=$(NFS_PORT) \ 34 | --build-arg MOUNTD_PORT=$(MOUNTD_PORT) \ 35 | $(CACHE_FROM) \ 36 | --progress plain \ 37 | --compress \ 38 | --file $< \ 39 | --target $(TARGET) \ 40 | $(LABELS) \ 41 | --pull \ 42 | --tag $(1) \ 43 | $(dir $<) 44 | 45 | build-date = $(shell $(DATE) -u +"%Y-%m-%dT%H:%M:%SZ") 46 | git-version = $(shell $(GIT) describe --tags --dirty --broken) 47 | git-rev = $(shell $(GIT) log -n1 --format=%H) 48 | 49 | default-labels = \ 50 | --label org.opencontainers.image.created=$(call build-date) \ 51 | --label org.opencontainers.image.version=$(call git-version) \ 52 | --label org.opencontainers.image.revision=$(call git-rev) \ 53 | --label org.opencontainers.image.ref.name=$(1) \ 54 | \ 55 | --label org.label-schema.build-date=$(call build-date) \ 56 | --label org.label-schema.version=$(call git-version) \ 57 | --label org.label-schema.vcs-ref=$(call git-rev) 58 | 59 | 60 | container-rpcbind: TARGET=rpcbind 61 | container-rpcbind: LABELS=$(call default-labels,$(RPCBIND_IMAGE):$(RPCBIND_TAG)) 62 | container-rpcbind: CACHE_FROM= \ 63 | --cache-from $(RPCBIND_IMAGE) \ 64 | --cache-from $(RPC_STATD_IMAGE) \ 65 | --cache-from $(NFS_GANESHA_IMAGE) 66 | container-rpcbind: \ 67 | Dockerfile \ 68 | images/rpcbind/entrypoint.sh \ 69 | images/rpcbind/healthcheck.sh \ 70 | images/rpcbind/README.md 71 | $(call docker-build,$(LOCAL_RPCBIND_IMAGE):$(LOCAL_RPCBIND_TAG)) 72 | .PHONY: container-rpcbind 73 | 74 | container-rpc.statd: TARGET=rpc.statd 75 | container-rpc.statd: LABELS=$(call default-labels,$(RPC_STATD_IMAGE):$(RPC_STATD_TAG)) 76 | container-rpc.statd: CACHE_FROM= \ 77 | --cache-from $(RPCBIND_IMAGE) \ 78 | --cache-from $(RPC_STATD_IMAGE) \ 79 | --cache-from $(NFS_GANESHA_IMAGE) 80 | container-rpc.statd: \ 81 | Dockerfile \ 82 | images/rpc.statd/entrypoint.sh \ 83 | images/rpc.statd/healthcheck.sh \ 84 | images/rpc.statd/README.md 85 | $(call docker-build,$(LOCAL_RPC_STATD_IMAGE):$(LOCAL_RPC_STATD_TAG)) 86 | .PHONY: container-rpc.statd 87 | 88 | container-dbus-daemon: TARGET=dbus-daemon 89 | container-dbus-daemon: LABELS=$(call default-labels,$(DBUS_DAEMON_IMAGE):$(DBUS_DAEMON_TAG)) 90 | container-dbus-daemon: CACHE_FROM=--cache-from $(DBUS_DAEMON_IMAGE) 91 | container-dbus-daemon: \ 92 | Dockerfile \ 93 | images/dbus-daemon/entrypoint.sh \ 94 | images/dbus-daemon/healthcheck.sh \ 95 | images/dbus-daemon/README.md 96 | $(call docker-build,$(LOCAL_DBUS_DAEMON_IMAGE):$(LOCAL_DBUS_DAEMON_TAG)) 97 | .PHONY: container-dbus-daemon 98 | 99 | container-nfs-ganesha: TARGET=nfs-ganesha 100 | container-nfs-ganesha: LABELS=$(call default-labels,$(NFS_GANESHA_IMAGE):$(NFS_GANESHA_TAG)) 101 | container-nfs-ganesha: CACHE_FROM= \ 102 | --cache-from $(RPCBIND_IMAGE) \ 103 | --cache-from $(RPC_STATD_IMAGE) \ 104 | --cache-from $(NFS_GANESHA_IMAGE) 105 | container-nfs-ganesha: \ 106 | Dockerfile \ 107 | images/nfs-ganesha/entrypoint.sh \ 108 | images/nfs-ganesha/ganesha.conf.sh \ 109 | images/nfs-ganesha/healthcheck.sh \ 110 | images/nfs-ganesha/README.md 111 | $(call docker-build,$(LOCAL_NFS_GANESHA_IMAGE):$(LOCAL_NFS_GANESHA_TAG)) 112 | .PHONY: container-nfs-ganesha 113 | 114 | container-ganesha-config-reload: TARGET=ganesha-config-reload 115 | container-ganesha-config-reload: LABELS=$(call default-labels,$(GANESHA_CONFIG_RELOAD_IMAGE):$(GANESHA_CONFIG_RELOAD_TAG)) 116 | container-ganesha-config-reload: CACHE_FROM= \ 117 | --cache-from $(GANESHA_CONFIG_RELOAD_IMAGE) 118 | container-ganesha-config-reload: \ 119 | ganesha-config-reload/Dockerfile \ 120 | ganesha-config-reload/go.mod \ 121 | ganesha-config-reload/go.sum \ 122 | ganesha-config-reload/main.go 123 | $(call docker-build,$(LOCAL_GANESHA_CONFIG_RELOAD_IMAGE):$(LOCAL_GANESHA_CONFIG_RELOAD_TAG)) 124 | .PHONY: container-ganesha-config-reload 125 | 126 | container-contained-ganesha-test: TARGET=contained-ganesha-test 127 | container-contained-ganesha-test: LABELS=$(call default-labels,$(CONTAINED_GANESHA_TEST_IMAGE):$(CONTAINED_GANESHA_TEST_TAG)) 128 | container-contained-ganesha-test: CACHE_FROM=--cache-from $(CONTAINED_GANESHA_TEST_IMAGE) 129 | container-contained-ganesha-test: test/Dockerfile 130 | $(call docker-build,$(LOCAL_CONTAINED_GANESHA_TEST_IMAGE):$(LOCAL_CONTAINED_GANESHA_TEST_TAG)) 131 | .PHONY: container-contained-ganesha-test 132 | -------------------------------------------------------------------------------- /deploy/docker-compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: 'contained-ganesha' 2 | 3 | services: 4 | pod: 5 | image: ${POD_IMAGE}:${POD_TAG} 6 | cap_drop: 7 | - ALL 8 | read_only: true 9 | restart: unless-stopped 10 | labels: 11 | app: contained-ganesha 12 | component: pod 13 | networks: 14 | - default 15 | expose: 16 | - ${PORTMAPPER_PORT}/tcp #rpcbind/portmapper 17 | - ${PORTMAPPER_PORT}/udp #rpcbind/portmapper 18 | - ${STATUS_PORT}/tcp #rpc.statd/status 19 | - ${STATUS_PORT}/udp #rpc.statd/status 20 | - ${NLOCKMGR_PORT}/tcp #nfs-ganesha/nlockmgr 21 | - ${NLOCKMGR_PORT}/udp #nfs-ganesha/nlockmgr 22 | - ${RQUOTAD_PORT}/tcp #nfs-ganesha/rquotad 23 | - ${RQUOTAD_PORT}/udp #nfs-ganesha/rquotad 24 | - ${NFS_PORT}/tcp #nfs-ganesha/nfs 25 | - ${NFS_PORT}/udp #nfs-ganesha/nfs 26 | - ${MOUNTD_PORT}/tcp #nfs-ganesha/mountd 27 | - ${MOUNTD_PORT}/udp #nfs-ganesha/mountd 28 | 29 | rpcbind: 30 | image: ${RPCBIND_IMAGE}:${RPCBIND_TAG} 31 | init: true 32 | read_only: true 33 | restart: on-failure 34 | cap_drop: 35 | - ALL 36 | cap_add: 37 | - DAC_OVERRIDE 38 | - CHOWN 39 | - NET_BIND_SERVICE 40 | - SETGID 41 | - SETUID 42 | labels: 43 | app: contained-ganesha 44 | component: rpcbind 45 | network_mode: "service:pod" 46 | volumes: 47 | - type: volume 48 | source: run 49 | target: /run 50 | volume: 51 | nocopy: true 52 | 53 | rpc.statd: 54 | image: ${RPC_STATD_IMAGE}:${RPC_STATD_TAG} 55 | init: true 56 | environment: 57 | STATUS_PORT: 58 | read_only: true 59 | restart: on-failure 60 | cap_drop: 61 | - ALL 62 | cap_add: 63 | - DAC_OVERRIDE 64 | - CHOWN 65 | - NET_BIND_SERVICE 66 | - SETGID 67 | - SETPCAP 68 | - SETUID 69 | labels: 70 | app: contained-ganesha 71 | component: rpc.statd 72 | depends_on: 73 | - rpcbind 74 | network_mode: "service:pod" 75 | volumes: 76 | - type: volume 77 | source: run 78 | target: /run 79 | volume: 80 | nocopy: true 81 | - type: volume 82 | source: rpc.statd-lib 83 | target: /var/lib/nfs 84 | volume: 85 | nocopy: true 86 | 87 | dbus-daemon: 88 | image: ${DBUS_DAEMON_IMAGE}:${DBUS_DAEMON_TAG} 89 | init: true 90 | read_only: true 91 | restart: on-failure 92 | cap_drop: 93 | - ALL 94 | cap_add: 95 | - SETGID 96 | - SETPCAP 97 | - SETUID 98 | labels: 99 | app: contained-ganesha 100 | component: dbus-daemon 101 | network_mode: "none" 102 | volumes: 103 | - type: volume 104 | source: dbus-daemon-run 105 | target: /run/dbus 106 | volume: 107 | nocopy: true 108 | - type: volume 109 | source: dbus-daemon-lib 110 | target: /var/lib/dbus 111 | volume: 112 | nocopy: true 113 | 114 | nfs-ganesha: 115 | image: ${NFS_GANESHA_IMAGE}:${NFS_GANESHA_TAG} 116 | environment: 117 | NLOCKMGR_PORT: 118 | RQUOTAD_PORT: 119 | NFS_PORT: 120 | MOUNTD_PORT: 121 | read_only: true 122 | restart: on-failure 123 | cap_drop: 124 | - ALL 125 | cap_add: 126 | - CHOWN 127 | - DAC_OVERRIDE 128 | - DAC_READ_SEARCH 129 | - FOWNER 130 | - FSETID 131 | - NET_BIND_SERVICE 132 | - SETGID 133 | - SETUID 134 | security_opt: 135 | # TODO Create a proper seccomp profile 136 | - seccomp:unconfined 137 | labels: 138 | app: contained-ganesha 139 | component: nfs-ganesha 140 | depends_on: 141 | - pod 142 | - rpcbind 143 | - rpc.statd 144 | - dbus-daemon 145 | network_mode: "service:pod" 146 | pid: "service:pod" 147 | volumes: 148 | - type: volume 149 | source: run 150 | target: /run 151 | volume: 152 | nocopy: true 153 | - type: volume 154 | source: dbus-daemon-run 155 | target: /run/dbus 156 | read_only: true 157 | volume: 158 | nocopy: true 159 | - type: volume 160 | source: nfs-ganesha-lib 161 | target: /var/lib/nfs/ganesha 162 | volume: 163 | nocopy: true 164 | - type: tmpfs 165 | target: /tmp 166 | - type: bind 167 | source: ./ganesha.conf.d/ 168 | target: /etc/ganesha/ganesha.conf.d/ 169 | read_only: true 170 | 171 | ganesha-config-reload: 172 | image: ${GANESHA_CONFIG_RELOAD_IMAGE}:${GANESHA_CONFIG_RELOAD_TAG} 173 | command: 174 | - -mode=file 175 | - -pid=/run/ganesha/ganesha.pid 176 | - /etc/ganesha/ganesha.conf.d 177 | read_only: true 178 | restart: on-failure 179 | cap_drop: 180 | - ALL 181 | labels: 182 | app: contained-ganesha 183 | component: ganesha-config-reload 184 | depends_on: 185 | - pod 186 | - nfs-ganesha 187 | network_mode: "none" 188 | pid: "service:pod" 189 | volumes: 190 | - type: volume 191 | source: run 192 | target: /run 193 | volume: 194 | nocopy: true 195 | - type: bind 196 | source: ./ganesha.conf.d/ 197 | target: /etc/ganesha/ganesha.conf.d/ 198 | read_only: true 199 | 200 | volumes: 201 | # Note: we use a *shared* `/run` (across all containers), since this volume 202 | # contains special files (or rather, sockets) that are used between them, 203 | # e.g., `rpcbind.sock` or the DBus system bus socket. 204 | run: 205 | driver: local 206 | driver_opts: 207 | type: tmpfs 208 | device: tmpfs 209 | o: size=16m 210 | labels: 211 | app: contained-ganesha 212 | 213 | rpc.statd-lib: 214 | driver: local 215 | labels: 216 | app: contained-ganesha 217 | component: rpc.statd 218 | 219 | dbus-daemon-run: 220 | driver: local 221 | driver_opts: 222 | type: tmpfs 223 | device: tmpfs 224 | o: size=16m 225 | labels: 226 | app: contained-ganesha 227 | component: dbus-daemon 228 | 229 | dbus-daemon-lib: 230 | driver: local 231 | labels: 232 | app: contained-ganesha 233 | component: dbus-daemon 234 | 235 | nfs-ganesha-lib: 236 | driver: local 237 | labels: 238 | app: contained-ganesha 239 | component: nfs-ganesha 240 | 241 | networks: 242 | default: 243 | -------------------------------------------------------------------------------- /deploy/kubernetes/hack/mkstatefulset.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ue -o pipefail 4 | 5 | source $1 6 | 7 | cat << EOF 8 | apiVersion: apps/v1 9 | kind: StatefulSet 10 | metadata: 11 | name: nfs-ganesha 12 | labels: 13 | app: contained-ganesha 14 | component: nfs-ganesha 15 | spec: 16 | selector: 17 | matchLabels: 18 | app: contained-ganesha 19 | component: nfs-ganesha 20 | serviceName: nfs-ganesha-headless 21 | replicas: 1 22 | template: 23 | metadata: 24 | labels: 25 | app: contained-ganesha 26 | component: nfs-ganesha 27 | spec: 28 | automountServiceAccountToken: false 29 | shareProcessNamespace: true 30 | 31 | containers: 32 | - name: nfs-ganesha 33 | image: ${NFS_GANESHA_IMAGE}:${NFS_GANESHA_TAG} 34 | env: 35 | - name: NLOCKMGR_PORT 36 | value: "${NLOCKMGR_PORT}" 37 | - name: RQUOTAD_PORT 38 | value: "${RQUOTAD_PORT}" 39 | - name: NFS_PORT 40 | value: "${NFS_PORT}" 41 | - name: MOUNTD_PORT 42 | value: "${MOUNTD_PORT}" 43 | securityContext: 44 | allowPrivilegeEscalation: false 45 | readOnlyRootFilesystem: true 46 | capabilities: 47 | drop: 48 | - ALL 49 | add: 50 | - CHOWN 51 | - DAC_OVERRIDE 52 | - DAC_READ_SEARCH 53 | - FOWNER 54 | - FSETID 55 | - NET_BIND_SERVICE 56 | - SETGID 57 | - SETUID 58 | ports: 59 | - name: nlockmgr-tcp 60 | containerPort: ${NLOCKMGR_PORT} 61 | protocol: TCP 62 | - name: nlockmgr-udp 63 | containerPort: ${NLOCKMGR_PORT} 64 | protocol: UDP 65 | - name: rquotad-tcp 66 | containerPort: ${RQUOTAD_PORT} 67 | protocol: TCP 68 | - name: rquotad-udp 69 | containerPort: ${RQUOTAD_PORT} 70 | protocol: UDP 71 | - name: nfs-tcp 72 | containerPort: ${NFS_PORT} 73 | protocol: TCP 74 | - name: nfs-udp 75 | containerPort: ${NFS_PORT} 76 | protocol: UDP 77 | - name: mountd-tcp 78 | containerPort: ${MOUNTD_PORT} 79 | protocol: TCP 80 | - name: mountd-udp 81 | containerPort: ${MOUNTD_PORT} 82 | protocol: UDP 83 | livenessProbe: 84 | tcpSocket: 85 | port: nfs-tcp 86 | readinessProbe: 87 | exec: 88 | command: ["/healthcheck.sh"] 89 | timeoutSeconds: 10 90 | terminationMessagePolicy: FallbackToLogsOnError 91 | volumeMounts: 92 | - name: run 93 | mountPath: /run 94 | - name: dbus-daemon-run 95 | mountPath: /run/dbus 96 | readOnly: true 97 | - name: nfs-ganesha-lib 98 | mountPath: /var/lib/nfs/ganesha 99 | - name: nfs-ganesha-tmp 100 | mountPath: /tmp 101 | - name: nfs-ganesha-config 102 | mountPath: /etc/ganesha/ganesha.conf.d 103 | readOnly: true 104 | 105 | - name: ganesha-config-reload 106 | image: ${GANESHA_CONFIG_RELOAD_IMAGE}:${GANESHA_CONFIG_RELOAD_TAG} 107 | args: 108 | - -mode=configmap 109 | - -pid=/run/ganesha/ganesha.pid 110 | - /etc/ganesha/ganesha.conf.d 111 | securityContext: 112 | allowPrivilegeEscalation: false 113 | readOnlyRootFilesystem: true 114 | capabilities: 115 | drop: 116 | - ALL 117 | terminationMessagePolicy: FallbackToLogsOnError 118 | volumeMounts: 119 | - name: run 120 | mountPath: /run 121 | readOnly: true 122 | - name: nfs-ganesha-config 123 | mountPath: /etc/ganesha/ganesha.conf.d 124 | readOnly: true 125 | 126 | - name: rpcbind 127 | image: ${RPCBIND_IMAGE}:${RPCBIND_TAG} 128 | securityContext: 129 | allowPrivilegeEscalation: false 130 | readOnlyRootFilesystem: true 131 | capabilities: 132 | drop: 133 | - ALL 134 | add: 135 | - DAC_OVERRIDE 136 | - CHOWN 137 | - NET_BIND_SERVICE 138 | - SETGID 139 | - SETUID 140 | ports: 141 | - name: portmapper-tcp 142 | containerPort: ${PORTMAPPER_PORT} 143 | protocol: TCP 144 | - name: portmapper-udp 145 | containerPort: ${PORTMAPPER_PORT} 146 | protocol: UDP 147 | livenessProbe: 148 | tcpSocket: 149 | port: portmapper-tcp 150 | readinessProbe: 151 | exec: 152 | command: ["/healthcheck.sh"] 153 | timeoutSeconds: 10 154 | terminationMessagePolicy: FallbackToLogsOnError 155 | volumeMounts: 156 | - name: run 157 | mountPath: /run 158 | 159 | - name: rpc-statd 160 | image: ${RPC_STATD_IMAGE}:${RPC_STATD_TAG} 161 | env: 162 | - name: STATUS_PORT 163 | value: "${STATUS_PORT}" 164 | securityContext: 165 | allowPrivilegeEscalation: false 166 | readOnlyRootFilesystem: true 167 | capabilities: 168 | drop: 169 | - ALL 170 | add: 171 | - DAC_OVERRIDE 172 | - CHOWN 173 | - NET_BIND_SERVICE 174 | - SETGID 175 | - SETPCAP 176 | - SETUID 177 | ports: 178 | - name: status-tcp 179 | containerPort: ${STATUS_PORT} 180 | protocol: TCP 181 | - name: status-udp 182 | containerPort: ${STATUS_PORT} 183 | protocol: UDP 184 | livenessProbe: 185 | tcpSocket: 186 | port: status-tcp 187 | readinessProbe: 188 | exec: 189 | command: ["/healthcheck.sh"] 190 | timeoutSeconds: 10 191 | terminationMessagePolicy: FallbackToLogsOnError 192 | volumeMounts: 193 | - name: run 194 | mountPath: /run 195 | - name: rpc-statd-lib 196 | mountPath: /var/lib/nfs 197 | 198 | - name: dbus-daemon 199 | image: ${DBUS_DAEMON_IMAGE}:${DBUS_DAEMON_TAG} 200 | securityContext: 201 | allowPrivilegeEscalation: false 202 | readOnlyRootFilesystem: true 203 | capabilities: 204 | drop: 205 | - ALL 206 | add: 207 | - SETGID 208 | - SETPCAP 209 | - SETUID 210 | livenessProbe: 211 | exec: 212 | command: ["/healthcheck.sh"] 213 | timeoutSeconds: 10 214 | terminationMessagePolicy: FallbackToLogsOnError 215 | volumeMounts: 216 | - name: dbus-daemon-run 217 | mountPath: /run/dbus 218 | - name: dbus-daemon-lib 219 | mountPath: /var/lib/dbus 220 | 221 | volumes: 222 | - name: nfs-ganesha-lib 223 | emptyDir: 224 | - name: nfs-ganesha-tmp 225 | emptyDir: 226 | medium: Memory 227 | - name: nfs-ganesha-config 228 | configMap: 229 | name: nfs-ganesha 230 | - name: run 231 | emptyDir: 232 | medium: Memory 233 | - name: rpc-statd-lib 234 | emptyDir: 235 | - name: dbus-daemon-run 236 | emptyDir: 237 | medium: Memory 238 | - name: dbus-daemon-lib 239 | emptyDir: 240 | EOF 241 | -------------------------------------------------------------------------------- /deploy/podman/hack/mkmanifest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ue -o pipefail 4 | 5 | source $1 6 | 7 | cat << EOF 8 | --- 9 | apiVersion: v1 10 | kind: Pod 11 | metadata: 12 | name: nfs-ganesha 13 | labels: 14 | app: contained-ganesha 15 | component: nfs-ganesha 16 | spec: 17 | automountServiceAccountToken: false 18 | shareProcessNamespace: true 19 | 20 | containers: 21 | - name: nfs-ganesha 22 | image: localhost/${LOCAL_NFS_GANESHA_IMAGE}:${LOCAL_NFS_GANESHA_TAG} 23 | env: 24 | - name: NLOCKMGR_PORT 25 | value: "${NLOCKMGR_PORT}" 26 | - name: RQUOTAD_PORT 27 | value: "${RQUOTAD_PORT}" 28 | - name: NFS_PORT 29 | value: "${NFS_PORT}" 30 | - name: MOUNTD_PORT 31 | value: "${MOUNTD_PORT}" 32 | securityContext: 33 | allowPrivilegeEscalation: false 34 | readOnlyRootFilesystem: true 35 | capabilities: 36 | drop: 37 | - ALL 38 | add: 39 | - CHOWN 40 | - DAC_OVERRIDE 41 | - DAC_READ_SEARCH 42 | - FOWNER 43 | - FSETID 44 | - NET_BIND_SERVICE 45 | - SETGID 46 | - SETUID 47 | ports: 48 | - name: nlockmgr-tcp 49 | containerPort: ${NLOCKMGR_PORT} 50 | protocol: TCP 51 | - name: nlockmgr-udp 52 | containerPort: ${NLOCKMGR_PORT} 53 | protocol: UDP 54 | - name: rquotad-tcp 55 | containerPort: ${RQUOTAD_PORT} 56 | protocol: TCP 57 | - name: rquotad-udp 58 | containerPort: ${RQUOTAD_PORT} 59 | protocol: UDP 60 | - name: nfs-tcp 61 | containerPort: ${NFS_PORT} 62 | protocol: TCP 63 | - name: nfs-udp 64 | containerPort: ${NFS_PORT} 65 | protocol: UDP 66 | - name: mountd-tcp 67 | containerPort: ${MOUNTD_PORT} 68 | protocol: TCP 69 | - name: mountd-udp 70 | containerPort: ${MOUNTD_PORT} 71 | protocol: UDP 72 | # Podman execs nc in the container for a TCP socket probe, 73 | # and we don't install that tool in the containers, so the 74 | # liveness check fails. 75 | #livenessProbe: 76 | # tcpSocket: 77 | # port: nfs-tcp 78 | readinessProbe: 79 | exec: 80 | command: ["/healthcheck.sh"] 81 | timeoutSeconds: 10 82 | terminationMessagePolicy: FallbackToLogsOnError 83 | volumeMounts: 84 | - name: run 85 | mountPath: /run 86 | - name: dbus-daemon-run 87 | mountPath: /run/dbus 88 | readOnly: true 89 | - name: nfs-ganesha-lib 90 | mountPath: /var/lib/nfs/ganesha 91 | - name: nfs-ganesha-tmp 92 | mountPath: /tmp 93 | - name: nfs-ganesha-config 94 | mountPath: /etc/ganesha/ganesha.conf.d 95 | readOnly: true 96 | 97 | - name: ganesha-config-reload 98 | image: localhost/${LOCAL_GANESHA_CONFIG_RELOAD_IMAGE}:${LOCAL_GANESHA_CONFIG_RELOAD_TAG} 99 | args: 100 | - -mode=configmap 101 | - -pid=/run/ganesha/ganesha.pid 102 | - /etc/ganesha/ganesha.conf.d 103 | securityContext: 104 | allowPrivilegeEscalation: false 105 | readOnlyRootFilesystem: true 106 | capabilities: 107 | drop: 108 | - ALL 109 | terminationMessagePolicy: FallbackToLogsOnError 110 | volumeMounts: 111 | - name: run 112 | mountPath: /run 113 | readOnly: true 114 | - name: nfs-ganesha-config 115 | mountPath: /etc/ganesha/ganesha.conf.d 116 | readOnly: true 117 | 118 | - name: rpcbind 119 | image: localhost/${LOCAL_RPCBIND_IMAGE}:${LOCAL_RPCBIND_TAG} 120 | securityContext: 121 | allowPrivilegeEscalation: false 122 | readOnlyRootFilesystem: true 123 | capabilities: 124 | drop: 125 | - ALL 126 | add: 127 | - DAC_OVERRIDE 128 | - CHOWN 129 | - FOWNER 130 | - NET_BIND_SERVICE 131 | - SETGID 132 | - SETUID 133 | ports: 134 | - name: portmapper-tcp 135 | containerPort: ${PORTMAPPER_PORT} 136 | protocol: TCP 137 | - name: portmapper-udp 138 | containerPort: ${PORTMAPPER_PORT} 139 | protocol: UDP 140 | #livenessProbe: 141 | # tcpSocket: 142 | # port: portmapper-tcp 143 | readinessProbe: 144 | exec: 145 | command: ["/healthcheck.sh"] 146 | timeoutSeconds: 10 147 | terminationMessagePolicy: FallbackToLogsOnError 148 | volumeMounts: 149 | - name: run 150 | mountPath: /run 151 | 152 | - name: rpc-statd 153 | image: localhost/${LOCAL_RPC_STATD_IMAGE}:${LOCAL_RPC_STATD_TAG} 154 | env: 155 | - name: STATUS_PORT 156 | value: "${STATUS_PORT}" 157 | securityContext: 158 | allowPrivilegeEscalation: false 159 | readOnlyRootFilesystem: true 160 | capabilities: 161 | drop: 162 | - ALL 163 | add: 164 | - DAC_OVERRIDE 165 | - CHOWN 166 | - NET_BIND_SERVICE 167 | - SETGID 168 | - SETPCAP 169 | - SETUID 170 | ports: 171 | - name: status-tcp 172 | containerPort: ${STATUS_PORT} 173 | protocol: TCP 174 | - name: status-udp 175 | containerPort: ${STATUS_PORT} 176 | protocol: UDP 177 | #livenessProbe: 178 | # tcpSocket: 179 | # port: status-tcp 180 | readinessProbe: 181 | exec: 182 | command: ["/healthcheck.sh"] 183 | timeoutSeconds: 10 184 | terminationMessagePolicy: FallbackToLogsOnError 185 | volumeMounts: 186 | - name: run 187 | mountPath: /run 188 | - name: rpc-statd-lib 189 | mountPath: /var/lib/nfs 190 | 191 | - name: dbus-daemon 192 | image: localhost/${LOCAL_DBUS_DAEMON_IMAGE}:${LOCAL_DBUS_DAEMON_TAG} 193 | securityContext: 194 | allowPrivilegeEscalation: false 195 | readOnlyRootFilesystem: true 196 | capabilities: 197 | drop: 198 | - ALL 199 | add: 200 | - SETGID 201 | - SETPCAP 202 | - SETUID 203 | livenessProbe: 204 | exec: 205 | command: ["/healthcheck.sh"] 206 | timeoutSeconds: 10 207 | terminationMessagePolicy: FallbackToLogsOnError 208 | volumeMounts: 209 | - name: dbus-daemon-run 210 | mountPath: /run/dbus 211 | - name: dbus-daemon-lib 212 | mountPath: /var/lib/dbus 213 | 214 | volumes: 215 | - name: nfs-ganesha-lib 216 | emptyDir: {} 217 | - name: nfs-ganesha-tmp 218 | emptyDir: 219 | medium: Memory 220 | - name: nfs-ganesha-config 221 | configMap: 222 | name: nfs-ganesha 223 | - name: run 224 | emptyDir: {} 225 | # There's a bug in podman when sharing Memory-backed EmptyDir 226 | # volumes between containers: new volumes are created for each 227 | # container instead of a single tmpfs being shared. 228 | # Hence falling back to regular mounts. 229 | # 230 | # See: https://github.com/containers/podman/issues/24930 231 | #medium: Memory 232 | - name: rpc-statd-lib 233 | emptyDir: {} 234 | - name: dbus-daemon-run 235 | emptyDir: {} 236 | # See note above. 237 | #medium: Memory 238 | - name: dbus-daemon-lib 239 | emptyDir: {} 240 | --- 241 | apiVersion: v1 242 | kind: ConfigMap 243 | metadata: 244 | name: nfs-ganesha 245 | labels: 246 | app: contained-ganesha 247 | component: nfs-ganesha 248 | data: 249 | local.conf: '' 250 | exports.conf: | 251 | EXPORT 252 | { 253 | Export_ID=1; 254 | Path = "/mem"; 255 | Pseudo = "/mem"; 256 | Access_Type = RW; 257 | FSAL { 258 | Name = MEM; 259 | } 260 | } 261 | EOF 262 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # {{{ Common base image 2 | ARG BASE_IMAGE=docker.io/almalinux 3 | ARG BASE_IMAGE_TAG=9.5-20250307 4 | FROM ${BASE_IMAGE}:${BASE_IMAGE_TAG} as base 5 | # Common labels across all images 6 | LABEL org.opencontainers.image.authors="Nicolas Trangez " \ 7 | org.opencontainers.image.url="https://github.com/NicolasT/contained-ganesha" \ 8 | org.opencontainers.image.source="https://github.com/NicolasT/contained-ganesha.git" \ 9 | org.opencontainers.image.vendor="Nicolas Trangez " \ 10 | \ 11 | org.label-schema.schema-version="1.0" \ 12 | org.label-schema.url="https://github.com/NicolasT/contained-ganesha" \ 13 | org.label-schema.vcs-url="https://github.com/NicolasT/contained-ganesha.git" \ 14 | org.label-schema.vendor="Nicolas Trangez " \ 15 | org.label-schema.docker.cmd.debug="docker exec -it \$CONTAINER /bin/bash" \ 16 | \ 17 | app.kubernetes.io/part-of="contained-ganesha" 18 | # }}} 19 | 20 | # {{{ A layer which has `rpcbind` installed but no entrypoint etc. 21 | FROM base as intermediate-rpcbind 22 | 23 | VOLUME ["/run"] 24 | RUN dnf install -y \ 25 | rpcbind \ 26 | && \ 27 | dnf clean all 28 | # }}} 29 | 30 | # {{{ A layer which has `nfs-utils` installed 31 | FROM intermediate-rpcbind as intermediate-nfs-utils 32 | 33 | VOLUME ["/var/lib/nfs"] 34 | RUN dnf install -y \ 35 | nfs-utils \ 36 | && \ 37 | dnf clean all 38 | # }}} 39 | 40 | # {{{ The `rpcbind` images 41 | FROM intermediate-rpcbind as rpcbind 42 | 43 | COPY images/rpcbind/entrypoint.sh images/rpcbind/healthcheck.sh images/rpcbind/README.md / 44 | ENTRYPOINT ["/entrypoint.sh"] 45 | CMD ["-d", "-s"] 46 | HEALTHCHECK CMD ["/healthcheck.sh"] 47 | 48 | ARG PORTMAPPER_PORT 49 | EXPOSE ${PORTMAPPER_PORT}/tcp 50 | EXPOSE ${PORTMAPPER_PORT}/udp 51 | 52 | LABEL org.opencontainers.image.documentation="https://github.com/NicolasT/contained-ganesha/blob/master/images/rpcbind/README.md" \ 53 | org.opencontainers.image.licenses="BSD-3-Clause" \ 54 | org.opencontainers.image.title="rpcbind" \ 55 | org.opencontainers.image.description="The rpcbind utility is a server that converts RPC program numbers into universal addresses. It must be running on the host to be able to make RPC calls on a server on that machine." \ 56 | \ 57 | org.label-schema.license="BSD-3-Clause" \ 58 | org.label-schema.name="rpcbind" \ 59 | org.label-schema.description="The rpcbind utility is a server that converts RPC program numbers into universal addresses. It must be running on the host to be able to make RPC calls on a server on that machine." \ 60 | org.label-schema.usage="https://github.com/NicolasT/contained-ganesha/blob/master/images/rpcbind/README.md" \ 61 | org.label-schema.docker.params="" \ 62 | org.label-schema.docker.cmd="docker run -d -p ${PORTMAPPER_PORT}:${PORTMAPPER_PORT} --cap-drop ALL --cap-add DAC_OVERRIDE --cap-add CHOWN --cap-add NET_BIND_SERVICE --cap-add SETGID --cap-add SETUID --read-only --tmpfs /run contained-ganesha/rpcbind" \ 63 | \ 64 | app.kubernetes.io/name="rpcbind" \ 65 | app.kubernetes.io/component="portmapper" 66 | # }}} 67 | 68 | # {{{ The `rpc.statd` image 69 | FROM intermediate-nfs-utils as rpc.statd 70 | 71 | COPY images/rpc.statd/entrypoint.sh images/rpc.statd/healthcheck.sh images/rpc.statd/README.md / 72 | ENTRYPOINT ["/entrypoint.sh"] 73 | HEALTHCHECK CMD ["/healthcheck.sh"] 74 | 75 | ARG STATUS_PORT 76 | ENV STATUS_PORT=${STATUS_PORT} 77 | EXPOSE ${STATUS_PORT}/tcp 78 | EXPOSE ${STATUS_PORT}/udp 79 | 80 | LABEL org.opencontainers.image.documentation="https://github.com/NicolasT/contained-ganesha/blob/master/images/rpc.statd/README.md" \ 81 | org.opencontainers.image.licenses="BSD-3-Clause" \ 82 | org.opencontainers.image.title="rpc.statd" \ 83 | org.opencontainers.image.description="NSM service daemon" \ 84 | \ 85 | org.label-schema.license="BSD-3-Clause" \ 86 | org.label-schema.name="rpc.statd" \ 87 | org.label-schema.description="NSM service daemon" \ 88 | org.label-schema.usage="https://github.com/NicolasT/contained-ganesha/blob/master/images/rpc.statd/README.md" \ 89 | org.label-schema.docker.params="STATUS_PORT=port for the status service to bind on" \ 90 | org.label-schema.docker.cmd="docker run -d -p ${STATUS_PORT}:${STATUS_PORT} --cap-drop ALL --cap-add DAC_OVERRIDE --cap-add CHOWN --cap-add NET_BIND_SERVICE --cap-add SETGID --cap-add SETPCAP --cap-add SETUID --read-only --tmpfs /run --tmpfs /var/lib/nfs contained-ganesha/rpc.statd" \ 91 | \ 92 | app.kubernetes.io/name="rpc.statd" \ 93 | app.kubernetes.io/component="status" 94 | # }}} 95 | 96 | # {{{ The `dbus-daemon` image 97 | FROM base as dbus-daemon 98 | 99 | VOLUME ["/run", "/var/lib/dbus"] 100 | 101 | # Note: the `nfs-ganesha` package needs to be installed for the DBus policy 102 | # files to be put in place. 103 | RUN /usr/bin/sed -i 's/ systemd//g' /etc/nsswitch.conf && \ 104 | \ 105 | dnf install -y \ 106 | centos-release-nfs-ganesha5 \ 107 | && \ 108 | dnf install -y \ 109 | dbus-daemon \ 110 | dbus-tools \ 111 | nfs-ganesha \ 112 | && \ 113 | dnf clean all 114 | 115 | COPY images/dbus-daemon/entrypoint.sh images/dbus-daemon/healthcheck.sh images/dbus-daemon/README.md / 116 | ENTRYPOINT ["/entrypoint.sh"] 117 | CMD ["--system"] 118 | HEALTHCHECK CMD ["/healthcheck.sh"] 119 | 120 | LABEL org.opencontainers.image.documentation="https://github.com/NicolasT/contained-ganesha/blob/master/images/dbus-daemon/README.md" \ 121 | org.opencontainers.image.licenses="(GPL-2.0+ or AFL-2.1) and GPL-2.0+" \ 122 | org.opencontainers.image.title="dbus-daemon" \ 123 | org.opencontainers.image.description="D-BUS is a system for sending messages between applications. It is used both for the system-wide message bus service, and as a per-user-login-session messaging facility." \ 124 | \ 125 | org.label-schema.license="(GPL-2.0+ or AFL-2.1) and GPL-2.0+" \ 126 | org.label-schema.name="dbus-daemon" \ 127 | org.label-schema.description="D-BUS is a system for sending messages between applications. It is used both for the system-wide message bus service, and as a per-user-login-session messaging facility." \ 128 | org.label-schema.usage="https://github.com/NicolasT/contained-ganesha/blob/master/images/dbus-daemon/README.md" \ 129 | org.label-schema.docker.params="" \ 130 | org.label-schema.docker.cmd="docker run -d --cap-drop ALL --cap-add SETGID --cap-add SETPCAP --cap-add SETUID --read-only --tmpfs /run/dbus --tmpfs /var/lib/dbus contained-ganesha/dbus-daemon" \ 131 | \ 132 | app.kubernetes.io/name="dbus-daemon" \ 133 | app.kubernetes.io/component="system-bus" 134 | # }}} 135 | 136 | # {{{ The `nfs-ganesha` image 137 | FROM intermediate-nfs-utils as nfs-ganesha 138 | 139 | # Disable systemd NSS plugin 140 | RUN /usr/bin/sed -i 's/ systemd//g' /etc/nsswitch.conf && \ 141 | \ 142 | dnf install -y \ 143 | centos-release-nfs-ganesha5 \ 144 | && \ 145 | dnf install -y \ 146 | nfs-ganesha \ 147 | nfs-ganesha-mem \ 148 | nfs-ganesha-vfs \ 149 | nfs-ganesha-utils \ 150 | nfs-utils \ 151 | && \ 152 | dnf clean all && \ 153 | \ 154 | rm /etc/ganesha/ganesha.conf && \ 155 | \ 156 | /usr/bin/install -v --directory --group=root --mode 0700 --owner=root /etc/ganesha/ganesha.conf.d && \ 157 | touch /etc/ganesha/ganesha.conf.d/local.conf && \ 158 | touch /etc/ganesha/ganesha.conf.d/exports.conf 159 | 160 | COPY images/nfs-ganesha/ganesha.conf.sh /etc/ganesha/ganesha.conf.sh 161 | 162 | COPY images/nfs-ganesha/entrypoint.sh images/nfs-ganesha/healthcheck.sh images/nfs-ganesha/README.md / 163 | ENTRYPOINT ["/entrypoint.sh"] 164 | CMD ["-N", "NIV_INFO"] 165 | HEALTHCHECK CMD ["/healthcheck.sh"] 166 | 167 | ARG NLOCKMGR_PORT 168 | ENV NLOCKMGR_PORT=${NLOCKMGR_PORT} 169 | EXPOSE ${NLOCKMGR_PORT}/tcp 170 | EXPOSE ${NLOCKMGR_PORT}/udp 171 | ARG RQUOTAD_PORT 172 | ENV RQUOTAD_PORT=${RQUOTAD_PORT} 173 | EXPOSE ${RQUOTAD_PORT}/tcp 174 | EXPOSE ${RQUOTAD_PORT}/udp 175 | ARG NFS_PORT 176 | ENV NFS_PORT=${NFS_PORT} 177 | EXPOSE ${NFS_PORT}/tcp 178 | EXPOSE ${NFS_PORT}/udp 179 | ARG MOUNTD_PORT 180 | ENV MOUNTD_PORT=${MOUNTD_PORT} 181 | EXPOSE ${MOUNTD_PORT}/tcp 182 | EXPOSE ${MOUNTD_PORT}/udp 183 | 184 | LABEL org.opencontainers.image.documentation="https://github.com/NicolasT/contained-ganesha/blob/master/images/nfs-ganesha/README.md" \ 185 | org.opencontainers.image.licenses="LGPL-3.0" \ 186 | org.opencontainers.image.title="nfs-ganesha" \ 187 | org.opencontainers.image.description="NFS-GANESHA is a NFS Server running in user space. It comes with various back-end modules (called FSALs) provided as shared objects to support different file systems and name-spaces." \ 188 | \ 189 | org.label-schema.license="LGPL-3.0" \ 190 | org.label-schema.name="nfs-ganesha" \ 191 | org.label-schema.description="NFS-GANESHA is a NFS Server running in user space. It comes with various back-end modules (called FSALs) provided as shared objects to support different file systems and name-spaces." \ 192 | org.label-schema.usage="https://github.com/NicolasT/contained-ganesha/blob/master/images/nfs-ganesha/README.md" \ 193 | org.label-schema.docker.params="NLOCKMGR_PORT=port for the nlockmgr service to bind on, RQUOTAD_PORT=port for the rquotad service to bind on, NFS_PORT=port for the nfs service to bind on, MOUNTD_PORT=port for the mountd service to bind on" \ 194 | org.label-schema.docker.cmd="docker run -d -p ${NLOCKMGR_PORT}:${NLOCKMGR_PORT} -p ${RQUOTAD_PORT}:${RQUOTAD_PORT} -p ${NFS_PORT}:${NFS_PORT} -p ${MOUNTD_PORT}:${MOUNTD_PORT} --cap-drop ALL --cap-add CHOWN --cap-add DAC_OVERRIDE --cap-add DAC_READ_SEARCH --cap-add FOWNER --cap-add FSETID --cap-add NET_BIND_SERVICE --cap-add SETGID --cap-add SETUID --read-only -v ./exports.conf:/etc/ganesha/ganesha.conf.d/exports.conf:ro --tmpfs /run -v dbus-daemon-run:/run/dbus:ro --tmpfs /var/lib/nfs/ganesha contained-ganesha/nfs-ganesha" \ 195 | \ 196 | app.kubernetes.io/name="nfs-ganesha" \ 197 | app.kubernetes.io/component="nfs" 198 | # }}} 199 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /.github/workflows/cicd.yaml: -------------------------------------------------------------------------------- 1 | name: CI/CD 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | check-ganesha-config-reload: 13 | name: Check ganesha-config-reload 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v6.0.1 18 | - name: Install Go 19 | uses: actions/setup-go@v6.1.0 20 | with: 21 | cache-dependency-path: | 22 | ganesha-config-reload/go.sum 23 | - name: Test 24 | run: | 25 | go test -v ./... 26 | working-directory: ./ganesha-config-reload 27 | 28 | build: 29 | name: Build container images 30 | needs: 31 | - check-ganesha-config-reload 32 | strategy: 33 | matrix: 34 | docker: [docker, podman] 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Checkout 38 | uses: actions/checkout@v6.0.1 39 | - name: Fetch tags 40 | run: | 41 | git fetch --depth=1 origin +refs/tags/*:refs/tags/* 42 | git fetch --prune --unshallow origin HEAD 43 | - name: Login to container image registry 44 | run: | 45 | echo ${GITHUB_TOKEN} | ${DOCKER} login -u ${GITHUB_ACTOR} --password-stdin docker.pkg.github.com 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | DOCKER: ${{ matrix.docker }} 49 | - name: Pull container images from registry 50 | run: | 51 | source .env 52 | ${DOCKER} pull ${RPCBIND_IMAGE}:${RPCBIND_TAG} || true 53 | ${DOCKER} pull ${RPC_STATD_IMAGE}:${RPC_STATD_TAG} || true 54 | ${DOCKER} pull ${DBUS_DAEMON_IMAGE}:${DBUS_DAEMON_TAG} || true 55 | ${DOCKER} pull ${NFS_GANESHA_IMAGE}:${NFS_GANESHA_TAG} || true 56 | ${DOCKER} pull ${GANESHA_CONFIG_RELOAD_IMAGE}:${GANESHA_CONFIG_RELOAD_TAG} || true 57 | ${DOCKER} pull ${CONTAINED_GANESHA_TEST_IMAGE}:${CONTAINED_GANESHA_TEST_TAG} || true 58 | env: 59 | DOCKER: ${{ matrix.docker }} 60 | - name: Build container images 61 | run: | 62 | make containers container-contained-ganesha-test DOCKER=${DOCKER} 63 | env: 64 | DOCKER: ${{ matrix.docker }} 65 | - name: Export container images 66 | run: | 67 | mkdir container-images 68 | source .env 69 | 70 | if [ ${DOCKER} = "podman" ]; then 71 | EXTRA_ARGS="--multi-image-archive" 72 | fi 73 | 74 | ${DOCKER} save \ 75 | ${EXTRA_ARGS:-} \ 76 | ${LOCAL_RPCBIND_IMAGE}:${LOCAL_RPCBIND_TAG} \ 77 | ${LOCAL_RPC_STATD_IMAGE}:${LOCAL_RPC_STATD_TAG} \ 78 | ${LOCAL_DBUS_DAEMON_IMAGE}:${LOCAL_DBUS_DAEMON_TAG} \ 79 | ${LOCAL_NFS_GANESHA_IMAGE}:${LOCAL_NFS_GANESHA_TAG} \ 80 | ${LOCAL_GANESHA_CONFIG_RELOAD_IMAGE}:${LOCAL_GANESHA_CONFIG_RELOAD_TAG} \ 81 | ${LOCAL_CONTAINED_GANESHA_TEST_IMAGE}:${LOCAL_CONTAINED_GANESHA_TEST_TAG} \ 82 | | gzip > container-images/build.tar.gz 83 | env: 84 | DOCKER: ${{ matrix.docker }} 85 | - name: Archive artifacts 86 | uses: actions/upload-artifact@v6.0.0 87 | with: 88 | name: container-images-${{ matrix.docker }} 89 | path: container-images 90 | if-no-files-found: error 91 | 92 | test-docker-compose: 93 | name: Test docker-compose 94 | needs: 95 | - build 96 | strategy: 97 | matrix: 98 | docker: [docker, podman] 99 | runs-on: ubuntu-latest 100 | steps: 101 | - name: Checkout 102 | uses: actions/checkout@v6.0.1 103 | - name: Retrieve artifacts 104 | uses: actions/download-artifact@v7.0.0 105 | with: 106 | name: container-images-${{ matrix.docker }} 107 | path: container-images/ 108 | - name: Import container images 109 | run: | 110 | cat container-images/build.tar.gz | gzip -d | docker load 111 | - name: Re-tag container images 112 | if: ${{ matrix.docker == 'podman' }} 113 | run: | 114 | source .env 115 | 116 | docker tag localhost/${LOCAL_RPCBIND_IMAGE}:${LOCAL_RPCBIND_TAG} ${LOCAL_RPCBIND_IMAGE}:${LOCAL_RPCBIND_TAG} 117 | docker tag localhost/${LOCAL_RPC_STATD_IMAGE}:${LOCAL_RPC_STATD_TAG} ${LOCAL_RPC_STATD_IMAGE}:${LOCAL_RPC_STATD_TAG} 118 | docker tag localhost/${LOCAL_DBUS_DAEMON_IMAGE}:${LOCAL_DBUS_DAEMON_TAG} ${LOCAL_DBUS_DAEMON_IMAGE}:${LOCAL_DBUS_DAEMON_TAG} 119 | docker tag localhost/${LOCAL_NFS_GANESHA_IMAGE}:${LOCAL_NFS_GANESHA_TAG} ${LOCAL_NFS_GANESHA_IMAGE}:${LOCAL_NFS_GANESHA_TAG} 120 | docker tag localhost/${LOCAL_GANESHA_CONFIG_RELOAD_IMAGE}:${LOCAL_GANESHA_CONFIG_RELOAD_TAG} ${LOCAL_GANESHA_CONFIG_RELOAD_IMAGE}:${LOCAL_GANESHA_CONFIG_RELOAD_TAG} 121 | docker tag localhost/${LOCAL_CONTAINED_GANESHA_TEST_IMAGE}:${LOCAL_CONTAINED_GANESHA_TEST_TAG} ${LOCAL_CONTAINED_GANESHA_TEST_IMAGE}:${LOCAL_CONTAINED_GANESHA_TEST_TAG} 122 | - name: Test 123 | run: | 124 | make -C deploy/docker-compose check-nobuild 125 | - name: Cleanup 126 | run: | 127 | make -C deploy/docker-compose down 128 | 129 | test-kubernetes: 130 | name: Test Kubernetes (using Kind) 131 | needs: 132 | - build 133 | strategy: 134 | matrix: 135 | docker: [docker, podman] 136 | runs-on: ubuntu-latest 137 | steps: 138 | - name: Checkout 139 | uses: actions/checkout@v6.0.1 140 | - name: Retrieve artifacts 141 | uses: actions/download-artifact@v7.0.0 142 | with: 143 | name: container-images-${{ matrix.docker }} 144 | path: container-images/ 145 | - name: Build manifests 146 | run: | 147 | make -C deploy/kubernetes manifests 148 | - name: Import container images 149 | run: | 150 | cat container-images/build.tar.gz | gzip -d | docker load 151 | - name: Re-tag container images 152 | if: ${{ matrix.docker == 'podman' }} 153 | run: | 154 | source .env 155 | 156 | docker tag localhost/${LOCAL_RPCBIND_IMAGE}:${LOCAL_RPCBIND_TAG} ${LOCAL_RPCBIND_IMAGE}:${LOCAL_RPCBIND_TAG} 157 | docker tag localhost/${LOCAL_RPC_STATD_IMAGE}:${LOCAL_RPC_STATD_TAG} ${LOCAL_RPC_STATD_IMAGE}:${LOCAL_RPC_STATD_TAG} 158 | docker tag localhost/${LOCAL_DBUS_DAEMON_IMAGE}:${LOCAL_DBUS_DAEMON_TAG} ${LOCAL_DBUS_DAEMON_IMAGE}:${LOCAL_DBUS_DAEMON_TAG} 159 | docker tag localhost/${LOCAL_NFS_GANESHA_IMAGE}:${LOCAL_NFS_GANESHA_TAG} ${LOCAL_NFS_GANESHA_IMAGE}:${LOCAL_NFS_GANESHA_TAG} 160 | docker tag localhost/${LOCAL_GANESHA_CONFIG_RELOAD_IMAGE}:${LOCAL_GANESHA_CONFIG_RELOAD_TAG} ${LOCAL_GANESHA_CONFIG_RELOAD_IMAGE}:${LOCAL_GANESHA_CONFIG_RELOAD_TAG} 161 | docker tag localhost/${LOCAL_CONTAINED_GANESHA_TEST_IMAGE}:${LOCAL_CONTAINED_GANESHA_TEST_TAG} ${LOCAL_CONTAINED_GANESHA_TEST_IMAGE}:${LOCAL_CONTAINED_GANESHA_TEST_TAG} 162 | - name: Start Kind cluster 163 | uses: helm/kind-action@v1.13.0 164 | with: 165 | cluster_name: kind 166 | - name: Import Docker images into Kind cluster 167 | run: | 168 | make -C deploy/kubernetes kind-load-docker-images-nobuild kind-load-cgt-image-nobuild 169 | - name: Deploy 170 | run: | 171 | make -C deploy/kubernetes kubectl-local-apply 172 | - name: Test 173 | run: | 174 | make -C deploy/kubernetes check 175 | - name: Cleanup 176 | run: | 177 | make -C deploy/kubernetes kubectl-local-delete 178 | 179 | test-podman: 180 | name: Test podman 181 | needs: 182 | - build 183 | strategy: 184 | matrix: 185 | docker: [docker, podman] 186 | runs-on: ubuntu-latest 187 | steps: 188 | - name: Checkout 189 | uses: actions/checkout@v6.0.1 190 | - name: Retrieve artifacts 191 | uses: actions/download-artifact@v7.0.0 192 | with: 193 | name: container-images-${{ matrix.docker }} 194 | path: container-images/ 195 | - name: Import container images 196 | run: | 197 | cat container-images/build.tar.gz | gzip -d | podman load 198 | - name: Test 199 | run: | 200 | make -C deploy/podman check-nobuild 201 | - name: Cleanup 202 | run: | 203 | make -C deploy/podman down 204 | 205 | tests-success: 206 | runs-on: ubuntu-latest 207 | if: ${{ always () }} 208 | needs: 209 | - test-docker-compose 210 | - test-kubernetes 211 | - test-podman 212 | steps: 213 | - name: Ensure tests succeeded 214 | if: "${{ (needs.test-docker-compose.result != 'success') || (needs.test-kubernetes.result != 'success') || (needs.test-podman.result != 'success') }}" 215 | run: | 216 | exit 1 217 | 218 | publish-github: 219 | name: Publish container images to GitHub Packages 220 | if: github.ref == 'refs/heads/master' 221 | needs: 222 | - build 223 | - tests-success 224 | runs-on: ubuntu-latest 225 | steps: 226 | - name: Checkout 227 | uses: actions/checkout@v6.0.1 228 | - name: Retrieve artifacts 229 | uses: actions/download-artifact@v7.0.0 230 | with: 231 | name: container-images-docker 232 | path: container-images/ 233 | - name: Import container images 234 | run: | 235 | cat container-images/build.tar.gz | gzip -d | docker load 236 | - name: Tag container images 237 | run: | 238 | source .env 239 | docker tag ${LOCAL_RPCBIND_IMAGE}:${LOCAL_RPCBIND_TAG} ${RPCBIND_IMAGE}:${RPCBIND_TAG} 240 | docker tag ${LOCAL_RPC_STATD_IMAGE}:${LOCAL_RPC_STATD_TAG} ${RPC_STATD_IMAGE}:${RPC_STATD_TAG} 241 | docker tag ${LOCAL_DBUS_DAEMON_IMAGE}:${LOCAL_DBUS_DAEMON_TAG} ${DBUS_DAEMON_IMAGE}:${DBUS_DAEMON_TAG} 242 | docker tag ${LOCAL_NFS_GANESHA_IMAGE}:${LOCAL_NFS_GANESHA_TAG} ${NFS_GANESHA_IMAGE}:${NFS_GANESHA_TAG} 243 | docker tag ${LOCAL_GANESHA_CONFIG_RELOAD_IMAGE}:${LOCAL_GANESHA_CONFIG_RELOAD_TAG} ${GANESHA_CONFIG_RELOAD_IMAGE}:${GANESHA_CONFIG_RELOAD_TAG} 244 | docker tag ${LOCAL_CONTAINED_GANESHA_TEST_IMAGE}:${LOCAL_CONTAINED_GANESHA_TEST_TAG} ${CONTAINED_GANESHA_TEST_IMAGE}:${CONTAINED_GANESHA_TEST_TAG} 245 | - name: Login to container image registry 246 | run: | 247 | echo ${GITHUB_TOKEN} | docker login -u ${GITHUB_ACTOR} --password-stdin docker.pkg.github.com 248 | env: 249 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 250 | - name: Push container images 251 | run: | 252 | source .env 253 | docker push ${RPCBIND_IMAGE}:${RPCBIND_TAG} 254 | docker push ${RPC_STATD_IMAGE}:${RPC_STATD_TAG} 255 | docker push ${DBUS_DAEMON_IMAGE}:${DBUS_DAEMON_TAG} 256 | docker push ${NFS_GANESHA_IMAGE}:${NFS_GANESHA_TAG} 257 | docker push ${GANESHA_CONFIG_RELOAD_IMAGE}:${GANESHA_CONFIG_RELOAD_TAG} 258 | docker push ${CONTAINED_GANESHA_TEST_IMAGE}:${CONTAINED_GANESHA_TEST_TAG} 259 | 260 | publish-dockerhub: 261 | name: Publish container images to DockerHub 262 | if: github.ref == 'refs/heads/master' 263 | needs: 264 | - build 265 | - tests-success 266 | runs-on: ubuntu-latest 267 | steps: 268 | - name: Checkout 269 | uses: actions/checkout@v6.0.1 270 | - name: Retrieve artifacts 271 | uses: actions/download-artifact@v7.0.0 272 | with: 273 | name: container-images-docker 274 | path: container-images/ 275 | - name: Import container images 276 | run: | 277 | cat container-images/build.tar.gz | gzip -d | docker load 278 | - name: Tag container images 279 | run: | 280 | source .env 281 | docker tag ${LOCAL_RPCBIND_IMAGE}:${LOCAL_RPCBIND_TAG} docker.io/nicolast/contained-ganesha-rpcbind:latest 282 | docker tag ${LOCAL_RPC_STATD_IMAGE}:${LOCAL_RPC_STATD_TAG} docker.io/nicolast/contained-ganesha-rpc.statd:latest 283 | docker tag ${LOCAL_DBUS_DAEMON_IMAGE}:${LOCAL_DBUS_DAEMON_TAG} docker.io/nicolast/contained-ganesha-dbus-daemon:latest 284 | docker tag ${LOCAL_NFS_GANESHA_IMAGE}:${LOCAL_NFS_GANESHA_TAG} docker.io/nicolast/contained-ganesha-nfs-ganesha:latest 285 | docker tag ${LOCAL_GANESHA_CONFIG_RELOAD_IMAGE}:${LOCAL_GANESHA_CONFIG_RELOAD_TAG} docker.io/nicolast/contained-ganesha-ganesha-config-reload:latest 286 | - name: Login to container image registry 287 | run: | 288 | echo ${DOCKERHUB_TOKEN} | docker login -u nicolast --password-stdin 289 | env: 290 | DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} 291 | - name: Push container images 292 | run: | 293 | docker push docker.io/nicolast/contained-ganesha-rpcbind:latest 294 | docker push docker.io/nicolast/contained-ganesha-rpc.statd:latest 295 | docker push docker.io/nicolast/contained-ganesha-dbus-daemon:latest 296 | docker push docker.io/nicolast/contained-ganesha-nfs-ganesha:latest 297 | docker push docker.io/nicolast/contained-ganesha-ganesha-config-reload:latest 298 | -------------------------------------------------------------------------------- /ganesha-config-reload/go.sum: -------------------------------------------------------------------------------- 1 | github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= 2 | github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 3 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 4 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 5 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 6 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 7 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 8 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 12 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 14 | github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 15 | github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= 16 | github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 17 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 18 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 19 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 20 | github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= 21 | github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 22 | github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= 23 | github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= 24 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 25 | github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 26 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 27 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 28 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 29 | github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= 30 | github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 31 | github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM= 32 | github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU= 33 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 34 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 35 | github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8= 36 | github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A= 37 | github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I= 38 | github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8= 39 | github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik= 40 | github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c= 41 | github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak= 42 | github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90= 43 | github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k= 44 | github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q= 45 | github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts= 46 | github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0= 47 | github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc= 48 | github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk= 49 | github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk= 50 | github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc= 51 | github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w= 52 | github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM= 53 | github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM= 54 | github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w= 55 | github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw= 56 | github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI= 57 | github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c= 58 | github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8= 59 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= 60 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 61 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 62 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 63 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 64 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 65 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 66 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 67 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 68 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 69 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 70 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 71 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 72 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 73 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 74 | github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= 75 | github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= 76 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 77 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 78 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 79 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 80 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 81 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 82 | github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= 83 | github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= 84 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 85 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 86 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 87 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 88 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 89 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 90 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 91 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 92 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 93 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 94 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 95 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 96 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 97 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 98 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 99 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 100 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 101 | github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= 102 | github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= 103 | github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= 104 | github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= 105 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 106 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 107 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 108 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 109 | github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= 110 | github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 111 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 112 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 113 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 114 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 115 | github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= 116 | github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= 117 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 118 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 119 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 120 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 121 | github.com/onsi/ginkgo/v2 v2.25.1 h1:Fwp6crTREKM+oA6Cz4MsO8RhKQzs2/gOIVOUscMAfZY= 122 | github.com/onsi/ginkgo/v2 v2.25.1/go.mod h1:ppTWQ1dh9KM/F1XgpeRqelR+zHVwV81DGRSDnFxK7Sk= 123 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 124 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 125 | github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= 126 | github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= 127 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 128 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 129 | github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8= 130 | github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U= 131 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 132 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 133 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 134 | github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= 135 | github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= 136 | github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 137 | github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 138 | github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= 139 | github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= 140 | github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= 141 | github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= 142 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 143 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 144 | github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= 145 | github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 146 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 147 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 148 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 149 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 150 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 151 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 152 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 153 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 154 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 155 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 156 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 157 | go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= 158 | go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= 159 | go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= 160 | go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= 161 | go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= 162 | go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= 163 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 164 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 165 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 166 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 167 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 168 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 169 | go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= 170 | go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= 171 | go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= 172 | go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 173 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 174 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 175 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 176 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 177 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 178 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 179 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 180 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 181 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 182 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 183 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 184 | golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= 185 | golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= 186 | golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= 187 | golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= 188 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 189 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 190 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 191 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 192 | golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= 193 | golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 194 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 195 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 196 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 197 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 198 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 199 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 200 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 201 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 202 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 203 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 204 | golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= 205 | golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 206 | golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= 207 | golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 208 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 209 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 210 | golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= 211 | golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= 212 | golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= 213 | golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 214 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 215 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 216 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 217 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 218 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 219 | golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= 220 | golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= 221 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 222 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 223 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 224 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 225 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= 226 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 227 | google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= 228 | google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= 229 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 230 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 231 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 232 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 233 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 234 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 235 | google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= 236 | google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= 237 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 238 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 239 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 240 | gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= 241 | gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 242 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 243 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 244 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 245 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 246 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 247 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 248 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 249 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 250 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 251 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 252 | k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= 253 | k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= 254 | k8s.io/apiextensions-apiserver v0.33.4 h1:rtq5SeXiDbXmSwxsF0MLe2Mtv3SwprA6wp+5qh/CrOU= 255 | k8s.io/apiextensions-apiserver v0.33.4/go.mod h1:mWXcZQkQV1GQyxeIjYApuqsn/081hhXPZwZ2URuJeSs= 256 | k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= 257 | k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= 258 | k8s.io/apiserver v0.33.4 h1:6N0TEVA6kASUS3owYDIFJjUH6lgN8ogQmzZvaFFj1/Y= 259 | k8s.io/apiserver v0.33.4/go.mod h1:8ODgXMnOoSPLMUg1aAzMFx+7wTJM+URil+INjbTZCok= 260 | k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= 261 | k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= 262 | k8s.io/component-base v0.33.4 h1:Jvb/aw/tl3pfgnJ0E0qPuYLT0NwdYs1VXXYQmSuxJGY= 263 | k8s.io/component-base v0.33.4/go.mod h1:567TeSdixWW2Xb1yYUQ7qk5Docp2kNznKL87eygY8Rc= 264 | k8s.io/component-helpers v0.34.1 h1:gWhH3CCdwAx5P3oJqZKb4Lg5FYZTWVbdWtOI8n9U4XY= 265 | k8s.io/component-helpers v0.34.1/go.mod h1:4VgnUH7UA/shuBur+OWoQC0xfb69sy/93ss0ybZqm3c= 266 | k8s.io/controller-manager v0.33.4 h1:HmlzmmNPu8H+cKEpAIRz0ptqpveKcj7KrCx9G+HXRAg= 267 | k8s.io/controller-manager v0.33.4/go.mod h1:CpO8RarLcs7zh0sE4pqz88quF3xU3Dc4ZDfshnB8hw4= 268 | k8s.io/csi-translation-lib v0.33.4 h1:LmiElxqQwISv0c2mdL3rswmPIIN6Qh+4Lv0bdKTTFoM= 269 | k8s.io/csi-translation-lib v0.33.4/go.mod h1:A4Kn6gTWX5EkxbHgtiDitNjDVvk2plie7lo8Hpa19Bg= 270 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 271 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 272 | k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= 273 | k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= 274 | k8s.io/kubelet v0.33.4 h1:+sbpLmSq+Y8DF/OQeyw75OpuiF60tvlYcmc/yjN+nl4= 275 | k8s.io/kubelet v0.33.4/go.mod h1:wboarviFRQld5rzZUjTliv7x00YVx+YhRd/p1OahX7Y= 276 | k8s.io/kubernetes v1.33.4 h1:T1d5FLUYm3/KyUeV7YJhKTR980zHCHb7K2xhCSo3lE8= 277 | k8s.io/kubernetes v1.33.4/go.mod h1:nrt8sldmckKz2fCZhgRX3SKfS2e+CzXATPv6ITNkU00= 278 | k8s.io/mount-utils v0.34.0 h1:f2QzKU8ZLz5cJ/TmRRZnfAVJ8EPsF+FT1I6pP/HA4gk= 279 | k8s.io/mount-utils v0.34.0/go.mod h1:MIjjYlqJ0ziYQg0MO09kc9S96GIcMkhF/ay9MncF0GA= 280 | k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= 281 | k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 282 | sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= 283 | sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= 284 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= 285 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= 286 | sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= 287 | sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 288 | sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= 289 | sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= 290 | sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= 291 | sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= 292 | --------------------------------------------------------------------------------