From 7688063849e71e77cbaee9b617c86e98038ea54a Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Sat, 2 Nov 2024 02:30:42 -0400 Subject: [PATCH] refactor for OLMv1 Signed-off-by: Joe Lanford --- go.mod | 69 +++-- go.sum | 180 ++++++------ internal/cmd/catalog.go | 1 + internal/cmd/catalog_add.go | 29 +- internal/cmd/catalog_content.go | 192 +++++++++++++ internal/cmd/catalog_list.go | 52 ++-- internal/cmd/catalog_remove.go | 6 +- internal/cmd/internal/olmv1/alpha_install.go | 30 -- .../cmd/internal/olmv1/alpha_uninstall.go | 34 --- internal/cmd/olmv1.go | 23 -- internal/cmd/operator_describe.go | 130 --------- internal/cmd/operator_install.go | 25 +- internal/cmd/operator_list.go | 55 ++-- internal/cmd/operator_list_available.go | 67 ----- internal/cmd/operator_list_operands.go | 29 +- internal/cmd/operator_uninstall.go | 78 +----- internal/cmd/operator_upgrade.go | 33 --- internal/cmd/root.go | 12 +- .../pkg/action/{ => v0}/action_suite_test.go | 2 +- internal/pkg/action/{ => v0}/catalog_add.go | 4 +- internal/pkg/action/{ => v0}/catalog_list.go | 2 +- .../pkg/action/{ => v0}/catalog_remove.go | 2 +- internal/pkg/action/{ => v0}/constants.go | 2 +- internal/pkg/action/{ => v0}/helpers.go | 2 +- .../pkg/action/{ => v0}/operator_install.go | 6 +- internal/pkg/action/{ => v0}/operator_list.go | 2 +- .../{ => v0}/operator_list_available.go | 4 +- .../pkg/action/{ => v0}/operator_uninstall.go | 7 +- .../{ => v0}/operator_uninstall_test.go | 6 +- .../pkg/action/{ => v0}/operator_upgrade.go | 2 +- internal/pkg/action/v1/action_suite_test.go | 13 + internal/pkg/action/v1/catalog_add.go | 122 ++++++++ internal/pkg/action/v1/catalog_content.go | 38 +++ internal/pkg/action/v1/catalog_list.go | 24 ++ internal/pkg/action/v1/catalog_remove.go | 25 ++ internal/pkg/action/v1/helpers.go | 71 +++++ internal/pkg/action/v1/operator_install.go | 194 +++++++++++++ internal/pkg/action/v1/operator_list.go | 24 ++ internal/pkg/action/v1/operator_uninstall.go | 70 +++++ internal/pkg/catalog/client.go | 260 ++++++++++++++++++ internal/pkg/experimental/action/operator.go | 9 - .../experimental/action/operator_install.go | 75 ----- .../experimental/action/operator_uninstall.go | 70 ----- .../catalogsource/catalogsource.go | 0 internal/pkg/{ => legacy}/operand/strategy.go | 0 internal/pkg/{ => legacy}/operator/package.go | 0 .../{ => legacy}/subscription/subscription.go | 0 pkg/action/config.go | 37 ++- pkg/action/{ => v0}/action_suite_test.go | 2 +- pkg/action/{ => v0}/operator_list_operands.go | 7 +- .../{ => v0}/operator_list_operands_test.go | 31 ++- pkg/action/v1/operator_list_operands.go | 116 ++++++++ 52 files changed, 1456 insertions(+), 818 deletions(-) create mode 100644 internal/cmd/catalog_content.go delete mode 100644 internal/cmd/internal/olmv1/alpha_install.go delete mode 100644 internal/cmd/internal/olmv1/alpha_uninstall.go delete mode 100644 internal/cmd/olmv1.go delete mode 100644 internal/cmd/operator_describe.go delete mode 100644 internal/cmd/operator_list_available.go delete mode 100644 internal/cmd/operator_upgrade.go rename internal/pkg/action/{ => v0}/action_suite_test.go (90%) rename internal/pkg/action/{ => v0}/catalog_add.go (98%) rename internal/pkg/action/{ => v0}/catalog_list.go (97%) rename internal/pkg/action/{ => v0}/catalog_remove.go (97%) rename internal/pkg/action/{ => v0}/constants.go (75%) rename internal/pkg/action/{ => v0}/helpers.go (99%) rename internal/pkg/action/{ => v0}/operator_install.go (98%) rename internal/pkg/action/{ => v0}/operator_list.go (97%) rename internal/pkg/action/{ => v0}/operator_list_available.go (95%) rename internal/pkg/action/{ => v0}/operator_uninstall.go (98%) rename internal/pkg/action/{ => v0}/operator_uninstall_test.go (98%) rename internal/pkg/action/{ => v0}/operator_upgrade.go (99%) create mode 100644 internal/pkg/action/v1/action_suite_test.go create mode 100644 internal/pkg/action/v1/catalog_add.go create mode 100644 internal/pkg/action/v1/catalog_content.go create mode 100644 internal/pkg/action/v1/catalog_list.go create mode 100644 internal/pkg/action/v1/catalog_remove.go create mode 100644 internal/pkg/action/v1/helpers.go create mode 100644 internal/pkg/action/v1/operator_install.go create mode 100644 internal/pkg/action/v1/operator_list.go create mode 100644 internal/pkg/action/v1/operator_uninstall.go create mode 100644 internal/pkg/catalog/client.go delete mode 100644 internal/pkg/experimental/action/operator.go delete mode 100644 internal/pkg/experimental/action/operator_install.go delete mode 100644 internal/pkg/experimental/action/operator_uninstall.go rename internal/pkg/{ => legacy}/catalogsource/catalogsource.go (100%) rename internal/pkg/{ => legacy}/operand/strategy.go (100%) rename internal/pkg/{ => legacy}/operator/package.go (100%) rename internal/pkg/{ => legacy}/subscription/subscription.go (100%) rename pkg/action/{ => v0}/action_suite_test.go (89%) rename pkg/action/{ => v0}/operator_list_operands.go (97%) rename pkg/action/{ => v0}/operator_list_operands_test.go (92%) create mode 100644 pkg/action/v1/operator_list_operands.go diff --git a/go.mod b/go.mod index 59ec355..15329a6 100644 --- a/go.mod +++ b/go.mod @@ -1,24 +1,25 @@ module github.com/operator-framework/kubectl-operator -go 1.22.5 +go 1.23 require ( - github.com/containerd/containerd v1.7.22 + github.com/Masterminds/semver/v3 v3.3.0 + github.com/containerd/containerd v1.7.23 github.com/containerd/platforms v0.2.1 github.com/onsi/ginkgo v1.16.5 - github.com/onsi/gomega v1.34.2 + github.com/onsi/gomega v1.35.1 github.com/opencontainers/image-spec v1.1.0 github.com/operator-framework/api v0.27.0 - github.com/operator-framework/operator-controller v0.15.0 + github.com/operator-framework/catalogd v1.0.0 + github.com/operator-framework/operator-controller v1.0.0 github.com/operator-framework/operator-lifecycle-manager v0.23.1 - github.com/operator-framework/operator-registry v1.47.0 - github.com/sirupsen/logrus v1.9.3 + github.com/operator-framework/operator-registry v1.48.0 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 - k8s.io/api v0.31.1 - k8s.io/apiextensions-apiserver v0.31.1 - k8s.io/apimachinery v0.31.1 - k8s.io/client-go v0.31.1 + k8s.io/api v0.31.2 + k8s.io/apiextensions-apiserver v0.31.2 + k8s.io/apimachinery v0.31.2 + k8s.io/client-go v0.31.2 sigs.k8s.io/controller-runtime v0.19.0 sigs.k8s.io/yaml v1.4.0 ) @@ -33,28 +34,31 @@ require ( github.com/containerd/cgroups/v3 v3.0.3 // indirect github.com/containerd/containerd/api v1.7.19 // indirect github.com/containerd/continuity v0.4.2 // indirect - github.com/containerd/errdefs v0.1.0 // indirect + github.com/containerd/errdefs v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/ttrpc v1.2.5 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect - github.com/containers/common v0.60.2 // indirect + github.com/containers/common v0.60.4 // indirect github.com/containers/image/v5 v5.32.2 // indirect github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect github.com/containers/ocicrypt v1.2.0 // indirect github.com/containers/storage v1.55.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/cli v27.2.0+incompatible // indirect + github.com/docker/cli v27.3.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v27.1.1+incompatible // indirect + github.com/docker/docker v27.2.0+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.11.2 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-git/go-git/v5 v5.12.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -69,15 +73,19 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/h2non/filetype v1.1.3 // indirect github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/joelanford/ignore v0.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/moby/locker v1.0.1 // indirect + github.com/moby/spdystream v0.4.0 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.3.0 // indirect @@ -85,35 +93,38 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect github.com/x448/float16 v0.8.4 // indirect go.etcd.io/bbolt v1.3.11 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.29.0 // indirect + golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/grpc v1.66.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect diff --git a/go.sum b/go.sum index 47f3774..158eff8 100644 --- a/go.sum +++ b/go.sum @@ -4,12 +4,16 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.12.5 h1:bpTInLlDy/nDRWFVcefDZZ1+U8tS+rz3MxjKgu9boo0= github.com/Microsoft/hcsshim v0.12.5/go.mod h1:tIUGego4G1EN5Hb6KC90aDYiUI2dqLSTTOCjVNpOgZ8= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -27,14 +31,14 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= -github.com/containerd/containerd v1.7.22 h1:nZuNnNRA6T6jB975rx2RRNqqH2k6ELYKDZfqTHqwyy0= -github.com/containerd/containerd v1.7.22/go.mod h1:e3Jz1rYRUZ2Lt51YrH9Rz0zPyJBOlSvB3ghr2jbVD8g= +github.com/containerd/containerd v1.7.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ= +github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= github.com/containerd/containerd/api v1.7.19 h1:VWbJL+8Ap4Ju2mx9c9qS1uFSB1OVYr5JJrW2yT5vFoA= github.com/containerd/containerd/api v1.7.19/go.mod h1:fwGavl3LNwAV5ilJ0sbrABL44AQxmNjDRcwheXDb6Ig= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= -github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= @@ -43,8 +47,8 @@ github.com/containerd/ttrpc v1.2.5 h1:IFckT1EFQoFBMG4c3sMdT8EP3/aKfumK1msY+Ze4oL github.com/containerd/ttrpc v1.2.5/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4= github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= -github.com/containers/common v0.60.2 h1:utcwp2YkO8c0mNlwRxsxfOiqfj157FRrBjxgjR6f+7o= -github.com/containers/common v0.60.2/go.mod h1:I0upBi1qJX3QmzGbUOBN1LVP6RvkKhd3qQpZbQT+Q54= +github.com/containers/common v0.60.4 h1:H5+LAMHPZEqX6vVNOQ+IguVsaFl8kbO/SZ/VPXjxhy0= +github.com/containers/common v0.60.4/go.mod h1:I0upBi1qJX3QmzGbUOBN1LVP6RvkKhd3qQpZbQT+Q54= github.com/containers/image/v5 v5.32.2 h1:SzNE2Y6sf9b1GJoC8qjCuMBXwQrACFp4p0RK15+4gmQ= github.com/containers/image/v5 v5.32.2/go.mod h1:v1l73VeMugfj/QtKI+jhYbwnwFCFnNGckvbST3rQ5Hk= github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= @@ -67,12 +71,12 @@ github.com/distribution/distribution/v3 v3.0.0-beta.1 h1:X+ELTxPuZ1Xe5MsD3kp2wfG github.com/distribution/distribution/v3 v3.0.0-beta.1/go.mod h1:O9O8uamhHzWWQVTjuQpyYUVm/ShPHPUDgvQMpHGVBDs= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v27.2.0+incompatible h1:yHD1QEB1/0vr5eBNpu8tncu8gWxg8EydFPOSKHzXSMM= -github.com/docker/cli v27.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ= +github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= -github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= +github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -95,10 +99,16 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -118,8 +128,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= -github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= +github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y= +github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -155,8 +165,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -164,6 +174,8 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= @@ -184,14 +196,18 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/joelanford/ignore v0.1.0 h1:VawbTDeg5EL+PN7W8gxVzGerfGpVo3gFdR5ZAqnkYRk= +github.com/joelanford/ignore v0.1.0/go.mod h1:Vb0PQMAQXK29fmiPjDukpO8I2NTcp1y8LbhFijD1/0o= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -200,10 +216,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0= -github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= +github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= @@ -219,6 +237,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -226,12 +246,12 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= -github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -240,12 +260,14 @@ github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/operator-framework/api v0.27.0 h1:OrVaGKZJvbZo58HTv2guz7aURkhVKYhFqZ/6VpifiXI= github.com/operator-framework/api v0.27.0/go.mod h1:lg2Xx+S8NQWGYlEOvFwQvH46E5EK5IrAIL7HWfAhciM= -github.com/operator-framework/operator-controller v0.15.0 h1:qSmdFKbJWQs/CyZ8pBA5YDMbid2wP3XJKBzMOvu/ezQ= -github.com/operator-framework/operator-controller v0.15.0/go.mod h1:6U2jhRr+T/d1K6nQR3SbIGB0rvDS7zAIAfSDn/hJLSY= +github.com/operator-framework/catalogd v1.0.0 h1:km19SrAvwwtmYs5/2YvTZgMWA17tDeVWv4NR5wNx+k4= +github.com/operator-framework/catalogd v1.0.0/go.mod h1:ERq4C2ksfkf3wu3XmtGP2fIkBSqS6LfaHhtcSEcU7Ww= +github.com/operator-framework/operator-controller v1.0.0 h1:Z3RFdgcORVLYBygs/WZ4ifxvFDcH977RQkl2gRnOtvc= +github.com/operator-framework/operator-controller v1.0.0/go.mod h1:EllisDxwKnhHpusytJ4ysCzeZexBy/wry0tWWHBfvu4= github.com/operator-framework/operator-lifecycle-manager v0.23.1 h1:Xw2ml1T4W2ieoFaVwanW/eFlZ11yAOJZUpUI8RLSql8= github.com/operator-framework/operator-lifecycle-manager v0.23.1/go.mod h1:q/QgVi/WooEyOFw8ipQrb2A/InjM4djCwPf7IlCpSOQ= -github.com/operator-framework/operator-registry v1.47.0 h1:Imr7X/W6FmXczwpIOXfnX8d6Snr1dzwWxkMG+lLAfhg= -github.com/operator-framework/operator-registry v1.47.0/go.mod h1:CJ3KcP8uRxtC8l9caM1RsV7r7jYlKAd452tcxcgXyTQ= +github.com/operator-framework/operator-registry v1.48.0 h1:OBTITNJdJuDz+OQVtwHCDP+cAsVeujJH/26HZ6o+zxQ= +github.com/operator-framework/operator-registry v1.48.0/go.mod h1:viEvcrj16nyauX78J38+BEELSaF+uY7GOu6TJdiOSqU= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= @@ -255,8 +277,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg= -github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= @@ -309,17 +331,17 @@ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib v1.3.0 h1:p9Gd+3dD7yB+AIph2Ltg11QDX6Y+yWMH0YQVTpTTP2c= go.opentelemetry.io/contrib/exporters/autoexport v0.46.1 h1:ysCfPZB9AjUlMa1UHYup3c9dAOCMQX/6sxSfPBUoxHw= go.opentelemetry.io/contrib/exporters/autoexport v0.46.1/go.mod h1:ha0aiYm+DOPsLHjh0zoQ8W8sLT+LJ58J3j47lGpSLrU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= go.opentelemetry.io/otel/exporters/otlp v0.20.0 h1:PTNgq9MRmQqqJY0REVbZFvwkYOA85vbdQU/nVfxDyqg= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 h1:bflGWrfYyuulcdxf14V6n9+CoQcu5SAAdHmDPAJnlps= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0/go.mod h1:qcTO4xHAxZLaLxPd60TdE88rxtItPHgHWqOhOGRr0as= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= @@ -330,27 +352,27 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0 h1:dEZWPjVN22urgY go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0/go.mod h1:sTt30Evb7hJB/gEk27qLb1+l9n4Tb8HvHkR0Wx3S6CU= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= @@ -372,8 +394,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -395,14 +417,14 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -414,8 +436,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -425,19 +447,19 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= -google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s= +google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -449,8 +471,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -461,6 +483,8 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -475,18 +499,18 @@ gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= -k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= -k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/N6E40= -k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ= -k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= -k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/apiserver v0.31.1 h1:Sars5ejQDCRBY5f7R3QFHdqN3s61nhkpaX8/k1iEw1c= -k8s.io/apiserver v0.31.1/go.mod h1:lzDhpeToamVZJmmFlaLwdYZwd7zB+WYRYIboqA1kGxM= -k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= -k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= -k8s.io/component-base v0.31.1 h1:UpOepcrX3rQ3ab5NB6g5iP0tvsgJWzxTyAo20sgYSy8= -k8s.io/component-base v0.31.1/go.mod h1:WGeaw7t/kTsqpVTaCoVEtillbqAhF2/JgvO0LDOMa0w= +k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= +k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= +k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0= +k8s.io/apiextensions-apiserver v0.31.2/go.mod h1:i+Geh+nGCJEGiCGR3MlBDkS7koHIIKWVfWeRFiOsUcM= +k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= +k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4= +k8s.io/apiserver v0.31.2/go.mod h1:o3nKZR7lPlJqkU5I3Ove+Zx3JuoFjQobGX1Gctw6XuE= +k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= +k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= +k8s.io/component-base v0.31.2 h1:Z1J1LIaC0AV+nzcPRFqfK09af6bZ4D1nAOpWsy9owlA= +k8s.io/component-base v0.31.2/go.mod h1:9PeyyFN/drHjtJZMCTkSpQJS3U9OXORnHQqMLDz0sUQ= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= diff --git a/internal/cmd/catalog.go b/internal/cmd/catalog.go index cf97cde..117dd00 100644 --- a/internal/cmd/catalog.go +++ b/internal/cmd/catalog.go @@ -14,6 +14,7 @@ func newCatalogCmd(cfg *action.Configuration) *cobra.Command { cmd.AddCommand( newCatalogAddCmd(cfg), newCatalogListCmd(cfg), + newCatalogContentCmd(cfg), newCatalogRemoveCmd(cfg), ) return cmd diff --git a/internal/cmd/catalog_add.go b/internal/cmd/catalog_add.go index c5e7a81..a7e6a55 100644 --- a/internal/cmd/catalog_add.go +++ b/internal/cmd/catalog_add.go @@ -1,17 +1,13 @@ package cmd import ( - "io" "time" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/operator-framework/operator-registry/pkg/image/containerdregistry" - "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" - internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action" + internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action/v1" "github.com/operator-framework/kubectl-operator/pkg/action" ) @@ -20,25 +16,18 @@ func newCatalogAddCmd(cfg *action.Configuration) *cobra.Command { a.Logf = log.Printf cmd := &cobra.Command{ - Use: "add ", - Short: "Add an operator catalog", + Use: "add ", + Short: "Add an cluster catalog", Args: cobra.ExactArgs(2), - PreRun: func(cmd *cobra.Command, args []string) { - regLogger := logrus.New() - regLogger.SetOutput(io.Discard) - a.RegistryOptions = []containerdregistry.RegistryOption{ - containerdregistry.WithLog(logrus.NewEntry(regLogger)), - } - }, Run: func(cmd *cobra.Command, args []string) { - a.CatalogSourceName = args[0] - a.IndexImage = args[1] + a.CatalogName = args[0] + a.CatalogImage = args[1] cs, err := a.Run(cmd.Context()) if err != nil { - log.Fatalf("failed to add catalog: %v", err) + log.Fatalf("failed to add clustercatalog: %v", err) } - log.Printf("created catalogsource %q\n", cs.Name) + log.Printf("created clustercatalog %q\n", cs.Name) }, } bindCatalogAddFlags(cmd.Flags(), a) @@ -47,7 +36,7 @@ func newCatalogAddCmd(cfg *action.Configuration) *cobra.Command { } func bindCatalogAddFlags(fs *pflag.FlagSet, a *internalaction.CatalogAdd) { - fs.StringVarP(&a.DisplayName, "display-name", "d", "", "display name of the index") - fs.StringVarP(&a.Publisher, "publisher", "p", "", "publisher of the index") + fs.Int32Var(&a.Priority, "priority", 0, "the priority of the catalog") + fs.DurationVar(&a.PollInterval, "poll-interval", 10*time.Minute, "the poll interval to configure for the catalog, set to 0 to disable") fs.DurationVar(&a.CleanupTimeout, "cleanup-timeout", time.Minute, "the amount of time to wait before cancelling cleanup") } diff --git a/internal/cmd/catalog_content.go b/internal/cmd/catalog_content.go new file mode 100644 index 0000000..72cf25e --- /dev/null +++ b/internal/cmd/catalog_content.go @@ -0,0 +1,192 @@ +package cmd + +import ( + "cmp" + "encoding/json" + "fmt" + "github.com/Masterminds/semver/v3" + "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" + internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action/v1" + "github.com/operator-framework/kubectl-operator/pkg/action" + "github.com/operator-framework/operator-registry/alpha/declcfg" + "github.com/operator-framework/operator-registry/alpha/property" + "github.com/spf13/cobra" + "iter" + "k8s.io/apimachinery/pkg/util/sets" + "maps" + "os" + "slices" + "strings" + "sync" + "text/tabwriter" +) + +func newCatalogContentCmd(cfg *action.Configuration) *cobra.Command { + cc := internalaction.NewCatalogContent(cfg) + + var ( + pkgName string + versionRange string + channels []string + ) + + cmd := &cobra.Command{ + Use: "content ", + Short: "View cluster catalog content", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + var constraints *semver.Constraints + if versionRange != "" { + var err error + constraints, err = semver.NewConstraint(versionRange) + if err != nil { + log.Fatalf("invalid version range %q: %v", versionRange, err) + } + } + + cc.CatalogName = args[0] + var ( + bundles = map[string]map[string]*bundleMetadata{} + mu sync.Mutex + ) + cc.WalkMetas = func(meta *declcfg.Meta, err error) error { + if err != nil { + return err + } + if pkgName != "" && pkgName != meta.Package { + return nil + } + if meta.Schema == declcfg.SchemaChannel { + var ch declcfg.Channel + if err := json.Unmarshal(meta.Blob, &ch); err != nil { + return err + } + for _, entry := range ch.Entries { + mu.Lock() + pkg, ok := bundles[ch.Package] + if !ok { + pkg = map[string]*bundleMetadata{} + } + b, ok := pkg[entry.Name] + if !ok { + b = &bundleMetadata{ + Name: entry.Name, + Channels: sets.New[string](), + } + pkg[entry.Name] = b + } + b.Channels.Insert(meta.Name) + bundles[ch.Package] = pkg + mu.Unlock() + } + return nil + } + if meta.Schema == declcfg.SchemaBundle { + bundle := declcfg.Bundle{} + if err := json.Unmarshal(meta.Blob, &bundle); err != nil { + return err + } + bundleVersion, err := getBundleVersion(bundle) + if err != nil { + return err + } + mu.Lock() + pkg, ok := bundles[bundle.Package] + if !ok { + pkg = map[string]*bundleMetadata{} + } + b, ok := pkg[bundle.Name] + if !ok { + b = &bundleMetadata{ + Name: meta.Name, + Channels: sets.New[string](), + } + pkg[bundle.Name] = b + } + b.Version = bundleVersion + bundles[bundle.Package] = pkg + mu.Unlock() + } + return nil + } + + if err := cc.Run(cmd.Context()); err != nil { + log.Fatalf("failed to get content for catalog %q: %v", cc.CatalogName, err) + } + + if len(bundles) == 0 { + log.Print("No resources found") + return + } + + tw := tabwriter.NewWriter(os.Stdout, 3, 4, 2, ' ', 0) + _, _ = fmt.Fprintf(tw, "PACKAGE\tVERSION\tCHANNELS\t\n") + + pkgNames := collect(maps.Keys(bundles)) + slices.SortFunc(pkgNames, func(a, b string) int { + return cmp.Compare(a, b) + }) + for _, pkg := range pkgNames { + bundleNames := collect(maps.Keys(bundles[pkg])) + slices.SortFunc(bundleNames, func(a, b string) int { + return -bundles[pkg][a].Version.Compare(bundles[pkg][b].Version) + }) + for _, bundleName := range bundleNames { + bundle := bundles[pkg][bundleName] + if constraints != nil && !constraints.Check(bundle.Version) { + continue + } + if len(channels) > 0 && !bundle.Channels.HasAny(channels...) { + continue + } + _, _ = fmt.Fprintf(tw, "%s\t%s\t%s\n", pkg, bundle.Version, strings.Join(sets.List(bundle.Channels), ",")) + } + } + _ = tw.Flush() + }, + } + cmd.Flags().StringVarP(&pkgName, "package", "p", "", "package name to filter") + cmd.Flags().StringVarP(&versionRange, "version", "v", "", "version range to filter") + cmd.Flags().StringSliceVarP(&channels, "channels", "c", []string{}, "channels to filter") + return cmd +} + +func collect[V any](i iter.Seq[V]) []V { + var out []V + for v := range i { + out = append(out, v) + } + return out +} + +type bundleMetadata struct { + Name string + Version *semver.Version + Channels sets.Set[string] +} + +func getBundleMetadata(b declcfg.Bundle) (string, string, error) { + version, err := getBundleVersion(b) + if err != nil { + return "", "", err + } + return b.Package, version.String(), nil +} + +func getBundleVersion(b declcfg.Bundle) (*semver.Version, error) { + packageValue := json.RawMessage{} + for _, p := range b.Properties { + if p.Type == property.TypePackage { + packageValue = p.Value + break + } + } + if len(packageValue) == 0 { + return nil, fmt.Errorf("no package property found") + } + packageProp := property.Package{} + if err := json.Unmarshal(packageValue, &packageProp); err != nil { + return nil, err + } + return semver.NewVersion(packageProp.Version) +} diff --git a/internal/cmd/catalog_list.go b/internal/cmd/catalog_list.go index 43107b9..d461d54 100644 --- a/internal/cmd/catalog_list.go +++ b/internal/cmd/catalog_list.go @@ -2,60 +2,64 @@ package cmd import ( "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "os" "text/tabwriter" "time" "github.com/spf13/cobra" - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/duration" + "cmp" + catalogdv1 "github.com/operator-framework/catalogd/api/v1" "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" - internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action" + internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action/v1" "github.com/operator-framework/kubectl-operator/pkg/action" + "k8s.io/apimachinery/pkg/api/meta" + "slices" ) func newCatalogListCmd(cfg *action.Configuration) *cobra.Command { - var allNamespaces bool l := internalaction.NewCatalogList(cfg) cmd := &cobra.Command{ Use: "list", - Short: "List installed operator catalogs", + Short: "List cluster catalogs", Run: func(cmd *cobra.Command, args []string) { - if allNamespaces { - cfg.Namespace = corev1.NamespaceAll - } catalogs, err := l.Run(cmd.Context()) if err != nil { log.Fatal(err) } if len(catalogs) == 0 { - if cfg.Namespace == corev1.NamespaceAll { - log.Print("No resources found") - } else { - log.Printf("No resources found in %s namespace.", cfg.Namespace) - } + log.Print("No resources found") return } - nsCol := "" - if allNamespaces { - nsCol = "\tNAMESPACE" - } + slices.SortFunc(catalogs, func(a, b catalogdv1.ClusterCatalog) int { + return -cmp.Compare(a.Spec.Priority, b.Spec.Priority) + }) + tw := tabwriter.NewWriter(os.Stdout, 3, 4, 2, ' ', 0) - _, _ = fmt.Fprintf(tw, "NAME%s\tDISPLAY\tTYPE\tPUBLISHER\tAGE\n", nsCol) - for _, cs := range catalogs { - ns := "" - if allNamespaces { - ns = "\t" + cs.Namespace + _, _ = fmt.Fprintf(tw, "NAME\tSERVING\tPRIORITY\tPOLL INTERVAL\tLAST UPDATE\tAGE\n") + for _, cat := range catalogs { + pollInterval := "Disabled" + if cat.Spec.Source.Image != nil && cat.Spec.Source.Image.PollIntervalMinutes != nil { + pollInterval = duration.ShortHumanDuration(time.Duration(*cat.Spec.Source.Image.PollIntervalMinutes) * time.Minute) } - age := time.Since(cs.CreationTimestamp.Time) - _, _ = fmt.Fprintf(tw, "%s%s\t%s\t%s\t%s\t%s\n", cs.Name, ns, cs.Spec.DisplayName, cs.Spec.SourceType, cs.Spec.Publisher, duration.HumanDuration(age)) + lastUpdate := time.Since(cat.Status.LastUnpacked.Time) + age := time.Since(cat.CreationTimestamp.Time) + _, _ = fmt.Fprintf(tw, "%s\t%s\t%d\t%s\t%s\t%s\n", cat.Name, servingStatus(cat), cat.Spec.Priority, pollInterval, duration.ShortHumanDuration(lastUpdate), duration.ShortHumanDuration(age)) } _ = tw.Flush() }, } - cmd.Flags().BoolVarP(&allNamespaces, "all-namespaces", "A", false, "list catalogs in all namespaces") return cmd } + +func servingStatus(cat catalogdv1.ClusterCatalog) metav1.ConditionStatus { + cond := meta.FindStatusCondition(cat.Status.Conditions, catalogdv1.TypeServing) + if cond == nil { + return metav1.ConditionFalse + } + return cond.Status +} diff --git a/internal/cmd/catalog_remove.go b/internal/cmd/catalog_remove.go index fa03131..e25b642 100644 --- a/internal/cmd/catalog_remove.go +++ b/internal/cmd/catalog_remove.go @@ -4,7 +4,7 @@ import ( "github.com/spf13/cobra" "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" - internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action" + internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action/v1" "github.com/operator-framework/kubectl-operator/pkg/action" ) @@ -12,7 +12,7 @@ func newCatalogRemoveCmd(cfg *action.Configuration) *cobra.Command { u := internalaction.NewCatalogRemove(cfg) cmd := &cobra.Command{ Use: "remove ", - Short: "Remove a operator catalog", + Short: "Remove a cluster catalog", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { u.CatalogName = args[0] @@ -20,7 +20,7 @@ func newCatalogRemoveCmd(cfg *action.Configuration) *cobra.Command { if err := u.Run(cmd.Context()); err != nil { log.Fatalf("failed to remove catalog %q: %v", u.CatalogName, err) } - log.Printf("catalogsource %q removed", u.CatalogName) + log.Printf("clustercatalog %q removed", u.CatalogName) }, } diff --git a/internal/cmd/internal/olmv1/alpha_install.go b/internal/cmd/internal/olmv1/alpha_install.go deleted file mode 100644 index f4e945b..0000000 --- a/internal/cmd/internal/olmv1/alpha_install.go +++ /dev/null @@ -1,30 +0,0 @@ -package olmv1 - -import ( - "github.com/spf13/cobra" - - "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" - experimentalaction "github.com/operator-framework/kubectl-operator/internal/pkg/experimental/action" - "github.com/operator-framework/kubectl-operator/pkg/action" -) - -func NewOperatorInstallCmd(cfg *action.Configuration) *cobra.Command { - i := experimentalaction.NewOperatorInstall(cfg) - i.Logf = log.Printf - - cmd := &cobra.Command{ - Use: "install ", - Short: "Install an operator", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - i.Package = args[0] - _, err := i.Run(cmd.Context()) - if err != nil { - log.Fatalf("failed to install operator: %v", err) - } - log.Printf("operator %q created", i.Package) - }, - } - - return cmd -} diff --git a/internal/cmd/internal/olmv1/alpha_uninstall.go b/internal/cmd/internal/olmv1/alpha_uninstall.go deleted file mode 100644 index 07ea7c1..0000000 --- a/internal/cmd/internal/olmv1/alpha_uninstall.go +++ /dev/null @@ -1,34 +0,0 @@ -package olmv1 - -import ( - "github.com/spf13/cobra" - - "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" - experimentalaction "github.com/operator-framework/kubectl-operator/internal/pkg/experimental/action" - "github.com/operator-framework/kubectl-operator/pkg/action" -) - -func NewOperatorUninstallCmd(cfg *action.Configuration) *cobra.Command { - u := experimentalaction.NewOperatorUninstall(cfg) - u.Logf = log.Printf - - cmd := &cobra.Command{ - Use: "uninstall ", - Short: "Uninstall an operator", - Long: `Uninstall deletes the named Operator object. - -Warning: this command permanently deletes objects from the cluster. If the -uninstalled Operator bundle contains CRDs, the CRDs will be deleted, which -cascades to the deletion of all operands. -`, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - u.Package = args[0] - if err := u.Run(cmd.Context()); err != nil { - log.Fatalf("uninstall operator: %v", err) - } - log.Printf("deleted operator %q", u.Package) - }, - } - return cmd -} diff --git a/internal/cmd/olmv1.go b/internal/cmd/olmv1.go deleted file mode 100644 index 9ca930b..0000000 --- a/internal/cmd/olmv1.go +++ /dev/null @@ -1,23 +0,0 @@ -package cmd - -import ( - "github.com/spf13/cobra" - - "github.com/operator-framework/kubectl-operator/internal/cmd/internal/olmv1" - "github.com/operator-framework/kubectl-operator/pkg/action" -) - -func newOlmV1Cmd(cfg *action.Configuration) *cobra.Command { - cmd := &cobra.Command{ - Use: "olmv1", - Short: "Manage operators via OLMv1 in a cluster from the command line", - Long: "Manage operators via OLMv1 in a cluster from the command line.", - } - - cmd.AddCommand( - olmv1.NewOperatorInstallCmd(cfg), - olmv1.NewOperatorUninstallCmd(cfg), - ) - - return cmd -} diff --git a/internal/cmd/operator_describe.go b/internal/cmd/operator_describe.go deleted file mode 100644 index 8e8545e..0000000 --- a/internal/cmd/operator_describe.go +++ /dev/null @@ -1,130 +0,0 @@ -package cmd - -import ( - "fmt" - "strings" - - "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/util/sets" - - "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" - internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action" - "github.com/operator-framework/kubectl-operator/internal/pkg/operator" - "github.com/operator-framework/kubectl-operator/pkg/action" -) - -var ( - // output helpers for the describe subcommand - pkgHdr = asHeader("Package") - repoHdr = asHeader("Repository") - catHdr = asHeader("Catalog") - chHdr = asHeader("Channels") - imHdr = asHeader("Install Modes") - sdHdr = asHeader("Description") - ldHdr = asHeader("Long Description") - - repoAnnot = "repository" - descAnnot = "description" -) - -func newOperatorDescribeCmd(cfg *action.Configuration) *cobra.Command { - l := internalaction.NewOperatorListAvailable(cfg) - // receivers for cmdline flags - var channel string - var longDescription bool - - cmd := &cobra.Command{ - Use: "describe ", - Short: "Describe an operator", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - // the operator to show details about, provided by the user - l.Package = args[0] - - // Find the package manifest and package channel for the operator - pms, err := l.Run(cmd.Context()) - if err != nil { - log.Fatal(err) - } - - // we only expect one item because describe always searches - // for a specific operator by name - pm := pms[0] - - pc, err := pm.GetChannel(channel) - if err != nil { - // the requested channel doesn't exist - log.Fatal(err) - } - - // prepare what we want to print to the console - out := make([]string, 0) - - // Starting adding data to our output. - out = append(out, - // package - pkgHdr+fmt.Sprintf("%s %s (by %s)\n\n", - pc.CurrentCSVDesc.DisplayName, - pc.CurrentCSVDesc.Version, - pc.CurrentCSVDesc.Provider.Name), - // repo - repoHdr+fmt.Sprintf("%s\n\n", - pc.CurrentCSVDesc.Annotations[repoAnnot]), - // catalog - catHdr+fmt.Sprintf("%s\n\n", pm.Status.CatalogSourceDisplayName), - // available channels - chHdr+fmt.Sprintf("%s\n\n", - strings.Join(getAvailableChannelsWithMarkers(*pc, pm), "\n")), - // install modes - imHdr+fmt.Sprintf("%s\n\n", - strings.Join(sets.List[string](pc.GetSupportedInstallModes()), "\n")), - // description - sdHdr+fmt.Sprintf("%s\n", - pc.CurrentCSVDesc.Annotations[descAnnot]), - ) - - // if the user requested a long description, add it to the output as well - if longDescription { - out = append(out, - "\n"+ldHdr+pm.Status.Channels[0].CurrentCSVDesc.LongDescription) - } - - // finally, print operator information to the console - for _, v := range out { - fmt.Print(v) - } - }, - } - - // add flags to the flagset for this command. - bindOperatorListAvailableFlags(cmd.Flags(), l) - cmd.Flags().StringVarP(&channel, "channel", "C", "", "package channel to describe") - cmd.Flags().BoolVarP(&longDescription, "with-long-description", "L", false, "include long description") - - return cmd -} - -// asHeader returns the string with "header bars" for displaying in -// plain text cases. -func asHeader(s string) string { - return fmt.Sprintf("== %s ==\n", s) -} - -// getAvailableChannelsWithMarkers parses all available package channels for a package manifest -// and returns those channel names with indicators for pretty-printing whether they are shown -// or the default channel -func getAvailableChannelsWithMarkers(channel operator.PackageChannel, pm operator.PackageManifest) []string { - channels := make([]string, len(pm.Status.Channels)) - for i, ch := range pm.Status.Channels { - n := ch.Name - if ch.IsDefaultChannel(pm.PackageManifest) { - n += " (default)" - } - if channel.Name == ch.Name { - n += " (shown)" - } - channels[i] = n - } - - return channels -} diff --git a/internal/cmd/operator_install.go b/internal/cmd/operator_install.go index 4e96cdf..db5126e 100644 --- a/internal/cmd/operator_install.go +++ b/internal/cmd/operator_install.go @@ -1,20 +1,17 @@ package cmd import ( - "fmt" "time" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/operator-framework/api/pkg/operators/v1alpha1" - "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" - internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action" + internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action/v1" "github.com/operator-framework/kubectl-operator/pkg/action" ) -func newOperatorInstallCmd(cfg *action.Configuration) *cobra.Command { +func newExtensionInstallCmd(cfg *action.Configuration) *cobra.Command { i := internalaction.NewOperatorInstall(cfg) i.Logf = log.Printf @@ -24,11 +21,14 @@ func newOperatorInstallCmd(cfg *action.Configuration) *cobra.Command { Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { i.Package = args[0] - csv, err := i.Run(cmd.Context()) + i.Namespace = internalaction.OperatorInstallNamespaceConfig{ + Name: cfg.Namespace, + } + clusterExtension, err := i.Run(cmd.Context()) if err != nil { - log.Fatalf("failed to install operator: %v", err) + log.Fatalf("failed to install cluster extension: %v", err) } - log.Printf("operator %q installed; installed csv is %q", i.Package, csv.Name) + log.Printf("cluster extension %q installed; installed bundle is %q", i.Package, clusterExtension.Status.Install.Bundle.Name) }, } bindOperatorInstallFlags(cmd.Flags(), i) @@ -37,10 +37,9 @@ func newOperatorInstallCmd(cfg *action.Configuration) *cobra.Command { } func bindOperatorInstallFlags(fs *pflag.FlagSet, i *internalaction.OperatorInstall) { - fs.StringVarP(&i.Channel, "channel", "c", "", "subscription channel") - fs.VarP(&i.Approval, "approval", "a", fmt.Sprintf("approval (%s or %s)", v1alpha1.ApprovalManual, v1alpha1.ApprovalAutomatic)) - fs.StringVarP(&i.Version, "version", "v", "", "install specific version for operator (default latest)") - fs.StringSliceVarP(&i.WatchNamespaces, "watch", "w", []string{}, "namespaces to watch") + fs.StringSliceVarP(&i.Channels, "channels", "c", []string{}, "upgrade channels from which to resolve bundles") + fs.StringVarP(&i.Version, "version", "v", "", "version (or version range) from which to resolve bundles") fs.DurationVar(&i.CleanupTimeout, "cleanup-timeout", time.Minute, "the amount of time to wait before cancelling cleanup") - fs.BoolVarP(&i.CreateOperatorGroup, "create-operator-group", "C", false, "create operator group if necessary") + fs.BoolVarP(&i.UnsafeCreateClusterRoleBinding, "unsafe-create-cluster-role-binding", "X", false, "create a cluster-admin ClusterRoleBinding for the extension installation") + fs.StringVarP(&i.ServiceAccount, "service-account", "s", "default", "service account to use for the extension installation") } diff --git a/internal/cmd/operator_list.go b/internal/cmd/operator_list.go index ce84634..3c5c7a3 100644 --- a/internal/cmd/operator_list.go +++ b/internal/cmd/operator_list.go @@ -3,65 +3,60 @@ package cmd import ( "fmt" "os" - "sort" "strings" "text/tabwriter" "time" "github.com/spf13/cobra" - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/duration" "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" - internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action" + internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action/v1" "github.com/operator-framework/kubectl-operator/pkg/action" + ocv1 "github.com/operator-framework/operator-controller/api/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "slices" ) -func newOperatorListCmd(cfg *action.Configuration) *cobra.Command { - var allNamespaces bool +func newExtensionListCmd(cfg *action.Configuration) *cobra.Command { l := internalaction.NewOperatorList(cfg) cmd := &cobra.Command{ Use: "list", - Short: "List installed operators", + Short: "List cluster extensions", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { - if allNamespaces { - cfg.Namespace = corev1.NamespaceAll - } - subs, err := l.Run(cmd.Context()) + clusterExtensions, err := l.Run(cmd.Context()) if err != nil { log.Fatalf("list operators: %v", err) } - if len(subs) == 0 { - if cfg.Namespace == corev1.NamespaceAll { - log.Print("No resources found") - } else { - log.Printf("No resources found in %s namespace.", cfg.Namespace) - } + if len(clusterExtensions) == 0 { + log.Print("No resources found") return } - sort.SliceStable(subs, func(i, j int) bool { - return strings.Compare(subs[i].Spec.Package, subs[j].Spec.Package) < 0 + slices.SortFunc(clusterExtensions, func(a, b ocv1.ClusterExtension) int { + return strings.Compare(a.Status.Install.Bundle.Name, b.Status.Install.Bundle.Name) }) - nsCol := "" - if allNamespaces { - nsCol = "\tNAMESPACE" - } tw := tabwriter.NewWriter(os.Stdout, 3, 4, 2, ' ', 0) - _, _ = fmt.Fprintf(tw, "PACKAGE%s\tSUBSCRIPTION\tINSTALLED CSV\tCURRENT CSV\tSTATUS\tAGE\n", nsCol) - for _, sub := range subs { - ns := "" - if allNamespaces { - ns = "\t" + sub.Namespace + _, _ = fmt.Fprintf(tw, "NAME\tNAMESPACE\tINSTALLED BUNDLE\tAT DESIRED STATE\tAGE\n") + for _, ce := range clusterExtensions { + installedBundle := "(not installed)" + if meta.IsStatusConditionPresentAndEqual(ce.Status.Conditions, ocv1.TypeInstalled, metav1.ConditionTrue) { + installedBundle = ce.Status.Install.Bundle.Name } - age := time.Since(sub.CreationTimestamp.Time) - _, _ = fmt.Fprintf(tw, "%s%s\t%s\t%s\t%s\t%s\t%s\n", sub.Spec.Package, ns, sub.Name, sub.Status.InstalledCSV, sub.Status.CurrentCSV, sub.Status.State, duration.HumanDuration(age)) + atDesiredState := "False" + progressing := meta.FindStatusCondition(ce.Status.Conditions, ocv1.TypeProgressing) + if progressing != nil && progressing.Status == metav1.ConditionTrue && progressing.Reason == ocv1.ReasonSucceeded { + atDesiredState = "True" + } + + age := time.Since(ce.CreationTimestamp.Time) + _, _ = fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\n", ce.Name, ce.Spec.Namespace, installedBundle, atDesiredState, duration.HumanDuration(age)) } _ = tw.Flush() }, } - cmd.Flags().BoolVarP(&allNamespaces, "all-namespaces", "A", false, "list operators in all namespaces") return cmd } diff --git a/internal/cmd/operator_list_available.go b/internal/cmd/operator_list_available.go deleted file mode 100644 index 4bbe259..0000000 --- a/internal/cmd/operator_list_available.go +++ /dev/null @@ -1,67 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - "sort" - "strings" - "text/tabwriter" - "time" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/duration" - - "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" - internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action" - "github.com/operator-framework/kubectl-operator/pkg/action" -) - -func newOperatorListAvailableCmd(cfg *action.Configuration) *cobra.Command { - l := internalaction.NewOperatorListAvailable(cfg) - cmd := &cobra.Command{ - Use: "list-available", - Short: "List operators available to be installed", - Args: cobra.MaximumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { - if len(args) == 1 { - l.Package = args[0] - } - - operators, err := l.Run(cmd.Context()) - if err != nil { - log.Fatal(err) - } - - if len(operators) == 0 { - if cfg.Namespace == corev1.NamespaceAll { - log.Print("No resources found") - } else { - log.Printf("No resources found in %s namespace.\n", cfg.Namespace) - } - return - } - - sort.SliceStable(operators, func(i, j int) bool { - return strings.Compare(operators[i].Name, operators[j].Name) < 0 - }) - - tw := tabwriter.NewWriter(os.Stdout, 3, 4, 2, ' ', 0) - _, _ = fmt.Fprintf(tw, "NAME\tCATALOG\tCHANNEL\tLATEST CSV\tAGE\n") - for _, op := range operators { - age := time.Since(op.CreationTimestamp.Time) - for _, ch := range op.Status.Channels { - _, _ = fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\n", op.Name, op.Status.CatalogSourceDisplayName, ch.Name, ch.CurrentCSV, duration.HumanDuration(age)) - } - } - _ = tw.Flush() - }, - } - bindOperatorListAvailableFlags(cmd.Flags(), l) - return cmd -} - -func bindOperatorListAvailableFlags(fs *pflag.FlagSet, l *internalaction.OperatorListAvailable) { - fs.VarP(&l.Catalog, "catalog", "c", "catalog to query (default: search all cluster catalogs)") -} diff --git a/internal/cmd/operator_list_operands.go b/internal/cmd/operator_list_operands.go index b83a293..de3c319 100644 --- a/internal/cmd/operator_list_operands.go +++ b/internal/cmd/operator_list_operands.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/operator-framework/kubectl-operator/pkg/action/v1" "io" "os" "strings" @@ -19,26 +20,21 @@ import ( "github.com/operator-framework/kubectl-operator/pkg/action" ) -func newOperatorListOperandsCmd(cfg *action.Configuration) *cobra.Command { - l := action.NewOperatorListOperands(cfg) +func newExtensionListOperandsCmd(cfg *action.Configuration) *cobra.Command { + l := v0.NewOperatorListOperands(cfg) output := "" validOutputs := []string{"json", "yaml"} cmd := &cobra.Command{ - Use: "list-operands ", - Short: "List operands of an installed operator", - Long: `List operands of an installed operator. + Use: "list-operands ", + Short: "List operands of an installed cluster extension", + Long: `List operands of an installed cluster extension. -This command lists all operands found throughout the cluster for the operator -specified on the command line. Since the scope of an operator is restricted by -its operator group, the output will include namespace-scoped operands from the -operator group's target namespaces and all cluster-scoped operands. +This command lists all operands found throughout the cluster for the cluster +extension specified on the command line. -To search for operands for an operator in a different namespace, use the ---namespace flag. By default, the namespace from the current context is used. - -Operand kinds are determined from the owned CustomResourceDefinitions listed in -the operator's ClusterServiceVersion.`, +Operand kinds are determined from the CustomResourceDefinitions that bear a label +matching the cluster extension's name.`, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { writeOutput := func(io.Writer, *unstructured.UnstructuredList) error { panic("writeOutput was not set") } //nolint:staticcheck @@ -75,12 +71,13 @@ the operator's ClusterServiceVersion.`, func writeTable(w io.Writer, operands *unstructured.UnstructuredList) error { var buf bytes.Buffer tw := tabwriter.NewWriter(&buf, 3, 4, 2, ' ', 0) - if _, err := fmt.Fprintf(tw, "APIVERSION\tKIND\tNAMESPACE\tNAME\tAGE\n"); err != nil { + if _, err := fmt.Fprintf(tw, "GROUP\tKIND\tNAMESPACE\tNAME\tAGE\n"); err != nil { return err } for _, o := range operands.Items { + gk := o.GroupVersionKind().GroupKind() age := time.Since(o.GetCreationTimestamp().Time) - if _, err := fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\n", o.GetAPIVersion(), o.GetKind(), o.GetNamespace(), o.GetName(), duration.HumanDuration(age)); err != nil { + if _, err := fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\n", gk.Group, gk.Kind, o.GetNamespace(), o.GetName(), duration.HumanDuration(age)); err != nil { return err } } diff --git a/internal/cmd/operator_uninstall.go b/internal/cmd/operator_uninstall.go index 86afea0..6e4bac4 100644 --- a/internal/cmd/operator_uninstall.go +++ b/internal/cmd/operator_uninstall.go @@ -1,91 +1,37 @@ package cmd import ( - "errors" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" - internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action" - "github.com/operator-framework/kubectl-operator/internal/pkg/operand" + internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action/v1" "github.com/operator-framework/kubectl-operator/pkg/action" + "github.com/spf13/cobra" ) -func newOperatorUninstallCmd(cfg *action.Configuration) *cobra.Command { +func newExtensionUninstallCmd(cfg *action.Configuration) *cobra.Command { u := internalaction.NewOperatorUninstall(cfg) u.Logf = log.Printf cmd := &cobra.Command{ - Use: "uninstall ", - Short: "Uninstall an operator and operands", - Long: `Uninstall removes the subscription, operator and optionally operands managed -by the operator as well as the relevant operatorgroup. - -Warning: this command permanently deletes objects from the cluster. Running -uninstall concurrently with other operations could result in undefined behavior. - -The uninstall command first checks to find the subscription associated with the -operator. It then lists all operands found throughout the cluster for the -operator specified if one is found. Since the scope of an operator is restricted -by its operator group, this search will include namespace-scoped operands from -the operator group's target namespaces and all cluster-scoped operands. - -The operand-deletion strategy is then considered if any operands are found -on-cluster. One of abort|ignore|delete. By default, the strategy is "abort", -which means that if any operands are found when deleting the operator abort the -uninstall without deleting anything. The "ignore" strategy keeps the operands on -cluster and deletes the subscription and the operator. The "delete" strategy -deletes the subscription, operands, and after they have finished finalizing, the -operator itself. + Use: "uninstall ", + Short: "Uninstall a cluster extension", + Long: `Uninstall removes the cluster extension from the cluster. -Setting --delete-operator-groups to true will delete the operatorgroup in the -provided namespace if no other active subscriptions are currently in that -namespace, after removing the operator. The subscription and operatorgroup will -be removed even if the operator is not found. +If the cluster extension includes CRDs, the CRDs will be deleted, and therefore +all custom resources of those types will be deleted as well. -There are other deletion flags for removing additional objects, such as custom -resource definitions, operator objects, and any other objects deployed alongside -the operator (e.g. RBAC objects for the operator). These flags are: +If the cluster extension includes a namespace, the namespace will be deleted, +and therefore all resources in that namespace will be deleted as well. - --delete-operator - - Deletes all objects associated with the operator by looking up the - operator object for the operator and deleting every referenced object - and then deleting the operator object itself. This implies the flag - '--operand-strategy=delete' because it is impossible to delete CRDs - without also deleting instances of those CRDs. - - -X, --delete-all - - This is a convenience flag that is effectively equivalent to the flags - '--delete-operator=true --delete-operator-groups=true'. - -NOTE: This command does not recursively uninstall unused dependencies. To return -a cluster to its state prior to a 'kubectl operator install' call, each -dependency of the operator that was installed automatically by OLM must be -individually uninstalled. +Warning: this command permanently deletes objects from the cluster. Running +uninstall concurrently with other operations could result in undefined behavior. `, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { u.Package = args[0] if err := u.Run(cmd.Context()); err != nil { - if errors.Is(err, operand.ErrAbortStrategy) { - log.Fatalf("uninstall operator: %v"+"\n\n%s", err, - "See kubectl operator uninstall --help for more information on operand deletion strategies.") - } log.Fatalf("uninstall operator: %v", err) } }, } - bindOperatorUninstallFlags(cmd.Flags(), u) return cmd } - -func bindOperatorUninstallFlags(fs *pflag.FlagSet, u *internalaction.OperatorUninstall) { - fs.BoolVarP(&u.DeleteAll, "delete-all", "X", false, "delete all objects associated with the operator, implies --delete-operator, --operand-strategy=delete, --delete-operator-groups") - fs.BoolVar(&u.DeleteOperator, "delete-operator", false, "delete operator object associated with the operator, --operand-strategy=delete") - fs.BoolVar(&u.DeleteOperatorGroups, "delete-operator-groups", false, "delete operator groups if no other operators remain") - fs.StringSliceVar(&u.DeleteOperatorGroupNames, "delete-operator-group-names", nil, "specific operator group names to delete (only effective with --delete-operator-groups)") - fs.VarP(&u.OperandStrategy, "operand-strategy", "s", "determines how to handle operands when deleting the operator, one of abort|ignore|delete (default: abort)") -} diff --git a/internal/cmd/operator_upgrade.go b/internal/cmd/operator_upgrade.go deleted file mode 100644 index d4dd12a..0000000 --- a/internal/cmd/operator_upgrade.go +++ /dev/null @@ -1,33 +0,0 @@ -package cmd - -import ( - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "github.com/operator-framework/kubectl-operator/internal/cmd/internal/log" - internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action" - "github.com/operator-framework/kubectl-operator/pkg/action" -) - -func newOperatorUpgradeCmd(cfg *action.Configuration) *cobra.Command { - u := internalaction.NewOperatorUpgrade(cfg) - cmd := &cobra.Command{ - Use: "upgrade ", - Short: "Upgrade an operator", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - u.Package = args[0] - csv, err := u.Run(cmd.Context()) - if err != nil { - log.Fatalf("failed to upgrade operator: %v", err) - } - log.Printf("operator %q upgraded; installed csv is %q", u.Package, csv.Name) - }, - } - bindOperatorUpgradeFlags(cmd.Flags(), u) - return cmd -} - -func bindOperatorUpgradeFlags(fs *pflag.FlagSet, u *internalaction.OperatorUpgrade) { - fs.StringVarP(&u.Channel, "channel", "c", "", "subscription channel") -} diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 994152b..2cda566 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -51,14 +51,10 @@ operators from the installed catalogs.`, cmd.AddCommand( newCatalogCmd(&cfg), - newOperatorInstallCmd(&cfg), - newOperatorUpgradeCmd(&cfg), - newOperatorUninstallCmd(&cfg), - newOperatorListCmd(&cfg), - newOperatorListAvailableCmd(&cfg), - newOperatorListOperandsCmd(&cfg), - newOperatorDescribeCmd(&cfg), - newOlmV1Cmd(&cfg), + newExtensionInstallCmd(&cfg), + newExtensionUninstallCmd(&cfg), + newExtensionListCmd(&cfg), + //newExtensionListOperandsCmd(&cfg), newVersionCmd(), ) diff --git a/internal/pkg/action/action_suite_test.go b/internal/pkg/action/v0/action_suite_test.go similarity index 90% rename from internal/pkg/action/action_suite_test.go rename to internal/pkg/action/v0/action_suite_test.go index 40957f3..0ab49ee 100644 --- a/internal/pkg/action/action_suite_test.go +++ b/internal/pkg/action/v0/action_suite_test.go @@ -1,4 +1,4 @@ -package action_test +package v0_test import ( "testing" diff --git a/internal/pkg/action/catalog_add.go b/internal/pkg/action/v0/catalog_add.go similarity index 98% rename from internal/pkg/action/catalog_add.go rename to internal/pkg/action/v0/catalog_add.go index f65029e..7c6bf8f 100644 --- a/internal/pkg/action/catalog_add.go +++ b/internal/pkg/action/v0/catalog_add.go @@ -1,4 +1,4 @@ -package action +package v0 import ( "context" @@ -20,7 +20,7 @@ import ( "github.com/operator-framework/operator-registry/pkg/image" "github.com/operator-framework/operator-registry/pkg/image/containerdregistry" - "github.com/operator-framework/kubectl-operator/internal/pkg/catalogsource" + "github.com/operator-framework/kubectl-operator/internal/pkg/legacy/catalogsource" "github.com/operator-framework/kubectl-operator/pkg/action" ) diff --git a/internal/pkg/action/catalog_list.go b/internal/pkg/action/v0/catalog_list.go similarity index 97% rename from internal/pkg/action/catalog_list.go rename to internal/pkg/action/v0/catalog_list.go index 9560ab7..029dbd7 100644 --- a/internal/pkg/action/catalog_list.go +++ b/internal/pkg/action/v0/catalog_list.go @@ -1,4 +1,4 @@ -package action +package v0 import ( "context" diff --git a/internal/pkg/action/catalog_remove.go b/internal/pkg/action/v0/catalog_remove.go similarity index 97% rename from internal/pkg/action/catalog_remove.go rename to internal/pkg/action/v0/catalog_remove.go index 8ae31d2..0cfbace 100644 --- a/internal/pkg/action/catalog_remove.go +++ b/internal/pkg/action/v0/catalog_remove.go @@ -1,4 +1,4 @@ -package action +package v0 import ( "context" diff --git a/internal/pkg/action/constants.go b/internal/pkg/action/v0/constants.go similarity index 75% rename from internal/pkg/action/constants.go rename to internal/pkg/action/v0/constants.go index 9a1f366..49f9a20 100644 --- a/internal/pkg/action/constants.go +++ b/internal/pkg/action/v0/constants.go @@ -1,4 +1,4 @@ -package action +package v0 const ( csvKind = "ClusterServiceVersion" diff --git a/internal/pkg/action/helpers.go b/internal/pkg/action/v0/helpers.go similarity index 99% rename from internal/pkg/action/helpers.go rename to internal/pkg/action/v0/helpers.go index e05e8c0..b52752e 100644 --- a/internal/pkg/action/helpers.go +++ b/internal/pkg/action/v0/helpers.go @@ -1,4 +1,4 @@ -package action +package v0 import ( "context" diff --git a/internal/pkg/action/operator_install.go b/internal/pkg/action/v0/operator_install.go similarity index 98% rename from internal/pkg/action/operator_install.go rename to internal/pkg/action/v0/operator_install.go index 3c89676..15b3913 100644 --- a/internal/pkg/action/operator_install.go +++ b/internal/pkg/action/v0/operator_install.go @@ -1,4 +1,4 @@ -package action +package v0 import ( "context" @@ -16,8 +16,8 @@ import ( "github.com/operator-framework/api/pkg/operators/v1alpha1" operatorsv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" - "github.com/operator-framework/kubectl-operator/internal/pkg/operator" - "github.com/operator-framework/kubectl-operator/internal/pkg/subscription" + "github.com/operator-framework/kubectl-operator/internal/pkg/legacy/operator" + "github.com/operator-framework/kubectl-operator/internal/pkg/legacy/subscription" "github.com/operator-framework/kubectl-operator/pkg/action" ) diff --git a/internal/pkg/action/operator_list.go b/internal/pkg/action/v0/operator_list.go similarity index 97% rename from internal/pkg/action/operator_list.go rename to internal/pkg/action/v0/operator_list.go index a4bfca1..ee5e401 100644 --- a/internal/pkg/action/operator_list.go +++ b/internal/pkg/action/v0/operator_list.go @@ -1,4 +1,4 @@ -package action +package v0 import ( "context" diff --git a/internal/pkg/action/operator_list_available.go b/internal/pkg/action/v0/operator_list_available.go similarity index 95% rename from internal/pkg/action/operator_list_available.go rename to internal/pkg/action/v0/operator_list_available.go index 6a65ff5..89e9cad 100644 --- a/internal/pkg/action/operator_list_available.go +++ b/internal/pkg/action/v0/operator_list_available.go @@ -1,4 +1,4 @@ -package action +package v0 import ( "context" @@ -10,7 +10,7 @@ import ( v1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" - "github.com/operator-framework/kubectl-operator/internal/pkg/operator" + "github.com/operator-framework/kubectl-operator/internal/pkg/legacy/operator" "github.com/operator-framework/kubectl-operator/pkg/action" ) diff --git a/internal/pkg/action/operator_uninstall.go b/internal/pkg/action/v0/operator_uninstall.go similarity index 98% rename from internal/pkg/action/operator_uninstall.go rename to internal/pkg/action/v0/operator_uninstall.go index bb61301..f339a74 100644 --- a/internal/pkg/action/operator_uninstall.go +++ b/internal/pkg/action/v0/operator_uninstall.go @@ -1,8 +1,9 @@ -package action +package v0 import ( "context" "fmt" + "github.com/operator-framework/kubectl-operator/pkg/action/v0" "strings" "time" @@ -15,7 +16,7 @@ import ( v1 "github.com/operator-framework/api/pkg/operators/v1" "github.com/operator-framework/api/pkg/operators/v1alpha1" - "github.com/operator-framework/kubectl-operator/internal/pkg/operand" + "github.com/operator-framework/kubectl-operator/internal/pkg/legacy/operand" "github.com/operator-framework/kubectl-operator/pkg/action" ) @@ -86,7 +87,7 @@ func (u *OperatorUninstall) Run(ctx context.Context) error { } // find operands related to the operator on cluster - lister := action.NewOperatorListOperands(u.config) + lister := v0.NewOperatorListOperands(u.config) operands, err := lister.Run(ctx, u.Package) if err != nil { return fmt.Errorf("list operands for operator %q: %v", u.Package, err) diff --git a/internal/pkg/action/operator_uninstall_test.go b/internal/pkg/action/v0/operator_uninstall_test.go similarity index 98% rename from internal/pkg/action/operator_uninstall_test.go rename to internal/pkg/action/v0/operator_uninstall_test.go index d9f141b..d045107 100644 --- a/internal/pkg/action/operator_uninstall_test.go +++ b/internal/pkg/action/v0/operator_uninstall_test.go @@ -1,4 +1,4 @@ -package action_test +package v0_test import ( "context" @@ -18,8 +18,8 @@ import ( v1 "github.com/operator-framework/api/pkg/operators/v1" "github.com/operator-framework/api/pkg/operators/v1alpha1" - internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action" - "github.com/operator-framework/kubectl-operator/internal/pkg/operand" + internalaction "github.com/operator-framework/kubectl-operator/internal/pkg/action/v0" + "github.com/operator-framework/kubectl-operator/internal/pkg/legacy/operand" "github.com/operator-framework/kubectl-operator/pkg/action" ) diff --git a/internal/pkg/action/operator_upgrade.go b/internal/pkg/action/v0/operator_upgrade.go similarity index 99% rename from internal/pkg/action/operator_upgrade.go rename to internal/pkg/action/v0/operator_upgrade.go index a2e43cb..59e7ad7 100644 --- a/internal/pkg/action/operator_upgrade.go +++ b/internal/pkg/action/v0/operator_upgrade.go @@ -1,4 +1,4 @@ -package action +package v0 import ( "context" diff --git a/internal/pkg/action/v1/action_suite_test.go b/internal/pkg/action/v1/action_suite_test.go new file mode 100644 index 0000000..8f3c900 --- /dev/null +++ b/internal/pkg/action/v1/action_suite_test.go @@ -0,0 +1,13 @@ +package v1_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestCommand(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Internal action Suite") +} diff --git a/internal/pkg/action/v1/catalog_add.go b/internal/pkg/action/v1/catalog_add.go new file mode 100644 index 0000000..d2e1a37 --- /dev/null +++ b/internal/pkg/action/v1/catalog_add.go @@ -0,0 +1,122 @@ +package v1 + +import ( + "context" + "fmt" + "time" + + "k8s.io/apimachinery/pkg/util/wait" + + "errors" + catalogdv1 "github.com/operator-framework/catalogd/api/v1" + "github.com/operator-framework/kubectl-operator/pkg/action" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type CatalogAdd struct { + config *action.Configuration + + CatalogName string + Labels map[string]string + CatalogImage string + Priority int32 + PollInterval time.Duration + + CleanupTimeout time.Duration + Logf func(string, ...interface{}) +} + +func NewCatalogAdd(cfg *action.Configuration) *CatalogAdd { + return &CatalogAdd{ + config: cfg, + Logf: func(string, ...interface{}) {}, + } +} + +func (a *CatalogAdd) applyClusterCatalog(ctx context.Context) error { + catalogMetadata := map[string]interface{}{ + "name": a.CatalogName, + } + if a.Labels != nil { + catalogMetadata["labels"] = a.Labels + } + + catalogImageSource := map[string]interface{}{ + "ref": a.CatalogImage, + } + if a.PollInterval != 0 { + catalogImageSource["pollInterval"] = metav1.Duration{Duration: a.PollInterval} + } + u := unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": catalogdv1.GroupVersion.String(), + "kind": "ClusterCatalog", + "metadata": catalogMetadata, + "spec": map[string]interface{}{ + "priority": a.Priority, + "source": map[string]interface{}{ + "type": catalogdv1.SourceTypeImage, + "image": catalogImageSource, + }, + }, + }} + return patchObject(ctx, a.config.Client, &u) +} + +func (a *CatalogAdd) Run(ctx context.Context) (*catalogdv1.ClusterCatalog, error) { + if err := a.applyClusterCatalog(ctx); err != nil { + return nil, fmt.Errorf("apply clustercatalog: %v", err) + } + + clusterCatalog, err := a.waitForCatalogServing(ctx) + if err != nil { + cleanupCtx, cancelCleanup := context.WithTimeout(context.Background(), a.CleanupTimeout) + defer cancelCleanup() + cleanupErr := a.cleanup(cleanupCtx) + return nil, errors.Join(err, cleanupErr) + } + + return clusterCatalog, nil +} + +func (a *CatalogAdd) waitForCatalogServing(ctx context.Context) (*catalogdv1.ClusterCatalog, error) { + clusterCatalog := &catalogdv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: a.CatalogName, + }, + } + + errMsg := "" + csKey := client.ObjectKeyFromObject(clusterCatalog) + if err := wait.PollUntilContextCancel(ctx, time.Millisecond*250, true, func(conditionCtx context.Context) (bool, error) { + if err := a.config.Client.Get(conditionCtx, csKey, clusterCatalog); err != nil { + return false, err + } + progressingCondition := meta.FindStatusCondition(clusterCatalog.Status.Conditions, catalogdv1.TypeProgressing) + if progressingCondition != nil && progressingCondition.Reason != catalogdv1.ReasonSucceeded { + errMsg = progressingCondition.Message + return false, nil + } + if !meta.IsStatusConditionPresentAndEqual(clusterCatalog.Status.Conditions, catalogdv1.TypeServing, metav1.ConditionTrue) { + return false, nil + } + return true, nil + }); err != nil { + if errMsg == "" { + errMsg = err.Error() + } + return nil, fmt.Errorf("clustercatalog %q did not start serving: %s", clusterCatalog.Name, errMsg) + } + return clusterCatalog, nil +} + +func (a *CatalogAdd) cleanup(ctx context.Context) error { + clusterCatalog := &catalogdv1.ClusterCatalog{ + ObjectMeta: metav1.ObjectMeta{ + Name: a.CatalogName, + }, + } + return deleteAndWait(ctx, a.config.Client, clusterCatalog) +} diff --git a/internal/pkg/action/v1/catalog_content.go b/internal/pkg/action/v1/catalog_content.go new file mode 100644 index 0000000..d6456ed --- /dev/null +++ b/internal/pkg/action/v1/catalog_content.go @@ -0,0 +1,38 @@ +package v1 + +import ( + "context" + catalogdv1 "github.com/operator-framework/catalogd/api/v1" + "github.com/operator-framework/kubectl-operator/internal/pkg/catalog" + "github.com/operator-framework/kubectl-operator/pkg/action" + "github.com/operator-framework/operator-registry/alpha/declcfg" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type CatalogContent struct { + config *action.Configuration + + CatalogName string + WalkMetas declcfg.WalkMetasReaderFunc +} + +func NewCatalogContent(cfg *action.Configuration) *CatalogContent { + return &CatalogContent{ + config: cfg, + } +} + +func (r *CatalogContent) Run(ctx context.Context) error { + clusterCatalog := catalogdv1.ClusterCatalog{} + if err := r.config.Client.Get(ctx, client.ObjectKey{Name: r.CatalogName}, &clusterCatalog); err != nil { + return err + } + + catalogClient := catalog.NewK8sClient(r.config.Config, r.config.Client, &clusterCatalog) + catalogContent, err := catalogClient.V1().All(ctx) + if err != nil { + return err + } + defer catalogContent.Close() + return declcfg.WalkMetasReader(catalogContent, r.WalkMetas) +} diff --git a/internal/pkg/action/v1/catalog_list.go b/internal/pkg/action/v1/catalog_list.go new file mode 100644 index 0000000..a38ce4b --- /dev/null +++ b/internal/pkg/action/v1/catalog_list.go @@ -0,0 +1,24 @@ +package v1 + +import ( + "context" + + catalogdv1 "github.com/operator-framework/catalogd/api/v1" + "github.com/operator-framework/kubectl-operator/pkg/action" +) + +type CatalogList struct { + config *action.Configuration +} + +func NewCatalogList(cfg *action.Configuration) *CatalogList { + return &CatalogList{cfg} +} + +func (l *CatalogList) Run(ctx context.Context) ([]catalogdv1.ClusterCatalog, error) { + clusterCatalogList := catalogdv1.ClusterCatalogList{} + if err := l.config.Client.List(ctx, &clusterCatalogList); err != nil { + return nil, err + } + return clusterCatalogList.Items, nil +} diff --git a/internal/pkg/action/v1/catalog_remove.go b/internal/pkg/action/v1/catalog_remove.go new file mode 100644 index 0000000..97ba9df --- /dev/null +++ b/internal/pkg/action/v1/catalog_remove.go @@ -0,0 +1,25 @@ +package v1 + +import ( + "context" + catalogdv1 "github.com/operator-framework/catalogd/api/v1" + "github.com/operator-framework/kubectl-operator/pkg/action" +) + +type CatalogRemove struct { + config *action.Configuration + + CatalogName string +} + +func NewCatalogRemove(cfg *action.Configuration) *CatalogRemove { + return &CatalogRemove{ + config: cfg, + } +} + +func (r *CatalogRemove) Run(ctx context.Context) error { + clusterCatalog := catalogdv1.ClusterCatalog{} + clusterCatalog.SetName(r.CatalogName) + return deleteAndWait(ctx, r.config.Client, &clusterCatalog) +} diff --git a/internal/pkg/action/v1/helpers.go b/internal/pkg/action/v1/helpers.go new file mode 100644 index 0000000..bedcbf9 --- /dev/null +++ b/internal/pkg/action/v1/helpers.go @@ -0,0 +1,71 @@ +package v1 + +import ( + "context" + "fmt" + "strings" + "time" + + "errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" + "sync" +) + +func deleteAndWait(ctx context.Context, cl client.Client, objs ...client.Object) error { + var ( + wg sync.WaitGroup + errs = make([]error, len(objs)) + ) + for i := range objs { + wg.Add(1) + go func(objectIndex int) { + defer wg.Done() + obj := objs[objectIndex] + gvk := obj.GetObjectKind().GroupVersionKind() + if gvk.Empty() { + gvks, unversioned, err := cl.Scheme().ObjectKinds(obj) + if err == nil && !unversioned && len(gvks) > 0 { + gvk = gvks[0] + } + } + lowerKind := strings.ToLower(gvk.Kind) + key := client.ObjectKeyFromObject(obj) + + err := cl.Delete(ctx, obj) + if err != nil && !apierrors.IsNotFound(err) { + errs[i] = fmt.Errorf("delete %s %q: %v", lowerKind, key.Name, err) + return + } + + if err := wait.PollUntilContextCancel(ctx, 250*time.Millisecond, true, func(conditionCtx context.Context) (bool, error) { + if err := cl.Get(conditionCtx, key, obj); apierrors.IsNotFound(err) { + return true, nil + } else if err != nil { + return false, err + } + return false, nil + }); err != nil { + errs[i] = fmt.Errorf("wait for %s %q deleted: %v", lowerKind, key.Name, err) + return + } + }(i) + } + wg.Wait() + return errors.Join(errs...) +} + +func patchObject(ctx context.Context, cl client.Client, obj interface{}) error { + var ( + u unstructured.Unstructured + err error + ) + u.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return err + } + return cl.Patch(ctx, &u, client.Apply) +} diff --git a/internal/pkg/action/v1/operator_install.go b/internal/pkg/action/v1/operator_install.go new file mode 100644 index 0000000..4e9d2bc --- /dev/null +++ b/internal/pkg/action/v1/operator_install.go @@ -0,0 +1,194 @@ +package v1 + +import ( + "context" + "fmt" + "time" + + "errors" + "github.com/operator-framework/kubectl-operator/pkg/action" + ocv1 "github.com/operator-framework/operator-controller/api/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/util/wait" + applyconfigurationscorev1 "k8s.io/client-go/applyconfigurations/core/v1" + applyconfigurationsrbacv1 "k8s.io/client-go/applyconfigurations/rbac/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type OperatorInstall struct { + config *action.Configuration + + UnsafeCreateClusterRoleBinding bool + + Namespace OperatorInstallNamespaceConfig + ServiceAccount string + + Package string + Channels []string + Version string + CatalogSelector metav1.LabelSelector + + CleanupTimeout time.Duration + + Logf func(string, ...interface{}) +} + +type OperatorInstallNamespaceConfig struct { + Name string + Labels map[string]string + Annotations map[string]string +} + +func NewOperatorInstall(cfg *action.Configuration) *OperatorInstall { + return &OperatorInstall{ + config: cfg, + Logf: func(string, ...interface{}) {}, + } +} + +func (i *OperatorInstall) applyNamespace(ctx context.Context) error { + ac := applyconfigurationscorev1.Namespace(i.Namespace.Name) + if i.Namespace.Labels != nil { + ac = ac.WithLabels(i.Namespace.Labels) + } + if i.Namespace.Annotations != nil { + ac = ac.WithAnnotations(i.Namespace.Annotations) + } + return patchObject(ctx, i.config.Client, ac) +} + +func (i *OperatorInstall) applyServiceAccount(ctx context.Context) error { + ac := applyconfigurationscorev1.ServiceAccount(i.ServiceAccount, i.Namespace.Name) + return patchObject(ctx, i.config.Client, ac) +} + +func (i *OperatorInstall) applyClusterRoleBinding(ctx context.Context, clusterRoleName string) error { + name := fmt.Sprintf("kubectl-operator-%s-cluster-admin", i.ServiceAccount) + ac := applyconfigurationsrbacv1.ClusterRoleBinding(name). + WithSubjects(applyconfigurationsrbacv1.Subject().WithNamespace(i.Namespace.Name).WithKind("ServiceAccount").WithName(i.ServiceAccount)). + WithRoleRef(applyconfigurationsrbacv1.RoleRef().WithKind("ClusterRole").WithName(clusterRoleName)) + return patchObject(ctx, i.config.Client, ac) +} + +func (i *OperatorInstall) applyClusterExtension(ctx context.Context) error { + catalogSource := map[string]interface{}{ + "packageName": i.Package, + } + if i.Version != "" { + catalogSource["version"] = i.Version + } + if i.Channels != nil { + catalogSource["channels"] = i.Channels + } + if i.CatalogSelector.MatchLabels != nil || i.CatalogSelector.MatchExpressions != nil { + catalogSource["selector"] = i.CatalogSelector + } + u := unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": ocv1.GroupVersion.String(), + "kind": "ClusterExtension", + "metadata": map[string]interface{}{ + "name": i.Package, + }, + "spec": map[string]interface{}{ + "namespace": i.Namespace.Name, + "serviceAccount": map[string]interface{}{ + "name": i.ServiceAccount, + }, + "source": map[string]interface{}{ + "sourceType": "Catalog", + "catalog": catalogSource, + }, + }, + }} + return patchObject(ctx, i.config.Client, &u) +} + +func (i *OperatorInstall) Run(ctx context.Context) (*ocv1.ClusterExtension, error) { + if err := i.applyNamespace(ctx); err != nil { + return nil, fmt.Errorf("apply namespace %q: %v", i.Namespace.Name, err) + } + if err := i.applyServiceAccount(ctx); err != nil { + return nil, fmt.Errorf("apply service account %q: %v", i.ServiceAccount, err) + } + + if i.UnsafeCreateClusterRoleBinding { + if err := i.applyClusterRoleBinding(ctx, "cluster-admin"); err != nil { + return nil, fmt.Errorf("apply cluster role binding: %v", err) + } + } + + if err := i.applyClusterExtension(ctx); err != nil { + return nil, fmt.Errorf("apply cluster extension: %v", err) + } + + clusterExtension, err := i.waitForClusterExtensionInstalled(ctx) + if err != nil { + cleanupCtx, cancelCleanup := context.WithTimeout(context.Background(), i.CleanupTimeout) + defer cancelCleanup() + cleanupErr := i.cleanup(cleanupCtx) + return nil, errors.Join(err, cleanupErr) + } + return clusterExtension, nil +} + +func (i *OperatorInstall) waitForClusterExtensionInstalled(ctx context.Context) (*ocv1.ClusterExtension, error) { + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: i.Package, + }, + } + errMsg := "" + key := client.ObjectKeyFromObject(clusterExtension) + if err := wait.PollUntilContextCancel(ctx, time.Millisecond*250, true, func(conditionCtx context.Context) (bool, error) { + if err := i.config.Client.Get(conditionCtx, key, clusterExtension); err != nil { + return false, err + } + progressingCondition := meta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing) + if progressingCondition != nil && progressingCondition.Reason != ocv1.ReasonSucceeded { + errMsg = progressingCondition.Message + return false, nil + } + if !meta.IsStatusConditionPresentAndEqual(clusterExtension.Status.Conditions, ocv1.TypeInstalled, metav1.ConditionTrue) { + return false, nil + } + return true, nil + }); err != nil { + if errMsg == "" { + errMsg = err.Error() + } + return nil, fmt.Errorf("cluster extension %q did not finish installing: %s", clusterExtension.Name, errMsg) + } + return clusterExtension, nil +} + +func (i *OperatorInstall) cleanup(ctx context.Context) error { + clusterExtension := &ocv1.ClusterExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: i.Package, + }, + } + clusterRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("kubectl-operator-%s-cluster-admin", i.ServiceAccount), + }, + } + serviceAccount := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: i.Namespace.Name, + Name: i.ServiceAccount, + }, + } + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: i.Namespace.Name, + }, + } + if err := deleteAndWait(ctx, i.config.Client, clusterExtension); err != nil { + return fmt.Errorf("delete clusterextension %q: %v", i.Package, err) + } + return deleteAndWait(ctx, i.config.Client, clusterRoleBinding, serviceAccount, namespace) +} diff --git a/internal/pkg/action/v1/operator_list.go b/internal/pkg/action/v1/operator_list.go new file mode 100644 index 0000000..695634a --- /dev/null +++ b/internal/pkg/action/v1/operator_list.go @@ -0,0 +1,24 @@ +package v1 + +import ( + "context" + + "github.com/operator-framework/kubectl-operator/pkg/action" + ocv1 "github.com/operator-framework/operator-controller/api/v1" +) + +type OperatorList struct { + config *action.Configuration +} + +func NewOperatorList(cfg *action.Configuration) *OperatorList { + return &OperatorList{cfg} +} + +func (l *OperatorList) Run(ctx context.Context) ([]ocv1.ClusterExtension, error) { + clusterExtensions := ocv1.ClusterExtensionList{} + if err := l.config.Client.List(ctx, &clusterExtensions); err != nil { + return nil, err + } + return clusterExtensions.Items, nil +} diff --git a/internal/pkg/action/v1/operator_uninstall.go b/internal/pkg/action/v1/operator_uninstall.go new file mode 100644 index 0000000..ef86692 --- /dev/null +++ b/internal/pkg/action/v1/operator_uninstall.go @@ -0,0 +1,70 @@ +package v1 + +import ( + "context" + "fmt" + "github.com/operator-framework/kubectl-operator/pkg/action" + ocv1 "github.com/operator-framework/operator-controller/api/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +type OperatorUninstall struct { + config *action.Configuration + + Package string + + Logf func(string, ...interface{}) +} + +func NewOperatorUninstall(cfg *action.Configuration) *OperatorUninstall { + return &OperatorUninstall{ + config: cfg, + Logf: func(string, ...interface{}) {}, + } +} + +type ErrPackageNotFound struct { + PackageName string +} + +func (e ErrPackageNotFound) Error() string { + return fmt.Sprintf("package %q not found", e.PackageName) +} + +func (u *OperatorUninstall) Run(ctx context.Context) error { + clusterExtension := &ocv1.ClusterExtension{} + if err := u.config.Client.Get(ctx, types.NamespacedName{Name: u.Package}, clusterExtension); err != nil { + if apierrors.IsNotFound(err) { + return &ErrPackageNotFound{u.Package} + } + return fmt.Errorf("get clusterextension %q: %v", u.Package, err) + } + + namespaceName := clusterExtension.Spec.Namespace + saName := clusterExtension.Spec.ServiceAccount.Name + + clusterRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("kubectl-operator-%s-cluster-admin", saName), + }, + } + serviceAccount := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespaceName, + Name: saName, + }, + } + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespaceName, + }, + } + if err := deleteAndWait(ctx, u.config.Client, clusterExtension); err != nil { + return fmt.Errorf("delete clusterextension %q: %v", u.Package, err) + } + return deleteAndWait(ctx, u.config.Client, clusterRoleBinding, serviceAccount, namespace) +} diff --git a/internal/pkg/catalog/client.go b/internal/pkg/catalog/client.go new file mode 100644 index 0000000..cb0f083 --- /dev/null +++ b/internal/pkg/catalog/client.go @@ -0,0 +1,260 @@ +package catalog + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + catalogdv1 "github.com/operator-framework/catalogd/api/v1" + "io" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/portforward" + "k8s.io/client-go/transport/spdy" + "math/rand" + "net/http" + "net/url" + "os" + "sigs.k8s.io/controller-runtime/pkg/client" + "strconv" + "strings" +) + +type Client interface { + V1() V1Client +} + +type V1Client interface { + All(ctx context.Context) (io.ReadCloser, error) +} + +type LiveClient struct { + HTTPClient *http.Client + BaseURL *url.URL +} + +func (c *LiveClient) V1() V1Client { + return &LiveClientV1{c} +} + +type LiveClientV1 struct { + *LiveClient +} + +func (c *LiveClientV1) All(ctx context.Context) (io.ReadCloser, error) { + allURL := c.LiveClient.BaseURL.JoinPath("api", "v1", "all").String() + req, err := http.NewRequestWithContext(ctx, http.MethodGet, allURL, nil) + if err != nil { + return nil, err + } + resp, err := c.LiveClient.HTTPClient.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code %d", resp.StatusCode) + } + return resp.Body, nil +} + +func NewK8sClient(cfg *rest.Config, cl client.Client, cc *catalogdv1.ClusterCatalog) Client { + + c := &portForwardClient{ + cfg: cfg, + cl: cl, + cc: cc, + } + c.httpClient = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: c.loadKnownCAs(), + }, + }, + } + return c +} + +type portForwardClient struct { + cfg *rest.Config + cl client.Client + cc *catalogdv1.ClusterCatalog + httpClient *http.Client +} + +func (c *portForwardClient) V1() V1Client { + return &portForwardClientV1{c} +} + +type portForwardClientV1 struct { + *portForwardClient +} + +func (c *portForwardClientV1) All(ctx context.Context) (io.ReadCloser, error) { + if !meta.IsStatusConditionTrue(c.cc.Status.Conditions, catalogdv1.TypeServing) { + return nil, fmt.Errorf("cluster extension %q is not serving", c.cc.Name) + } + if c.cc.Status.URLs == nil { + return nil, fmt.Errorf("cluster extension %q has no URLs", c.cc.Name) + } + baseURL, err := url.Parse(c.cc.Status.URLs.Base) + if err != nil { + return nil, err + } + serviceHostname := baseURL.Hostname() + servicePortStr := baseURL.Port() + if servicePortStr == "" { + switch baseURL.Scheme { + case "http": + servicePortStr = "80" + case "https": + servicePortStr = "443" + } + } + servicePort, err := strconv.Atoi(servicePortStr) + if err != nil { + return nil, err + } + + labels := strings.Split(serviceHostname, ".") + if len(labels) < 2 { + return nil, fmt.Errorf("invalid base URL %q", c.cc.Status.URLs.Base) + } + serviceName := labels[0] + namespace := labels[1] + + // Find a pod and pod port for the given service + podName, podPort, err := c.getPodAndPortForService(ctx, namespace, serviceName, int32(servicePort)) + if err != nil { + return nil, err + } + + pf, err := c.getPortForwarder(namespace, podName, podPort) + if err != nil { + return nil, err + } + + fwdErr := make(chan error, 1) + go func() { + fwdErr <- pf.ForwardPorts() + }() + + defer pf.Close() + select { + case <-ctx.Done(): + return nil, ctx.Err() + case err := <-fwdErr: + return nil, err + case <-pf.Ready: + } + forwardedPorts, err := pf.GetPorts() + if err != nil { + return nil, err + } + if len(forwardedPorts) != 1 { + return nil, fmt.Errorf("expected 1 forwarded port, got %d", len(forwardedPorts)) + } + localPort := forwardedPorts[0].Local + + localURL := url.URL{ + Scheme: baseURL.Scheme, + Host: fmt.Sprintf("localhost:%d", localPort), + Path: baseURL.Path, + } + liveClient := &LiveClient{ + HTTPClient: c.httpClient, + BaseURL: &localURL, + } + return liveClient.V1().All(ctx) +} + +// Get a pod for a given service +func (c *portForwardClient) getPodAndPortForService(ctx context.Context, namespace, serviceName string, servicePort int32) (string, int32, error) { + svc := corev1.Service{} + if err := c.cl.Get(ctx, client.ObjectKey{Name: serviceName, Namespace: namespace}, &svc); err != nil { + return "", -1, err + } + + podPort := -1 + for _, port := range svc.Spec.Ports { + if port.Port == servicePort { + podPort = port.TargetPort.IntValue() + break + } + } + if podPort == -1 { + return "", -1, fmt.Errorf("service %q has no port %q", serviceName, servicePort) + } + + endpoints := corev1.Endpoints{} + if err := c.cl.Get(ctx, client.ObjectKey{Name: serviceName, Namespace: namespace}, &endpoints); err != nil { + return "", -1, err + } + + readyAddresses := []corev1.EndpointAddress{} + for _, subset := range endpoints.Subsets { + readyAddresses = append(readyAddresses, subset.Addresses...) + } + + randAddress := rand.Int31n(int32(len(readyAddresses))) + address := readyAddresses[randAddress] + podName := address.TargetRef.Name + + // Select the first pod (or you could add load balancing logic here) + return podName, int32(podPort), nil +} + +// Port forwarding logic to connect to a pod +func (c *portForwardClient) getPortForwarder(namespace, podName string, podPort int32) (*portforward.PortForwarder, error) { + apiserverURL, err := url.Parse(c.cfg.Host) + if err != nil { + return nil, err + } + + portForwardURL := apiserverURL.JoinPath( + "api", "v1", + "namespaces", namespace, + "pods", podName, "portforward", + ) + + transport, upgrader, err := spdy.RoundTripperFor(c.cfg) + if err != nil { + return nil, err + } + + dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", portForwardURL) + + ports := []string{fmt.Sprintf("0:%d", podPort)} + stopChan := make(chan struct{}, 1) + readyChan := make(chan struct{}, 1) + + pf, err := portforward.New(dialer, ports, stopChan, readyChan, io.Discard, os.Stderr) + if err != nil { + return nil, err + } + + return pf, nil +} + +func (c *portForwardClient) loadKnownCAs() *x509.CertPool { + knownCAsSecrets := []struct { + Namespace string + Name string + Key string + }{ + {"olmv1-system", "olmv1-cert", "ca.crt"}, + } + rootCAs := x509.NewCertPool() + for _, secretInfo := range knownCAsSecrets { + secret := corev1.Secret{} + if err := c.cl.Get(context.TODO(), client.ObjectKey{Name: secretInfo.Name, Namespace: secretInfo.Namespace}, &secret); err != nil { + continue + } + caCert, ok := secret.Data[secretInfo.Key] + if !ok { + continue + } + rootCAs.AppendCertsFromPEM(caCert) + } + return rootCAs +} diff --git a/internal/pkg/experimental/action/operator.go b/internal/pkg/experimental/action/operator.go deleted file mode 100644 index 37204bf..0000000 --- a/internal/pkg/experimental/action/operator.go +++ /dev/null @@ -1,9 +0,0 @@ -package action - -import ( - "time" -) - -const ( - pollTimeout = 250 * time.Millisecond -) diff --git a/internal/pkg/experimental/action/operator_install.go b/internal/pkg/experimental/action/operator_install.go deleted file mode 100644 index 59aee2a..0000000 --- a/internal/pkg/experimental/action/operator_install.go +++ /dev/null @@ -1,75 +0,0 @@ -package action - -import ( - "context" - "fmt" - - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" - - olmv1 "github.com/operator-framework/operator-controller/api/v1alpha1" - - "github.com/operator-framework/kubectl-operator/pkg/action" -) - -type OperatorInstall struct { - config *action.Configuration - - Package string - - Logf func(string, ...interface{}) -} - -func NewOperatorInstall(cfg *action.Configuration) *OperatorInstall { - return &OperatorInstall{ - config: cfg, - Logf: func(string, ...interface{}) {}, - } -} - -func (i *OperatorInstall) Run(ctx context.Context) (*olmv1.ClusterExtension, error) { - // TODO(developer): Lookup package information when the OLMv1 equivalent of the - // packagemanifests API is available. That way, we can check to see if the - // package is actually available to the cluster before creating the Operator - // object. - - opKey := types.NamespacedName{Name: i.Package} - op := &olmv1.ClusterExtension{ - ObjectMeta: metav1.ObjectMeta{Name: opKey.Name}, - Spec: olmv1.ClusterExtensionSpec{ - Source: olmv1.SourceConfig{ - SourceType: "Catalog", - Catalog: &olmv1.CatalogSource{ - PackageName: i.Package, - }, - }, - }, - } - if err := i.config.Client.Create(ctx, op); err != nil { - return nil, err - } - - // TODO(developer): Improve the logic in this poll wait once the Operator reconciler - // and conditions types and reasons are improved. For now, this will stop waiting as - // soon as a Ready condition is found, but we should probably wait until the Operator - // stops progressing. - // All Types will exist, so Ready may have a false Status. So, wait until - // Type=Ready,Status=True happens - - if err := wait.PollUntilContextCancel(ctx, pollTimeout, true, func(conditionCtx context.Context) (bool, error) { - if err := i.config.Client.Get(conditionCtx, opKey, op); err != nil { - return false, err - } - installedCondition := meta.FindStatusCondition(op.Status.Conditions, olmv1.TypeInstalled) - if installedCondition != nil && installedCondition.Status == metav1.ConditionTrue { - return true, nil - } - return false, nil - }); err != nil { - return nil, fmt.Errorf("waiting for operator to become ready: %v", err) - } - - return op, nil -} diff --git a/internal/pkg/experimental/action/operator_uninstall.go b/internal/pkg/experimental/action/operator_uninstall.go deleted file mode 100644 index 83ef261..0000000 --- a/internal/pkg/experimental/action/operator_uninstall.go +++ /dev/null @@ -1,70 +0,0 @@ -package action - -import ( - "context" - "fmt" - "strings" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" - "sigs.k8s.io/controller-runtime/pkg/client" - - olmv1 "github.com/operator-framework/operator-controller/api/v1alpha1" - - "github.com/operator-framework/kubectl-operator/pkg/action" -) - -type OperatorUninstall struct { - config *action.Configuration - - Package string - - Logf func(string, ...interface{}) -} - -func NewOperatorUninstall(cfg *action.Configuration) *OperatorUninstall { - return &OperatorUninstall{ - config: cfg, - Logf: func(string, ...interface{}) {}, - } -} - -func (u *OperatorUninstall) Run(ctx context.Context) error { - opKey := types.NamespacedName{Name: u.Package} - op := &olmv1.ClusterExtension{} - op.SetName(opKey.Name) - op.SetGroupVersionKind(olmv1.GroupVersion.WithKind("Operator")) - - lowerKind := strings.ToLower(op.GetObjectKind().GroupVersionKind().Kind) - if err := u.config.Client.Delete(ctx, op); err != nil && !apierrors.IsNotFound(err) { - return fmt.Errorf("delete %s %q: %v", lowerKind, op.GetName(), err) - } - return waitForDeletion(ctx, u.config.Client, op) -} - -func objectKeyForObject(obj client.Object) types.NamespacedName { - return types.NamespacedName{ - Namespace: obj.GetNamespace(), - Name: obj.GetName(), - } -} - -func waitForDeletion(ctx context.Context, cl client.Client, objs ...client.Object) error { - for _, obj := range objs { - obj := obj - lowerKind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind) - key := objectKeyForObject(obj) - if err := wait.PollUntilContextCancel(ctx, pollTimeout, true, func(conditionCtx context.Context) (bool, error) { - if err := cl.Get(conditionCtx, key, obj); apierrors.IsNotFound(err) { - return true, nil - } else if err != nil { - return false, err - } - return false, nil - }); err != nil { - return fmt.Errorf("wait for %s %q deleted: %v", lowerKind, key.Name, err) - } - } - return nil -} diff --git a/internal/pkg/catalogsource/catalogsource.go b/internal/pkg/legacy/catalogsource/catalogsource.go similarity index 100% rename from internal/pkg/catalogsource/catalogsource.go rename to internal/pkg/legacy/catalogsource/catalogsource.go diff --git a/internal/pkg/operand/strategy.go b/internal/pkg/legacy/operand/strategy.go similarity index 100% rename from internal/pkg/operand/strategy.go rename to internal/pkg/legacy/operand/strategy.go diff --git a/internal/pkg/operator/package.go b/internal/pkg/legacy/operator/package.go similarity index 100% rename from internal/pkg/operator/package.go rename to internal/pkg/legacy/operator/package.go diff --git a/internal/pkg/subscription/subscription.go b/internal/pkg/legacy/subscription/subscription.go similarity index 100% rename from internal/pkg/subscription/subscription.go rename to internal/pkg/legacy/subscription/subscription.go diff --git a/pkg/action/config.go b/pkg/action/config.go index 879c179..878c1aa 100644 --- a/pkg/action/config.go +++ b/pkg/action/config.go @@ -1,28 +1,30 @@ package action import ( - "context" - + catalogdv1 "github.com/operator-framework/catalogd/api/v1" "github.com/spf13/pflag" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/controller-runtime/pkg/client" - v1 "github.com/operator-framework/api/pkg/operators/v1" - "github.com/operator-framework/api/pkg/operators/v1alpha1" - olmv1 "github.com/operator-framework/operator-controller/api/v1alpha1" - operatorsv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" + ofapiv1 "github.com/operator-framework/api/pkg/operators/v1" + ofapiv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + ocv1 "github.com/operator-framework/operator-controller/api/v1" + packageserveroperatorsv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" ) func NewScheme() (*runtime.Scheme, error) { - sch := runtime.NewScheme() + sch := scheme.Scheme for _, f := range []func(*runtime.Scheme) error{ - v1alpha1.AddToScheme, - operatorsv1.AddToScheme, - v1.AddToScheme, + ofapiv1alpha1.AddToScheme, + packageserveroperatorsv1.AddToScheme, + ofapiv1.AddToScheme, apiextensionsv1.AddToScheme, - olmv1.AddToScheme, + ocv1.AddToScheme, + catalogdv1.AddToScheme, } { if err := f(sch); err != nil { return nil, err @@ -32,6 +34,7 @@ func NewScheme() (*runtime.Scheme, error) { } type Configuration struct { + Config *rest.Config Client client.Client Namespace string Scheme *runtime.Scheme @@ -86,18 +89,10 @@ func (c *Configuration) Load() error { return err } + c.Config = cc c.Scheme = sch - c.Client = &operatorClient{cl} + c.Client = client.WithFieldOwner(cl, "kubectl-operator") c.Namespace = ns return nil } - -type operatorClient struct { - client.Client -} - -func (c *operatorClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { - opts = append(opts, client.FieldOwner("kubectl-operator")) - return c.Client.Create(ctx, obj, opts...) -} diff --git a/pkg/action/action_suite_test.go b/pkg/action/v0/action_suite_test.go similarity index 89% rename from pkg/action/action_suite_test.go rename to pkg/action/v0/action_suite_test.go index 0ea7afc..cd7bcb3 100644 --- a/pkg/action/action_suite_test.go +++ b/pkg/action/v0/action_suite_test.go @@ -1,4 +1,4 @@ -package action_test +package v0_test import ( "testing" diff --git a/pkg/action/operator_list_operands.go b/pkg/action/v0/operator_list_operands.go similarity index 97% rename from pkg/action/operator_list_operands.go rename to pkg/action/v0/operator_list_operands.go index c43b0b0..890cc3c 100644 --- a/pkg/action/operator_list_operands.go +++ b/pkg/action/v0/operator_list_operands.go @@ -1,8 +1,9 @@ -package action +package v0 import ( "context" "fmt" + "github.com/operator-framework/kubectl-operator/pkg/action" "sort" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -18,10 +19,10 @@ import ( // OperatorListOperands knows how to find and list custom resources given a package name and namespace. type OperatorListOperands struct { - config *Configuration + config *action.Configuration } -func NewOperatorListOperands(cfg *Configuration) *OperatorListOperands { +func NewOperatorListOperands(cfg *action.Configuration) *OperatorListOperands { return &OperatorListOperands{ config: cfg, } diff --git a/pkg/action/operator_list_operands_test.go b/pkg/action/v0/operator_list_operands_test.go similarity index 92% rename from pkg/action/operator_list_operands_test.go rename to pkg/action/v0/operator_list_operands_test.go index 32d887a..f03eabd 100644 --- a/pkg/action/operator_list_operands_test.go +++ b/pkg/action/v0/operator_list_operands_test.go @@ -1,7 +1,8 @@ -package action_test +package v0_test import ( "context" + "github.com/operator-framework/kubectl-operator/pkg/action/v0" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -131,7 +132,7 @@ var _ = Describe("OperatorListOperands", func() { }) It("should fail due to missing operator", func() { - lister := action.NewOperatorListOperands(&cfg) + lister := v0.NewOperatorListOperands(&cfg) _, err := lister.Run(context.TODO(), "missing") Expect(err.Error()).To(ContainSubstring(`package "missing" not found in namespace "etcd-namespace"`)) }) @@ -140,7 +141,7 @@ var _ = Describe("OperatorListOperands", func() { operator.Status.Components = nil Expect(cfg.Client.Update(context.TODO(), operator)).To(Succeed()) - lister := action.NewOperatorListOperands(&cfg) + lister := v0.NewOperatorListOperands(&cfg) _, err := lister.Run(context.TODO(), "etcd") Expect(err.Error()).To(ContainSubstring("could not find underlying components for operator")) }) @@ -149,7 +150,7 @@ var _ = Describe("OperatorListOperands", func() { operator.Status.Components = &v1.Components{} Expect(cfg.Client.Update(context.TODO(), operator)).To(Succeed()) - lister := action.NewOperatorListOperands(&cfg) + lister := v0.NewOperatorListOperands(&cfg) _, err := lister.Run(context.TODO(), "etcd") Expect(err.Error()).To(ContainSubstring("could not find underlying CSV for operator")) }) @@ -157,7 +158,7 @@ var _ = Describe("OperatorListOperands", func() { It("should fail due to missing CSV in cluster", func() { Expect(cfg.Client.Delete(context.TODO(), csv)).To(Succeed()) - lister := action.NewOperatorListOperands(&cfg) + lister := v0.NewOperatorListOperands(&cfg) _, err := lister.Run(context.TODO(), "etcd") Expect(err.Error()).To(ContainSubstring("could not get etcd-namespace/etcdoperator.v0.9.4-clusterwide CSV on cluster")) }) @@ -166,7 +167,7 @@ var _ = Describe("OperatorListOperands", func() { csv.Spec.CustomResourceDefinitions.Owned = nil Expect(cfg.Client.Update(context.TODO(), csv)).To(Succeed()) - lister := action.NewOperatorListOperands(&cfg) + lister := v0.NewOperatorListOperands(&cfg) _, err := lister.Run(context.TODO(), "etcd") Expect(err.Error()).To(ContainSubstring("no owned CustomResourceDefinitions specified on CSV etcd-namespace/etcdoperator.v0.9.4-clusterwide")) }) @@ -175,7 +176,7 @@ var _ = Describe("OperatorListOperands", func() { csv.Status.Phase = v1alpha1.CSVPhaseFailed Expect(cfg.Client.Update(context.TODO(), csv)).To(Succeed()) - lister := action.NewOperatorListOperands(&cfg) + lister := v0.NewOperatorListOperands(&cfg) _, err := lister.Run(context.TODO(), "etcd") Expect(err.Error()).To(ContainSubstring("CSV underlying operator is not in a succeeded state")) }) @@ -183,7 +184,7 @@ var _ = Describe("OperatorListOperands", func() { It("should fail if there is not exactly 1 operator group", func() { Expect(cfg.Client.Delete(context.TODO(), og)).To(Succeed()) - lister := action.NewOperatorListOperands(&cfg) + lister := v0.NewOperatorListOperands(&cfg) _, err := lister.Run(context.TODO(), "etcd") Expect(err.Error()).To(ContainSubstring("unexpected number (0) of operator groups found in namespace etcd")) }) @@ -191,7 +192,7 @@ var _ = Describe("OperatorListOperands", func() { It("should fail if an owned CRD does not exist", func() { Expect(cfg.Client.Delete(context.TODO(), crd)).To(Succeed()) - lister := action.NewOperatorListOperands(&cfg) + lister := v0.NewOperatorListOperands(&cfg) _, err := lister.Run(context.TODO(), "etcd") Expect(err.Error()).To(ContainSubstring("customresourcedefinitions.apiextensions.k8s.io \"etcdclusters.etcd.database.coreos.com\" not found")) }) @@ -201,14 +202,14 @@ var _ = Describe("OperatorListOperands", func() { Expect(cfg.Client.Delete(context.TODO(), etcdcluster2)).To(Succeed()) Expect(cfg.Client.Delete(context.TODO(), etcdcluster3)).To(Succeed()) - lister := action.NewOperatorListOperands(&cfg) + lister := v0.NewOperatorListOperands(&cfg) operands, err := lister.Run(context.TODO(), "etcd") Expect(err).To(BeNil()) Expect(operands.Items).To(HaveLen(0)) }) It("should return operands from all namespaces", func() { - lister := action.NewOperatorListOperands(&cfg) + lister := v0.NewOperatorListOperands(&cfg) operands, err := lister.Run(context.TODO(), "etcd") Expect(err).To(BeNil()) Expect(getObjectNames(*operands)).To(ConsistOf( @@ -222,7 +223,7 @@ var _ = Describe("OperatorListOperands", func() { og.Status.Namespaces = []string{"ns1", "ns2"} Expect(cfg.Client.Update(context.TODO(), og)).To(Succeed()) - lister := action.NewOperatorListOperands(&cfg) + lister := v0.NewOperatorListOperands(&cfg) operands, err := lister.Run(context.TODO(), "etcd") Expect(err).To(BeNil()) Expect(getObjectNames(*operands)).To(ConsistOf( @@ -236,7 +237,7 @@ var _ = Describe("OperatorListOperands", func() { og.Status.Namespaces = []string{"ns1"} Expect(cfg.Client.Update(context.TODO(), og)).To(Succeed()) - lister := action.NewOperatorListOperands(&cfg) + lister := v0.NewOperatorListOperands(&cfg) operands, err := lister.Run(context.TODO(), "etcd") Expect(err).To(BeNil()) Expect(getObjectNames(*operands)).To(ConsistOf( @@ -249,7 +250,7 @@ var _ = Describe("OperatorListOperands", func() { og.Status.Namespaces = []string{"ns2"} Expect(cfg.Client.Update(context.TODO(), og)).To(Succeed()) - lister := action.NewOperatorListOperands(&cfg) + lister := v0.NewOperatorListOperands(&cfg) operands, err := lister.Run(context.TODO(), "etcd") Expect(err).To(BeNil()) Expect(getObjectNames(*operands)).To(ConsistOf( @@ -262,7 +263,7 @@ var _ = Describe("OperatorListOperands", func() { og.Status.Namespaces = []string{"other"} Expect(cfg.Client.Update(context.TODO(), og)).To(Succeed()) - lister := action.NewOperatorListOperands(&cfg) + lister := v0.NewOperatorListOperands(&cfg) operands, err := lister.Run(context.TODO(), "etcd") Expect(err).To(BeNil()) Expect(getObjectNames(*operands)).To(ConsistOf( diff --git a/pkg/action/v1/operator_list_operands.go b/pkg/action/v1/operator_list_operands.go new file mode 100644 index 0000000..af0961a --- /dev/null +++ b/pkg/action/v1/operator_list_operands.go @@ -0,0 +1,116 @@ +package v0 + +import ( + "cmp" + "context" + "fmt" + "github.com/operator-framework/kubectl-operator/pkg/action" + ocv1 "github.com/operator-framework/operator-controller/api/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "slices" +) + +// OperatorListOperands knows how to find and listOperandsForCRD custom resources given a package name and namespace. +type OperatorListOperands struct { + config *action.Configuration +} + +func NewOperatorListOperands(cfg *action.Configuration) *OperatorListOperands { + return &OperatorListOperands{ + config: cfg, + } +} + +func (o *OperatorListOperands) Run(ctx context.Context, clusterExtensionName string) (*unstructured.UnstructuredList, error) { + crds, err := o.getCRDs(ctx, clusterExtensionName) + if err != nil { + return nil, err + } + + var result unstructured.UnstructuredList + result.SetGroupVersionKind(schema.GroupVersionKind{ + Version: "v1", + Kind: "List", + }) + for _, crd := range crds { + operands, err := o.listOperandsForCRD(ctx, crd) + if err != nil { + return nil, err + } + result.Items = append(result.Items, operands...) + } + + // sort results + slices.SortFunc(result.Items, func(a, b unstructured.Unstructured) int { + if x := cmp.Compare( + a.GroupVersionKind().GroupKind().String(), + b.GroupVersionKind().GroupKind().String(), + ); x != 0 { + return x + } + if x := cmp.Compare(a.GetNamespace(), b.GetNamespace()); x != 0 { + return x + } + return cmp.Compare(a.GetName(), b.GetName()) + }) + + return &result, nil +} + +func (o *OperatorListOperands) getCRDs(ctx context.Context, clusterExtensionName string) ([]apiextensionsv1.CustomResourceDefinition, error) { + ce := ocv1.ClusterExtension{} + ceKey := types.NamespacedName{ + Name: clusterExtensionName, + } + if err := o.config.Client.Get(ctx, ceKey, &ce); err != nil { + return nil, fmt.Errorf("get cluster extension %q: %v", clusterExtensionName, err) + } + + progCond := meta.FindStatusCondition(ce.Status.Conditions, ocv1.TypeProgressing) + ready := meta.IsStatusConditionTrue(ce.Status.Conditions, ocv1.TypeInstalled) && + progCond != nil && progCond.Status == metav1.ConditionFalse && + progCond.Reason == ocv1.ReasonSucceeded + + if !ready { + return nil, fmt.Errorf("cluster extension %q is not at steady state: operand listOperandsForCRD may be inaccurate", clusterExtensionName) + } + + crds := apiextensionsv1.CustomResourceDefinitionList{} + labelSelector := client.MatchingLabels{ + "olm.operatorframework.io/owner-kind": "ClusterExtension", + "olm.operatorframework.io/owner-name": clusterExtensionName, + } + if err := o.config.Client.List(ctx, &crds, labelSelector); err != nil { + return nil, fmt.Errorf("list crds: %v", err) + } + return crds.Items, nil +} + +func (o *OperatorListOperands) listOperandsForCRD(ctx context.Context, crd apiextensionsv1.CustomResourceDefinition) ([]unstructured.Unstructured, error) { + servedVersion := "" + for _, v := range crd.Spec.Versions { + if v.Served { + servedVersion = v.Name + break + } + } + + list := unstructured.UnstructuredList{} + gvk := schema.GroupVersionKind{ + Group: crd.Spec.Group, + Version: servedVersion, + Kind: crd.Spec.Names.ListKind, + } + list.SetGroupVersionKind(gvk) + if err := o.config.Client.List(ctx, &list); err != nil { + return nil, err + } + + return list.Items, nil +}