diff --git a/Makefile b/Makefile index 4855a221b1..b6de49825f 100644 --- a/Makefile +++ b/Makefile @@ -173,8 +173,6 @@ shell-lint: ## Run shell linting. .PHONY: verify verify: gomod-verify ci-lint fmt-verify shell-lint toc-verify manifests generate update-helm generate-apiref prepare-release-branch git --no-pager diff --exit-code config/components apis charts/kueue/templates client-go site/ - # verify kjobctl - cd cmd/experimental/kjobctl && make verify ##@ Build diff --git a/cmd/experimental/kjobctl/Makefile b/cmd/experimental/kjobctl/Makefile index 651941172e..7408f40366 100644 --- a/cmd/experimental/kjobctl/Makefile +++ b/cmd/experimental/kjobctl/Makefile @@ -15,6 +15,13 @@ 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 @@ -69,7 +76,6 @@ fmt-verify: exit 1; \ fi - .PHONY: fmt fmt: ## Run go fmt against code. go fmt ./... @@ -83,13 +89,14 @@ 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 - -# 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. -test-e2e: - go test ./test/e2e/ -v -ginkgo.v +test: verify gotestsum gomod-download envtest ginkgo ## Run tests. + # Unit tests + $(GOTESTSUM) --junitfile $(ARTIFACTS)/junit.xml -- $(GO_TEST_FLAGS) $(shell $(GO_CMD) list ./... | grep -v '/test/') -coverpkg=./... -coverprofile $(ARTIFACTS)/cover.out + # Integration 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) --race --junit-report=junit.xml --output-dir=$(ARTIFACTS) -v $(INTEGRATION_TARGET) .PHONY: lint lint: golangci-lint ## Run golangci-lint linter @@ -131,13 +138,17 @@ KUSTOMIZE_VERSION ?= $(shell cd $(TOOLS_DIR) && $(GO_CMD) list -m -f '{{.Version CONTROLLER_GEN_VERSION ?= $(shell cd $(TOOLS_DIR) && $(GO_CMD) list -m -f '{{.Version}}' sigs.k8s.io/controller-tools) ENVTEST_VERSION ?= $(shell cd $(TOOLS_DIR) && $(GO_CMD) list -m -f '{{.Version}}' sigs.k8s.io/controller-runtime/tools/setup-envtest) GOLANGCI_LINT_VERSION ?= $(shell cd $(TOOLS_DIR) && $(GO_CMD) list -m -f '{{.Version}}' github.com/golangci/golangci-lint) +GOTESTSUM_VERSION ?=$(shell cd $(TOOLS_DIR) && $(GO_CMD) list -m -f '{{.Version}}' gotest.tools/gotestsum) +GINKGO_VERSION ?= $(shell cd $(TOOLS_DIR) && $(GO_CMD) list -m -f '{{.Version}}' github.com/onsi/ginkgo/v2) ## Tool Binaries KUBECTL ?= kubectl KUSTOMIZE ?= $(LOCALBIN)/kustomize-$(KUSTOMIZE_VERSION) CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen-$(CONTROLLER_GEN_VERSION) ENVTEST ?= $(LOCALBIN)/setup-envtest-$(ENVTEST_VERSION) -GOLANGCI_LINT = $(LOCALBIN)/golangci-lint-$(GOLANGCI_LINT_VERSION) +GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint-$(GOLANGCI_LINT_VERSION) +GOTESTSUM ?= $(LOCALBIN)/gotestsum-$(GOTESTSUM_VERSION) +GINKGO ?= $(LOCALBIN)/ginkgo-$(GINKGO_VERSION) .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. @@ -157,7 +168,15 @@ $(ENVTEST): $(LOCALBIN) .PHONY: golangci-lint 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}) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) + +.PHONY: gotestsum +gotestsum: ## Download gotestsum locally if necessary. + $(call go-install-tool,$(GOTESTSUM),gotest.tools/gotestsum,$(GOTESTSUM_VERSION)) + +.PHONY: ginkgo +ginkgo: ## Download ginkgo locally if necessary. + $(call go-install-tool,$(GINKGO),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) diff --git a/cmd/experimental/kjobctl/go.mod b/cmd/experimental/kjobctl/go.mod index 1ad55b3ce5..b6537cbaf2 100644 --- a/cmd/experimental/kjobctl/go.mod +++ b/cmd/experimental/kjobctl/go.mod @@ -4,6 +4,8 @@ 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.1 k8s.io/api v0.30.2 k8s.io/apimachinery v0.30.2 @@ -20,16 +22,19 @@ require ( github.com/davecgh/go-spew v1.1.1 // 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/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/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 @@ -56,11 +61,13 @@ require ( golang.org/x/term v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.21.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.33.0 // indirect 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/apiextensions-apiserver v0.30.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect diff --git a/cmd/experimental/kjobctl/go.sum b/cmd/experimental/kjobctl/go.sum index 2729d28b3b..03a5dee8a3 100644 --- a/cmd/experimental/kjobctl/go.sum +++ b/cmd/experimental/kjobctl/go.sum @@ -2,7 +2,11 @@ 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/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= @@ -20,10 +24,16 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m 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/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= 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= @@ -32,12 +42,13 @@ 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/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= @@ -97,6 +108,9 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +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= @@ -118,7 +132,15 @@ 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/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.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -147,11 +169,17 @@ 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/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= @@ -221,6 +249,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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= @@ -258,6 +288,8 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI= k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI= +k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws= +k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4= k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/cli-runtime v0.30.2 h1:ooM40eEJusbgHNEqnHziN9ZpLN5U4WcQGsdLKVxpkKE= diff --git a/cmd/experimental/kjobctl/hack/tools/go.mod b/cmd/experimental/kjobctl/hack/tools/go.mod index 38d471b10e..34003f2652 100644 --- a/cmd/experimental/kjobctl/hack/tools/go.mod +++ b/cmd/experimental/kjobctl/hack/tools/go.mod @@ -4,6 +4,8 @@ go 1.22.4 require ( github.com/golangci/golangci-lint v1.59.1 + github.com/onsi/ginkgo/v2 v2.19.0 + gotest.tools/gotestsum v1.12.0 k8s.io/code-generator v0.30.2 sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20240618123038-8290d13680ed sigs.k8s.io/controller-tools v0.15.0 @@ -11,9 +13,12 @@ require ( ) require ( + github.com/bitfield/gotestdox v0.2.2 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dnephin/pflag v1.0.7 // indirect github.com/fatih/color v1.17.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0-beta // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.4.1 // indirect @@ -25,6 +30,7 @@ require ( github.com/golang/protobuf v1.5.4 // 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-20240525223248-4bfdf5a9a2af // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -46,6 +52,7 @@ require ( golang.org/x/net v0.26.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.21.0 // indirect + golang.org/x/term v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/tools v0.22.0 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/cmd/experimental/kjobctl/hack/tools/go.sum b/cmd/experimental/kjobctl/hack/tools/go.sum index 361b685872..87e403d514 100644 --- a/cmd/experimental/kjobctl/hack/tools/go.sum +++ b/cmd/experimental/kjobctl/hack/tools/go.sum @@ -1,3 +1,5 @@ +github.com/bitfield/gotestdox v0.2.2 h1:x6RcPAbBbErKLnapz1QeAlf3ospg8efBsedU93CDsnE= +github.com/bitfield/gotestdox v0.2.2/go.mod h1:D+gwtS0urjBrzguAkTM2wodsTQYFHdpx8eqRJ3N+9pY= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -9,6 +11,10 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/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/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -70,6 +76,8 @@ 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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -121,6 +129,7 @@ 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= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -132,37 +141,101 @@ 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.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/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.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.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.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.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= 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.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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 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.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -186,6 +259,10 @@ 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= 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.31.0-alpha.1 h1:ZWYSLeKpvdx5GLJdIkFDMz7I1qS/Gwb2QiJT2feQpRg= diff --git a/cmd/experimental/kjobctl/hack/tools/pinversion.go b/cmd/experimental/kjobctl/hack/tools/pinversion.go index 399f7f8c45..82d6d73a0d 100644 --- a/cmd/experimental/kjobctl/hack/tools/pinversion.go +++ b/cmd/experimental/kjobctl/hack/tools/pinversion.go @@ -16,9 +16,12 @@ 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/golangci/golangci-lint/pkg/exitcodes" + _ "github.com/onsi/ginkgo/v2/ginkgo/command" + _ "github.com/onsi/ginkgo/v2/ginkgo/run" + _ "gotest.tools/gotestsum/cmd" _ "k8s.io/code-generator" _ "sigs.k8s.io/controller-runtime/tools/setup-envtest/env" // since verify will error when referencing a cmd package 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..483cf73032 --- /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: constants.ProfileLabel, + 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..1d69f847f2 --- /dev/null +++ b/cmd/experimental/kjobctl/pkg/cmd/list/list_job_test.go @@ -0,0 +1,276 @@ +/* +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 only kjobctl jobs": { + 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"). + 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 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..2e6238c541 --- /dev/null +++ b/cmd/experimental/kjobctl/pkg/cmd/list/list_test.go @@ -0,0 +1,101 @@ +/* +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 ( + "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/testing/fake.go b/cmd/experimental/kjobctl/pkg/cmd/testing/fake.go index 4fe5795aac..5328fd06d9 100644 --- a/cmd/experimental/kjobctl/pkg/cmd/testing/fake.go +++ b/cmd/experimental/kjobctl/pkg/cmd/testing/fake.go @@ -1,3 +1,19 @@ +/* +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 testing import ( 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..77992d109b --- /dev/null +++ b/cmd/experimental/kjobctl/pkg/testing/wrappers/job_wrappers.go @@ -0,0 +1,107 @@ +/* +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, + }, + 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 +} + +// Completions updates job completions. +func (j *JobWrapper) Completions(p int32) *JobWrapper { + j.Spec.Completions = ptr.To(p) + return j +} + +// 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 +}