diff --git a/Makefile-test.mk b/Makefile-test.mk index 99dff5e039..5df1278c41 100644 --- a/Makefile-test.mk +++ b/Makefile-test.mk @@ -62,6 +62,7 @@ JOBSET_VERSION = $(shell $(GO_CMD) list -m -f "{{.Version}}" sigs.k8s.io/jobset) .PHONY: test test: gotestsum ## Run tests. $(GOTESTSUM) --junitfile $(ARTIFACTS)/junit.xml -- $(GO_TEST_FLAGS) $(shell $(GO_CMD) list ./... | grep -v '/test/') -coverpkg=./... -coverprofile $(ARTIFACTS)/cover.out + cd cmd/experimental/kjobctl && make test .PHONY: test-integration test-integration: gomod-download envtest ginkgo mpi-operator-crd ray-operator-crd jobset-operator-crd kf-training-operator-crd cluster-autoscaler-crd kueuectl ## Run tests. @@ -69,6 +70,7 @@ test-integration: gomod-download envtest ginkgo mpi-operator-crd ray-operator-cr KUEUE_BIN=$(PROJECT_DIR)/bin \ ENVTEST_K8S_VERSION=$(ENVTEST_K8S_VERSION) \ $(GINKGO) $(GINKGO_ARGS) -procs=$(INTEGRATION_NPROCS) --junit-report=junit.xml --output-dir=$(ARTIFACTS) -v $(INTEGRATION_TARGET) + cd cmd/experimental/kjobctl && make test-integration CREATE_KIND_CLUSTER ?= true .PHONY: test-e2e diff --git a/cmd/experimental/kjobctl/Makefile b/cmd/experimental/kjobctl/Makefile index 693210c19d..d26d05e9f4 100644 --- a/cmd/experimental/kjobctl/Makefile +++ b/cmd/experimental/kjobctl/Makefile @@ -11,6 +11,17 @@ endif GO_CMD ?= go GO_FMT ?= gofmt +PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) +ARTIFACTS ?= $(PROJECT_DIR)/bin +TOOLS_DIR := $(PROJECT_DIR)/hack/tools + +# Number of processes to use during integration tests to run specs within a +# suite in parallel. Suites still run sequentially. User may set this value to 1 +# to run without parallelism. +INTEGRATION_NPROCS ?= 4 +# Folder where the integration tests are located. +INTEGRATION_TARGET ?= $(PROJECT_DIR)/test/integration/... + # Setting SHELL to bash allows bash commands to be executed by recipes. # Options are set to exit when a recipe line exits non-zero or a piped command fails. SHELL = /usr/bin/env bash -o pipefail @@ -65,7 +76,6 @@ fmt-verify: exit 1; \ fi - .PHONY: fmt fmt: ## Run go fmt against code. go fmt ./... @@ -79,8 +89,15 @@ vet: ## Run go vet against code. go vet ./... .PHONY: test -test: manifests generate fmt vet envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out +test: gotestsum ## Run tests. + $(GOTESTSUM) --junitfile $(ARTIFACTS)/junit.xml -- $(GO_TEST_FLAGS) $(shell $(GO_CMD) list ./... | grep -v '/test/') -coverpkg=./... -coverprofile $(ARTIFACTS)/cover.out + +.PHONY: test-integration +test-integration: gomod-download envtest ginkgo kubectl-kjob ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" \ + KUEUE_BIN=$(PROJECT_DIR)/bin \ + ENVTEST_K8S_VERSION=$(ENVTEST_K8S_VERSION) \ + $(GINKGO) $(GINKGO_ARGS) -procs=$(INTEGRATION_NPROCS) --junit-report=junit.xml --output-dir=$(ARTIFACTS) -v $(INTEGRATION_TARGET) # Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors. .PHONY: test-e2e # Run the e2e tests against a Kind k8s instance that is spun up. @@ -132,6 +149,8 @@ KUSTOMIZE_VERSION ?= v5.4.1 CONTROLLER_TOOLS_VERSION ?= v0.15.0 ENVTEST_VERSION ?= release-0.18 GOLANGCI_LINT_VERSION ?= v1.57.2 +GOTESTSUM_VERSION ?= v1.12.0 +GINKGO_VERSION ?= v2.19.0 .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. @@ -153,6 +172,16 @@ golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. $(GOLANGCI_LINT): $(LOCALBIN) $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,${GOLANGCI_LINT_VERSION}) +GOTESTSUM = $(PROJECT_DIR)/bin/gotestsum +.PHONY: gotestsum +gotestsum: ## Download gotestsum locally if necessary. + @GOBIN=$(PROJECT_DIR)/bin GO111MODULE=on $(GO_CMD) install gotest.tools/gotestsum@$(GOTESTSUM_VERSION) + +GINKGO = $(PROJECT_DIR)/bin/ginkgo +.PHONY: ginkgo +ginkgo: ## Download ginkgo locally if necessary. + @GOBIN=$(PROJECT_DIR)/bin GO111MODULE=on $(GO_CMD) install github.com/onsi/ginkgo/v2/ginkgo@$(GINKGO_VERSION) + # go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist # $1 - target path with name of binary (ideally with version) # $2 - package url which can be installed diff --git a/cmd/experimental/kjobctl/go.mod b/cmd/experimental/kjobctl/go.mod index d3c7336b35..8071520f6f 100644 --- a/cmd/experimental/kjobctl/go.mod +++ b/cmd/experimental/kjobctl/go.mod @@ -3,33 +3,45 @@ module sigs.k8s.io/kueue/cmd/experimental/kjobctl go 1.22.3 require ( + github.com/google/go-cmp v0.6.0 + github.com/onsi/ginkgo/v2 v2.19.0 + github.com/onsi/gomega v1.33.1 github.com/spf13/cobra v1.8.0 - k8s.io/apimachinery v0.30.1 + gotest.tools/gotestsum v1.12.0 + k8s.io/api v0.30.1 + k8s.io/apimachinery v0.30.2 k8s.io/cli-runtime v0.30.1 k8s.io/client-go v0.30.1 - k8s.io/code-generator v0.30.0 + k8s.io/code-generator v0.30.2 k8s.io/klog/v2 v2.120.1 + k8s.io/utils v0.0.0-20240102154912-e7106e64919e sigs.k8s.io/controller-runtime v0.18.2 sigs.k8s.io/controller-tools v0.15.0 ) require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/bitfield/gotestdox v0.2.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dnephin/pflag v1.0.7 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fatih/color v1.16.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobuffalo/flect v1.0.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect @@ -46,14 +58,16 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/ginkgo/v2 v2.19.0 // indirect - github.com/onsi/gomega v1.33.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.18.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/oauth2 v0.12.0 // indirect @@ -68,11 +82,9 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.30.1 // indirect k8s.io/apiextensions-apiserver v0.30.0 // indirect k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect diff --git a/cmd/experimental/kjobctl/go.sum b/cmd/experimental/kjobctl/go.sum index 0f2fde9229..7015a24712 100644 --- a/cmd/experimental/kjobctl/go.sum +++ b/cmd/experimental/kjobctl/go.sum @@ -2,7 +2,13 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bitfield/gotestdox v0.2.2 h1:x6RcPAbBbErKLnapz1QeAlf3ospg8efBsedU93CDsnE= +github.com/bitfield/gotestdox v0.2.2/go.mod h1:D+gwtS0urjBrzguAkTM2wodsTQYFHdpx8eqRJ3N+9pY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -14,12 +20,17 @@ github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk= +github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -28,6 +39,8 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= @@ -36,7 +49,6 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= @@ -44,6 +56,8 @@ github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnD github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -106,8 +120,13 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -133,9 +152,17 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +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= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -153,8 +180,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -162,17 +189,34 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= +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= 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -185,6 +229,13 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -196,6 +247,9 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -209,18 +263,41 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= @@ -234,12 +311,19 @@ 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-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -275,20 +359,24 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/gotestsum v1.12.0 h1:CmwtaGDkHxrZm4Ib0Vob89MTfpc3GrEFMJKovliPwGk= +gotest.tools/gotestsum v1.12.0/go.mod h1:fAvqkSptospfSbQw26CTYzNwnsE/ztqLeyhP0h67ARY= +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.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= -k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= -k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= +k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/cli-runtime v0.30.1 h1:kSBBpfrJGS6lllc24KeniI9JN7ckOOJKnmFYH1RpTOw= k8s.io/cli-runtime v0.30.1/go.mod h1:zhHgbqI4J00pxb6gM3gJPVf2ysDjhQmQtnTxnMScab8= k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= -k8s.io/code-generator v0.30.0 h1:3VUVqHvWFSVSm9kqL/G6kD4ZwNdHF6J/jPyo3Jgjy3k= -k8s.io/code-generator v0.30.0/go.mod h1:mBMZhfRR4IunJUh2+7LVmdcWwpouCH5+LNPkZ3t/v7Q= +k8s.io/code-generator v0.30.2 h1:ZY1+aGkqZVwKIyGsOzquaeZ5rSfE6wZHur8z3jQAaiw= +k8s.io/code-generator v0.30.2/go.mod h1:RQP5L67QxqgkVquk704CyvWFIq0e6RCMmLTXxjE8dVA= k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo= k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= diff --git a/cmd/experimental/kjobctl/hack/tools/pinversion.go b/cmd/experimental/kjobctl/hack/tools/pinversion.go index cdbc10a7c5..1eb469052a 100644 --- a/cmd/experimental/kjobctl/hack/tools/pinversion.go +++ b/cmd/experimental/kjobctl/hack/tools/pinversion.go @@ -16,9 +16,13 @@ limitations under the License. package tools -// Keep a reference to the code generators so they are not removed by go mod tidy +// Keep a reference to the code generators, so they are not removed by go mod tidy import ( + _ "github.com/onsi/ginkgo/v2/ginkgo/command" + _ "github.com/onsi/ginkgo/v2/ginkgo/run" + _ "gotest.tools/gotestsum/cmd" _ "k8s.io/code-generator" + // since verify will error when referencing a cmd package // we need to reference individual dependencies used by it _ "sigs.k8s.io/controller-tools/pkg/crd" diff --git a/cmd/experimental/kjobctl/pkg/cmd/cmd.go b/cmd/experimental/kjobctl/pkg/cmd/cmd.go index 9f892b0195..5e38f9eab4 100644 --- a/cmd/experimental/kjobctl/pkg/cmd/cmd.go +++ b/cmd/experimental/kjobctl/pkg/cmd/cmd.go @@ -22,12 +22,15 @@ import ( "github.com/spf13/cobra" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/utils/clock" "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/cmd/completion" + "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/cmd/list" "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/cmd/util" ) type KjobctlOptions struct { + Clock clock.Clock ConfigFlags *genericclioptions.ConfigFlags genericiooptions.IOStreams @@ -51,6 +54,10 @@ func NewKjobctlCmd(o KjobctlOptions) *cobra.Command { Short: "ML/AI/Batch Jobs Made Easy", } + if o.Clock == nil { + o.Clock = clock.RealClock{} + } + flags := cmd.PersistentFlags() configFlags := o.ConfigFlags @@ -66,5 +73,7 @@ func NewKjobctlCmd(o KjobctlOptions) *cobra.Command { cobra.CheckErr(cmd.RegisterFlagCompletionFunc("cluster", completion.ClustersFunc(clientGetter))) cobra.CheckErr(cmd.RegisterFlagCompletionFunc("user", completion.UsersFunc(clientGetter))) + cmd.AddCommand(list.NewListCmd(clientGetter, o.IOStreams, o.Clock)) + return cmd } diff --git a/cmd/experimental/kjobctl/pkg/cmd/list/helpers.go b/cmd/experimental/kjobctl/pkg/cmd/list/helpers.go new file mode 100644 index 0000000000..2c3ea6ad0f --- /dev/null +++ b/cmd/experimental/kjobctl/pkg/cmd/list/helpers.go @@ -0,0 +1,64 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package list + +import ( + "errors" + "os" + "strconv" + + "github.com/spf13/cobra" +) + +const ( + defaultListRequestLimit = 100 + KjobctlListRequestLimitEnvName = "KJOBCTL_LIST_REQUEST_LIMIT" +) + +var ( + invalidListRequestLimitError = errors.New("invalid list request limit") +) + +func listRequestLimit() (int64, error) { + listRequestLimitEnv := os.Getenv(KjobctlListRequestLimitEnvName) + + if len(listRequestLimitEnv) == 0 { + return defaultListRequestLimit, nil + } + + limit, err := strconv.ParseInt(listRequestLimitEnv, 10, 64) + if err != nil { + return 0, invalidListRequestLimitError + } + + return limit, nil +} + +func addFieldSelectorFlagVar(cmd *cobra.Command, p *string) { + cmd.Flags().StringVar(p, "field-selector", "", + "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.") +} + +func addLabelSelectorFlagVar(cmd *cobra.Command, p *string) { + cmd.Flags().StringVarP(p, "selector", "l", "", + "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints.") +} + +func addAllNamespacesFlagVar(cmd *cobra.Command, p *bool) { + cmd.Flags().BoolVarP(p, "all-namespaces", "A", false, + "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") +} diff --git a/cmd/experimental/kjobctl/pkg/cmd/list/list.go b/cmd/experimental/kjobctl/pkg/cmd/list/list.go new file mode 100644 index 0000000000..8d7d10cdac --- /dev/null +++ b/cmd/experimental/kjobctl/pkg/cmd/list/list.go @@ -0,0 +1,43 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package list + +import ( + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/utils/clock" + + "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/cmd/util" +) + +const ( + listExample = ` # List Job + kjobctl list job` +) + +func NewListCmd(clientGetter util.ClientGetter, streams genericiooptions.IOStreams, clock clock.Clock) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Display resources", + Example: listExample, + SuggestFor: []string{"ps"}, + } + + cmd.AddCommand(NewJobCmd(clientGetter, streams, clock)) + + return cmd +} diff --git a/cmd/experimental/kjobctl/pkg/cmd/list/list_job.go b/cmd/experimental/kjobctl/pkg/cmd/list/list_job.go new file mode 100644 index 0000000000..1389df9328 --- /dev/null +++ b/cmd/experimental/kjobctl/pkg/cmd/list/list_job.go @@ -0,0 +1,196 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package list + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/client-go/kubernetes/scheme" + batchv1 "k8s.io/client-go/kubernetes/typed/batch/v1" + "k8s.io/utils/clock" + + "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/cmd/util" + "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/constants" +) + +const ( + jobExample = ` # List Job + kjobctl list job` +) + +type JobOptions struct { + Clock clock.Clock + PrintFlags *genericclioptions.PrintFlags + + Limit int64 + AllNamespaces bool + Namespace string + ProfileFilter string + FieldSelector string + LabelSelector string + ClusterQueueFilter string + + Client batchv1.BatchV1Interface + + genericiooptions.IOStreams +} + +func NewJobOptions(streams genericiooptions.IOStreams, clock clock.Clock) *JobOptions { + return &JobOptions{ + PrintFlags: genericclioptions.NewPrintFlags("").WithTypeSetter(scheme.Scheme), + IOStreams: streams, + Clock: clock, + } +} + +func NewJobCmd(clientGetter util.ClientGetter, streams genericiooptions.IOStreams, clock clock.Clock) *cobra.Command { + o := NewJobOptions(streams, clock) + + cmd := &cobra.Command{ + Use: "job [--selector key1=value1] [--field-selector key1=value1] [--all-namespaces]", + DisableFlagsInUseLine: true, + Short: "List Job", + Example: jobExample, + Run: func(cmd *cobra.Command, args []string) { + cobra.CheckErr(o.Complete(clientGetter)) + cobra.CheckErr(o.Run(cmd.Context())) + }, + } + + o.PrintFlags.AddFlags(cmd) + + addAllNamespacesFlagVar(cmd, &o.AllNamespaces) + addFieldSelectorFlagVar(cmd, &o.FieldSelector) + addLabelSelectorFlagVar(cmd, &o.LabelSelector) + + cmd.Flags().StringVarP(&o.ProfileFilter, "profile", "p", "", + "Filter by profile name which is associated with the resource.") + + return cmd +} + +// Complete completes all the required options +func (o *JobOptions) Complete(clientGetter util.ClientGetter) error { + var err error + + o.Limit, err = listRequestLimit() + if err != nil { + return err + } + + o.Namespace, _, err = clientGetter.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + clientset, err := clientGetter.K8sClientset() + if err != nil { + return err + } + + o.Client = clientset.BatchV1() + + return nil +} + +func (o *JobOptions) ToPrinter(headers bool) (printers.ResourcePrinterFunc, error) { + if !o.PrintFlags.OutputFlagSpecified() { + printer := newJobTablePrinter(). + WithNamespace(o.AllNamespaces). + WithHeaders(headers). + WithClock(o.Clock) + return printer.PrintObj, nil + } + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return nil, err + } + + return printer.PrintObj, nil +} + +// Run performs the list operation. +func (o *JobOptions) Run(ctx context.Context) error { + var totalCount int + + namespace := o.Namespace + if o.AllNamespaces { + namespace = "" + } + + opts := metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=true", constants.ManagedLabel), + FieldSelector: o.FieldSelector, + Limit: o.Limit, + } + + if len(o.ProfileFilter) > 0 { + opts.LabelSelector = fmt.Sprintf("%s,%s=%s", opts.LabelSelector, constants.ProfileLabel, o.ProfileFilter) + } + if len(o.LabelSelector) > 0 { + opts.LabelSelector = fmt.Sprintf("%s,%s", opts.LabelSelector, o.LabelSelector) + } + + tabWriter := printers.GetNewTabWriter(o.Out) + + for { + headers := totalCount == 0 + + list, err := o.Client.Jobs(namespace).List(ctx, opts) + if err != nil { + return err + } + + totalCount += len(list.Items) + + printer, err := o.ToPrinter(headers) + if err != nil { + return err + } + + if err := printer.PrintObj(list, tabWriter); err != nil { + return err + } + + if list.Continue != "" { + opts.Continue = list.Continue + continue + } + + if totalCount == 0 { + if !o.AllNamespaces { + fmt.Fprintf(o.ErrOut, "No resources found in %s namespace.\n", o.Namespace) + } else { + fmt.Fprintln(o.ErrOut, "No resources found") + } + return nil + } + + if err := tabWriter.Flush(); err != nil { + return err + } + + return nil + } +} diff --git a/cmd/experimental/kjobctl/pkg/cmd/list/list_job_printer.go b/cmd/experimental/kjobctl/pkg/cmd/list/list_job_printer.go new file mode 100644 index 0000000000..67309068c0 --- /dev/null +++ b/cmd/experimental/kjobctl/pkg/cmd/list/list_job_printer.go @@ -0,0 +1,114 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package list + +import ( + "errors" + "fmt" + "io" + "time" + + batchv1 "k8s.io/api/batch/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/duration" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/utils/clock" + "k8s.io/utils/ptr" + + "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/constants" +) + +type listJobPrinter struct { + clock clock.Clock + printOptions printers.PrintOptions +} + +var _ printers.ResourcePrinter = (*listJobPrinter)(nil) + +func (p *listJobPrinter) PrintObj(obj runtime.Object, out io.Writer) error { + printer := printers.NewTablePrinter(p.printOptions) + + list, ok := obj.(*batchv1.JobList) + if !ok { + return errors.New("invalid object type") + } + + table := &metav1.Table{ + ColumnDefinitions: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Profile", Type: "string"}, + {Name: "Completions", Type: "string"}, + {Name: "Duration", Type: "string"}, + {Name: "Age", Type: "string"}, + }, + Rows: p.printJobList(list), + } + + return printer.PrintObj(table, out) +} + +func (p *listJobPrinter) WithNamespace(f bool) *listJobPrinter { + p.printOptions.WithNamespace = f + return p +} + +func (p *listJobPrinter) WithHeaders(f bool) *listJobPrinter { + p.printOptions.NoHeaders = !f + return p +} + +func (p *listJobPrinter) WithClock(c clock.Clock) *listJobPrinter { + p.clock = c + return p +} + +func newJobTablePrinter() *listJobPrinter { + return &listJobPrinter{ + clock: clock.RealClock{}, + } +} + +func (p *listJobPrinter) printJobList(list *batchv1.JobList) []metav1.TableRow { + rows := make([]metav1.TableRow, len(list.Items)) + for index := range list.Items { + rows[index] = p.printJob(&list.Items[index]) + } + return rows +} + +func (p *listJobPrinter) printJob(job *batchv1.Job) metav1.TableRow { + row := metav1.TableRow{ + Object: runtime.RawExtension{Object: job}, + } + var durationStr string + if job.Status.StartTime != nil { + completionTime := time.Now() + if job.Status.CompletionTime != nil { + completionTime = job.Status.CompletionTime.Time + } + durationStr = duration.HumanDuration(completionTime.Sub(job.Status.StartTime.Time)) + } + row.Cells = []any{ + job.Name, + job.ObjectMeta.Labels[constants.ProfileLabel], + fmt.Sprintf("%d/%d", job.Status.Succeeded, ptr.Deref(job.Spec.Completions, 1)), + durationStr, + duration.HumanDuration(p.clock.Since(job.CreationTimestamp.Time)), + } + return row +} diff --git a/cmd/experimental/kjobctl/pkg/cmd/list/list_job_test.go b/cmd/experimental/kjobctl/pkg/cmd/list/list_job_test.go new file mode 100644 index 0000000000..b711303e46 --- /dev/null +++ b/cmd/experimental/kjobctl/pkg/cmd/list/list_job_test.go @@ -0,0 +1,253 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package list + +import ( + "fmt" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + batchv1 "k8s.io/api/batch/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/client-go/kubernetes/fake" + kubetesting "k8s.io/client-go/testing" + testingclock "k8s.io/utils/clock/testing" + + cmdtesting "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/cmd/testing" + "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/testing/wrappers" +) + +func TestJobCmd(t *testing.T) { + testStartTime := time.Now() + + testCases := map[string]struct { + ns string + objs []runtime.Object + args []string + wantOut string + wantOutErr string + wantErr error + }{ + "should print job list with namespace filter": { + ns: "ns1", + objs: []runtime.Object{ + wrappers.MakeJob("j1", "ns1"). + Profile("profile1"). + Completions(3). + CreationTimestamp(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + StartTime(testStartTime.Add(-2 * time.Hour).Truncate(time.Second)). + CompletionTime(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + Succeeded(3). + Obj(), + wrappers.MakeJob("j2", "ns2"). + Profile("profile2"). + Completions(3). + CreationTimestamp(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + StartTime(testStartTime.Add(-2 * time.Hour).Truncate(time.Second)). + CompletionTime(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + Succeeded(3). + Obj(), + }, + wantOut: `NAME PROFILE COMPLETIONS DURATION AGE +j1 profile1 3/3 60m 60m +`, + }, + "should print job list with profile filter": { + args: []string{"--profile", "profile1"}, + objs: []runtime.Object{ + wrappers.MakeJob("j1", metav1.NamespaceDefault). + Profile("profile1"). + Completions(3). + CreationTimestamp(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + StartTime(testStartTime.Add(-2 * time.Hour).Truncate(time.Second)). + CompletionTime(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + Succeeded(3). + Obj(), + wrappers.MakeJob("j2", metav1.NamespaceDefault). + Profile("profile2"). + Completions(3). + CreationTimestamp(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + StartTime(testStartTime.Add(-2 * time.Hour).Truncate(time.Second)). + CompletionTime(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + Succeeded(3). + Obj(), + }, + wantOut: `NAME PROFILE COMPLETIONS DURATION AGE +j1 profile1 3/3 60m 60m +`, + }, + "should print job list with profile filter (short flag)": { + args: []string{"-p", "profile1"}, + objs: []runtime.Object{ + wrappers.MakeJob("j1", metav1.NamespaceDefault). + Profile("profile1"). + Completions(3). + CreationTimestamp(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + StartTime(testStartTime.Add(-2 * time.Hour).Truncate(time.Second)). + CompletionTime(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + Succeeded(3). + Obj(), + wrappers.MakeJob("j2", metav1.NamespaceDefault). + Profile("profile2"). + Completions(3). + CreationTimestamp(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + StartTime(testStartTime.Add(-2 * time.Hour).Truncate(time.Second)). + CompletionTime(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + Succeeded(3). + Obj(), + }, + wantOut: `NAME PROFILE COMPLETIONS DURATION AGE +j1 profile1 3/3 60m 60m +`, + }, + "should print job list with label selector filter": { + args: []string{"--selector", "foo=bar"}, + objs: []runtime.Object{ + wrappers.MakeJob("j1", metav1.NamespaceDefault). + Profile("profile1"). + Label("foo", "bar"). + Completions(3). + CreationTimestamp(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + StartTime(testStartTime.Add(-2 * time.Hour).Truncate(time.Second)). + CompletionTime(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + Succeeded(3). + Obj(), + wrappers.MakeJob("j2", metav1.NamespaceDefault). + Profile("profile2"). + Completions(3). + CreationTimestamp(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + StartTime(testStartTime.Add(-2 * time.Hour).Truncate(time.Second)). + CompletionTime(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + Succeeded(3). + Obj(), + }, + wantOut: `NAME PROFILE COMPLETIONS DURATION AGE +j1 profile1 3/3 60m 60m +`, + }, + "should print job list with label selector filter (short flag)": { + args: []string{"-l", "foo=bar"}, + objs: []runtime.Object{ + wrappers.MakeJob("j1", metav1.NamespaceDefault). + Profile("profile1"). + Label("foo", "bar"). + Completions(3). + CreationTimestamp(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + StartTime(testStartTime.Add(-2 * time.Hour).Truncate(time.Second)). + CompletionTime(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + Succeeded(3). + Obj(), + wrappers.MakeJob("j2", metav1.NamespaceDefault). + Profile("profile2"). + Completions(3). + CreationTimestamp(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + StartTime(testStartTime.Add(-2 * time.Hour).Truncate(time.Second)). + CompletionTime(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + Succeeded(3). + Obj(), + }, + wantOut: `NAME PROFILE COMPLETIONS DURATION AGE +j1 profile1 3/3 60m 60m +`, + }, + "should print job list with field selector filter": { + args: []string{"--field-selector", "metadata.name=j1"}, + objs: []runtime.Object{ + wrappers.MakeJob("j1", metav1.NamespaceDefault). + Profile("profile1"). + Completions(3). + CreationTimestamp(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + StartTime(testStartTime.Add(-2 * time.Hour).Truncate(time.Second)). + CompletionTime(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + Succeeded(3). + Obj(), + wrappers.MakeJob("j2", metav1.NamespaceDefault). + Profile("profile2"). + Completions(3). + CreationTimestamp(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + StartTime(testStartTime.Add(-2 * time.Hour).Truncate(time.Second)). + CompletionTime(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + Succeeded(3). + Obj(), + }, + wantOut: `NAME PROFILE COMPLETIONS DURATION AGE +j1 profile1 3/3 60m 60m +`, + }, + "should print not found error": { + wantOutErr: fmt.Sprintf("No resources found in %s namespace.\n", metav1.NamespaceDefault), + }, + "should print not found error with all-namespaces filter": { + args: []string{"-A"}, + wantOutErr: "No resources found\n", + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + streams, _, out, outErr := genericiooptions.NewTestIOStreams() + + clientset := fake.NewSimpleClientset(tc.objs...) + clientset.PrependReactor("list", "jobs", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) { + listAction := action.(kubetesting.ListActionImpl) + fieldsSelector := listAction.GetListRestrictions().Fields + + obj, err := clientset.Tracker().List(listAction.GetResource(), listAction.GetKind(), listAction.Namespace) + jobList := obj.(*batchv1.JobList) + + filtered := make([]batchv1.Job, 0, len(jobList.Items)) + for _, item := range jobList.Items { + fieldsSet := fields.Set{ + "metadata.name": item.Name, + } + if fieldsSelector.Matches(fieldsSet) { + filtered = append(filtered, item) + } + } + jobList.Items = filtered + return true, jobList, err + }) + + tcg := cmdtesting.NewTestClientGetter().WithK8sClientset(clientset) + if len(tc.ns) > 0 { + tcg.WithNamespace(tc.ns) + } + + cmd := NewJobCmd(tcg, streams, testingclock.NewFakeClock(testStartTime)) + cmd.SetArgs(tc.args) + + gotErr := cmd.Execute() + if diff := cmp.Diff(tc.wantErr, gotErr, cmpopts.EquateErrors()); diff != "" { + t.Errorf("Unexpected error (-want/+got)\n%s", diff) + } + + gotOut := out.String() + if diff := cmp.Diff(tc.wantOut, gotOut); diff != "" { + t.Errorf("Unexpected output (-want/+got)\n%s", diff) + } + + gotOutErr := outErr.String() + if diff := cmp.Diff(tc.wantOutErr, gotOutErr); diff != "" { + t.Errorf("Unexpected output (-want/+got)\n%s", diff) + } + }) + } +} diff --git a/cmd/experimental/kjobctl/pkg/cmd/list/list_test.go b/cmd/experimental/kjobctl/pkg/cmd/list/list_test.go new file mode 100644 index 0000000000..df755416a0 --- /dev/null +++ b/cmd/experimental/kjobctl/pkg/cmd/list/list_test.go @@ -0,0 +1,85 @@ +package list + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/client-go/kubernetes/fake" + testingclock "k8s.io/utils/clock/testing" + + cmdtesting "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/cmd/testing" + "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/testing/wrappers" +) + +func TestListCmd(t *testing.T) { + testStartTime := time.Now() + + testCases := map[string]struct { + ns string + objs []runtime.Object + args []string + wantOut string + wantOutErr string + wantErr error + }{ + "should print job list with all namespaces": { + args: []string{"job", "--all-namespaces"}, + objs: []runtime.Object{ + wrappers.MakeJob("j1", "ns1"). + Profile("profile1"). + Completions(3). + CreationTimestamp(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + StartTime(testStartTime.Add(-2 * time.Hour).Truncate(time.Second)). + CompletionTime(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + Succeeded(3). + Obj(), + wrappers.MakeJob("j2", "ns2"). + Profile("profile2"). + Completions(3). + CreationTimestamp(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + StartTime(testStartTime.Add(-2 * time.Hour).Truncate(time.Second)). + CompletionTime(testStartTime.Add(-1 * time.Hour).Truncate(time.Second)). + Succeeded(3). + Obj(), + }, + wantOut: `NAMESPACE NAME PROFILE COMPLETIONS DURATION AGE +ns1 j1 profile1 3/3 60m 60m +ns2 j2 profile2 3/3 60m 60m +`, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + streams, _, out, outErr := genericiooptions.NewTestIOStreams() + + tcg := cmdtesting.NewTestClientGetter() + if len(tc.ns) > 0 { + tcg.WithNamespace(tc.ns) + } + + tcg.WithK8sClientset(fake.NewSimpleClientset(tc.objs...)) + + cmd := NewListCmd(tcg, streams, testingclock.NewFakeClock(testStartTime)) + cmd.SetArgs(tc.args) + + gotErr := cmd.Execute() + if diff := cmp.Diff(tc.wantErr, gotErr, cmpopts.EquateErrors()); diff != "" { + t.Errorf("Unexpected error (-want/+got)\n%s", diff) + } + + gotOut := out.String() + if diff := cmp.Diff(tc.wantOut, gotOut); diff != "" { + t.Errorf("Unexpected output (-want/+got)\n%s", diff) + } + + gotOutErr := outErr.String() + if diff := cmp.Diff(tc.wantOutErr, gotOutErr); diff != "" { + t.Errorf("Unexpected output (-want/+got)\n%s", diff) + } + }) + } +} diff --git a/cmd/experimental/kjobctl/pkg/cmd/util/client_getter.go b/cmd/experimental/kjobctl/pkg/cmd/util/client_getter.go index e7e56e55ff..803cbc5be1 100644 --- a/cmd/experimental/kjobctl/pkg/cmd/util/client_getter.go +++ b/cmd/experimental/kjobctl/pkg/cmd/util/client_getter.go @@ -26,7 +26,7 @@ import ( type ClientGetter interface { genericclioptions.RESTClientGetter - K8sClientSet() (k8s.Interface, error) + K8sClientset() (k8s.Interface, error) KjobctlClientset() (versioned.Interface, error) } @@ -42,7 +42,7 @@ func NewClientGetter(clientGetter genericclioptions.RESTClientGetter) ClientGett } } -func (cg *clientGetterImpl) K8sClientSet() (k8s.Interface, error) { +func (cg *clientGetterImpl) K8sClientset() (k8s.Interface, error) { config, err := cg.ToRESTConfig() if err != nil { return nil, err diff --git a/cmd/experimental/kjobctl/pkg/constants/constants.go b/cmd/experimental/kjobctl/pkg/constants/constants.go new file mode 100644 index 0000000000..a7d6bf36cd --- /dev/null +++ b/cmd/experimental/kjobctl/pkg/constants/constants.go @@ -0,0 +1,7 @@ +package constants + +const ( + ManagedLabel = "kjobctl.x-k8s.io/managed" + QueueLabel = "kueue.x-k8s.io/queue-name" + ProfileLabel = "kjobctl.x-k8s.io/profile" +) diff --git a/cmd/experimental/kjobctl/pkg/testing/wrappers/job_wrappers.go b/cmd/experimental/kjobctl/pkg/testing/wrappers/job_wrappers.go new file mode 100644 index 0000000000..0e41c449d8 --- /dev/null +++ b/cmd/experimental/kjobctl/pkg/testing/wrappers/job_wrappers.go @@ -0,0 +1,126 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package wrappers + +import ( + "time" + + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/constants" +) + +// JobWrapper wraps a Job. +type JobWrapper struct{ batchv1.Job } + +// MakeJob creates a wrapper for a suspended job +func MakeJob(name, ns string) *JobWrapper { + return &JobWrapper{batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + Labels: map[string]string{ + constants.ManagedLabel: "true", + }, + }, + Spec: batchv1.JobSpec{ + Parallelism: ptr.To[int32](1), + Suspend: ptr.To(true), + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyNever, + Containers: []corev1.Container{ + { + Name: "c", + Image: "pause", + Resources: corev1.ResourceRequirements{Requests: corev1.ResourceList{}}, + }, + }, + NodeSelector: map[string]string{}, + }, + }, + }, + }} +} + +// Obj returns the inner Job. +func (j *JobWrapper) Obj() *batchv1.Job { + return &j.Job +} + +// Clone returns deep copy of the Job. +func (j *JobWrapper) Clone() *JobWrapper { + return &JobWrapper{Job: *j.DeepCopy()} +} + +// Parallelism updates job parallelism. +func (j *JobWrapper) Parallelism(p int32) *JobWrapper { + j.Spec.Parallelism = ptr.To(p) + return j +} + +// Completions updates job completions. +func (j *JobWrapper) Completions(p int32) *JobWrapper { + j.Spec.Completions = ptr.To(p) + return j +} + +// Queue sets the local queue name +func (j *JobWrapper) Queue(queue string) *JobWrapper { + return j.Label(constants.QueueLabel, queue) +} + +// Profile sets the profile name +func (j *JobWrapper) Profile(profile string) *JobWrapper { + return j.Label(constants.ProfileLabel, profile) +} + +// Label sets the .metadata.labels key and value +func (j *JobWrapper) Label(key, value string) *JobWrapper { + if j.Labels == nil { + j.Labels = make(map[string]string) + } + j.ObjectMeta.Labels[key] = value + return j +} + +// CreationTimestamp sets the .metadata.creationTimestamp +func (j *JobWrapper) CreationTimestamp(t time.Time) *JobWrapper { + j.ObjectMeta.CreationTimestamp = metav1.NewTime(t) + return j +} + +// StartTime sets the .status.startTime +func (j *JobWrapper) StartTime(t time.Time) *JobWrapper { + j.Status.StartTime = &metav1.Time{Time: t} + return j +} + +// CompletionTime sets the .status.completionTime +func (j *JobWrapper) CompletionTime(t time.Time) *JobWrapper { + j.Status.CompletionTime = &metav1.Time{Time: t} + return j +} + +// Succeeded sets the .status.succeeded +func (j *JobWrapper) Succeeded(value int32) *JobWrapper { + j.Status.Succeeded = value + return j +} diff --git a/cmd/experimental/kjobctl/test/framework/framework.go b/cmd/experimental/kjobctl/test/framework/framework.go new file mode 100644 index 0000000000..4d96ad1a20 --- /dev/null +++ b/cmd/experimental/kjobctl/test/framework/framework.go @@ -0,0 +1,65 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package framework + +import ( + "context" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" +) + +type Framework struct { + testEnv *envtest.Environment + cancel context.CancelFunc +} + +func (f *Framework) Init() *rest.Config { + ginkgo.By("bootstrapping test environment") + + f.testEnv = &envtest.Environment{} + cfg, err := f.testEnv.Start() + + gomega.ExpectWithOffset(1, err).NotTo(gomega.HaveOccurred()) + gomega.ExpectWithOffset(1, cfg).NotTo(gomega.BeNil()) + + return cfg +} + +func (f *Framework) SetupClient(cfg *rest.Config) (context.Context, client.Client) { + k8sClient, err := client.New(cfg, client.Options{Scheme: scheme.Scheme}) + gomega.ExpectWithOffset(1, err).NotTo(gomega.HaveOccurred()) + gomega.ExpectWithOffset(1, k8sClient).NotTo(gomega.BeNil()) + + ctx, cancel := context.WithCancel(context.Background()) + f.cancel = cancel + + return ctx, k8sClient +} + +func (f *Framework) Teardown() { + ginkgo.By("tearing down the test environment") + if f.cancel != nil { + f.cancel() + } + err := f.testEnv.Stop() + gomega.ExpectWithOffset(1, err).NotTo(gomega.HaveOccurred()) +} diff --git a/cmd/experimental/kjobctl/test/integration/kjobctl/list_test.go b/cmd/experimental/kjobctl/test/integration/kjobctl/list_test.go new file mode 100644 index 0000000000..b76a6e5dd2 --- /dev/null +++ b/cmd/experimental/kjobctl/test/integration/kjobctl/list_test.go @@ -0,0 +1,95 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kjobctl + +import ( + "fmt" + "os" + "time" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/duration" + "k8s.io/cli-runtime/pkg/genericiooptions" + testingclock "k8s.io/utils/clock/testing" + + "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/cmd" + "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/cmd/list" + "sigs.k8s.io/kueue/cmd/experimental/kjobctl/pkg/testing/wrappers" + "sigs.k8s.io/kueue/cmd/experimental/kjobctl/test/util" +) + +var _ = ginkgo.Describe("Kjobctl List", ginkgo.Ordered, ginkgo.ContinueOnFailure, func() { + var ns *corev1.Namespace + + ginkgo.BeforeEach(func() { + ns = &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{GenerateName: "ns-"}} + gomega.Expect(k8sClient.Create(ctx, ns)).To(gomega.Succeed()) + }) + + ginkgo.AfterEach(func() { + gomega.Expect(util.DeleteNamespace(ctx, k8sClient, ns)).To(gomega.Succeed()) + os.Unsetenv(list.KjobctlListRequestLimitEnvName) + }) + + ginkgo.When("List Jobs", func() { + var ( + j1 *batchv1.Job + j2 *batchv1.Job + j3 *batchv1.Job + ) + + ginkgo.JustBeforeEach(func() { + j1 = wrappers.MakeJob("j1", ns.Name).Completions(3).Profile("profile1").Obj() + gomega.Expect(k8sClient.Create(ctx, j1)).To(gomega.Succeed()) + + j2 = wrappers.MakeJob("j2", ns.Name).Profile("profile1").Completions(3).Obj() + gomega.Expect(k8sClient.Create(ctx, j2)).To(gomega.Succeed()) + + j3 = wrappers.MakeJob("very-long-job-name", ns.Name).Profile("profile1").Completions(3).Obj() + gomega.Expect(k8sClient.Create(ctx, j3)).To(gomega.Succeed()) + }) + + // Simple client set that are using on unit tests not allow paging. + ginkgo.It("Should print jobs list with paging", func() { + streams, _, output, errOutput := genericiooptions.NewTestIOStreams() + configFlags := CreateConfigFlagsWithRestConfig(cfg, streams) + executeTime := time.Now() + kjobctl := cmd.NewKjobctlCmd(cmd.KjobctlOptions{ConfigFlags: configFlags, IOStreams: streams, + Clock: testingclock.NewFakeClock(executeTime)}) + + os.Setenv(list.KjobctlListRequestLimitEnvName, "1") + kjobctl.SetArgs([]string{"list", "job", "--namespace", ns.Name}) + err := kjobctl.Execute() + + gomega.Expect(err).NotTo(gomega.HaveOccurred(), "%s: %s", err, output) + gomega.Expect(errOutput.String()).Should(gomega.BeEmpty()) + gomega.Expect(output.String()).Should(gomega.Equal(fmt.Sprintf(`NAME PROFILE COMPLETIONS DURATION AGE +j1 profile1 0/3 %s +j2 profile1 0/3 %s +very-long-job-name profile1 0/3 %s +`, + duration.HumanDuration(executeTime.Sub(j1.CreationTimestamp.Time)), + duration.HumanDuration(executeTime.Sub(j2.CreationTimestamp.Time)), + duration.HumanDuration(executeTime.Sub(j3.CreationTimestamp.Time)), + ))) + }) + }) +}) diff --git a/cmd/experimental/kjobctl/test/integration/kjobctl/suite_test.go b/cmd/experimental/kjobctl/test/integration/kjobctl/suite_test.go new file mode 100644 index 0000000000..3aafe854a6 --- /dev/null +++ b/cmd/experimental/kjobctl/test/integration/kjobctl/suite_test.go @@ -0,0 +1,51 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kjobctl + +import ( + "context" + "testing" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + + "sigs.k8s.io/kueue/cmd/experimental/kjobctl/test/framework" +) + +var ( + cfg *rest.Config + k8sClient client.Client + ctx context.Context + fwk *framework.Framework +) + +func TestKjobctl(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "Kjobctl Suite") +} + +var _ = ginkgo.BeforeSuite(func() { + fwk = &framework.Framework{} + cfg = fwk.Init() + ctx, k8sClient = fwk.SetupClient(cfg) +}) + +var _ = ginkgo.AfterSuite(func() { + fwk.Teardown() +}) diff --git a/cmd/experimental/kjobctl/test/integration/kjobctl/util.go b/cmd/experimental/kjobctl/test/integration/kjobctl/util.go new file mode 100644 index 0000000000..a78ec4e9be --- /dev/null +++ b/cmd/experimental/kjobctl/test/integration/kjobctl/util.go @@ -0,0 +1,17 @@ +package kjobctl + +import ( + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/client-go/rest" +) + +func CreateConfigFlagsWithRestConfig(cfg *rest.Config, streams genericiooptions.IOStreams) *genericclioptions.ConfigFlags { + return genericclioptions. + NewConfigFlags(true). + WithDiscoveryQPS(50.0). + WithWarningPrinter(streams). + WithWrapConfigFn(func(*rest.Config) *rest.Config { + return cfg + }) +} diff --git a/cmd/experimental/kjobctl/test/util/util.go b/cmd/experimental/kjobctl/test/util/util.go new file mode 100644 index 0000000000..548c4d9bab --- /dev/null +++ b/cmd/experimental/kjobctl/test/util/util.go @@ -0,0 +1,40 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "context" + + v1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// DeleteNamespace deletes all objects the tests typically create in the namespace. +func DeleteNamespace(ctx context.Context, c client.Client, ns *corev1.Namespace) error { + if ns == nil { + return nil + } + if err := c.DeleteAllOf(ctx, &v1.Job{}, client.InNamespace(ns.Name)); err != nil && !apierrors.IsNotFound(err) { + return err + } + if err := c.Delete(ctx, ns); err != nil && !apierrors.IsNotFound(err) { + return err + } + return nil +}