#!/usr/bin/env bash # Copyright 2020-2025 the Pinniped contributors. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" KUBE_VERSIONS=("$@") BASE_PKG="go.pinniped.dev" export GO111MODULE="on" # Note that you can change this value to 10 to debug the Kubernetes code generator shell scripts used below. debug_level="${CODEGEN_LOG_LEVEL:-1}" # If we're not running in a container, assume that we want to loop over and run each build # in a container. if [[ -z "${CONTAINED:-}" ]]; then for kubeVersion in "${KUBE_VERSIONS[@]}"; do # CODEGEN_IMAGE is the container image to use when running CODEGEN_IMAGE="ghcr.io/pinniped-ci-bot/k8s-code-generator-$(echo "$kubeVersion" | cut -d"." -f1-2):latest" echo "generating code for ${kubeVersion} using ${CODEGEN_IMAGE}..." docker run --rm \ --pull always \ --env CONTAINED=1 \ --env CODEGEN_LOG_LEVEL="$debug_level" \ --volume "${ROOT}:/work" \ --workdir "/work" \ "${CODEGEN_IMAGE}" \ "/work/hack/lib/$(basename "${BASH_SOURCE[0]}")" \ "${kubeVersion}" \ | sed "s|^|${kubeVersion} > |" done exit 0 fi # Now that we know we are running in the nested container, expect there to be only # a single Kubernetes version if [[ "${#KUBE_VERSIONS[@]}" -ne 1 ]]; then echo "when running in a container, we can only generate for a single kubernetes version" >&2 exit 1 fi # Add this to the git config inside the container to avoid permission errors when running this script on linux. git config --global --add safe.directory /work # Link the root directory into GOPATH since that is where output ends up. GOPATH_ROOT="${GOPATH}/src/${BASE_PKG}" mkdir -p "$(dirname "${GOPATH_ROOT}")" ln -s "${ROOT}" "${GOPATH_ROOT}" ROOT="${GOPATH_ROOT}" cd "${ROOT}" # KUBE_VERSION is the full version (e.g., '1.28.0-rc.0'). KUBE_VERSION="${KUBE_VERSIONS[0]}" export KUBE_VERSION # KUBE_MINOR_VERSION is just the major/minor version (e.g., '1.28'). KUBE_MINOR_VERSION="$(echo "${KUBE_VERSION}" | cut -d"." -f1-2)" export KUBE_MINOR_VERSION # Check if it is a recent version of Kube. KUBE_MAJOR_NUMBER="$(echo "${KUBE_VERSION}" | cut -d"." -f1)" # (e.g. '1') KUBE_MINOR_NUMBER="$(echo "${KUBE_VERSION}" | cut -d"." -f2)" # (e.g. '28') KUBE_1_29_OR_NEWER="no" if [[ "$KUBE_MAJOR_NUMBER" -gt "1" || ( "$KUBE_MAJOR_NUMBER" == "1" && "$KUBE_MINOR_NUMBER" -ge "29" ) ]]; then KUBE_1_29_OR_NEWER="yes" fi KUBE_1_30_OR_NEWER="no" if [[ "$KUBE_MAJOR_NUMBER" -gt "1" || ( "$KUBE_MAJOR_NUMBER" == "1" && "$KUBE_MINOR_NUMBER" -ge "30" ) ]]; then KUBE_1_30_OR_NEWER="yes" fi KUBE_1_35_OR_NEWER="no" if [[ "$KUBE_MAJOR_NUMBER" -gt "1" || ( "$KUBE_MAJOR_NUMBER" == "1" && "$KUBE_MINOR_NUMBER" -ge "35" ) ]]; then KUBE_1_35_OR_NEWER="yes" fi # KUBE_MODULE_VERSION is just version of client libraries (e.g., 'v0.28.9-rc-0'). KUBE_MODULE_VERSION="v0.$(echo "${KUBE_VERSION}" | cut -d '.' -f 2-)" export KUBE_MODULE_VERSION # Start by picking an output directory and deleting any previously-generated code. OUTPUT_DIR="${ROOT}/generated/${KUBE_MINOR_VERSION}" rm -rf "${OUTPUT_DIR}" mkdir -p "${OUTPUT_DIR}" cd "${OUTPUT_DIR}" echo "running in container to generate ${KUBE_VERSION} into ${OUTPUT_DIR}..." # Next, copy in the base definitions of our APIs from ./apis into the generated directory, substituting some # variables in the template files and renaming them to strip the `.tmpl` extension. cp -R "${ROOT}/apis" "${OUTPUT_DIR}/apis" find "${OUTPUT_DIR}" -type f -exec sed -i "s|GENERATED_PKG|generated/${KUBE_MINOR_VERSION}|g" {} \; find "${OUTPUT_DIR}" -type f -not -name '*.tmpl' -exec rm {} \; find "${OUTPUT_DIR}" -type f -name '*.tmpl' -exec bash -c 'mv "$0" "${0%.tmpl}"' {} \; # Starting in Kube 1.29, we need to use language level 1.21. GO_LANG_LEVEL="1.13" if [[ "$KUBE_1_29_OR_NEWER" == "yes" ]]; then GO_LANG_LEVEL="1.21" fi # Make the generated API code its own Go module. echo "generating ${OUTPUT_DIR}/apis/go.mod..." cat << EOF > "${OUTPUT_DIR}/apis/go.mod" // This go.mod file is generated by ./hack/update.sh. module ${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/apis go $GO_LANG_LEVEL require ( k8s.io/apimachinery ${KUBE_MODULE_VERSION} k8s.io/api ${KUBE_MODULE_VERSION} ) EOF # Create a Go file that declares the package or else we can't use "go mod tidy" in the client subdirectory. # Without this, go mod tidy complains that this directory "but does not contain package". # We will delete this at the end of the script. cat << EOF > "${OUTPUT_DIR}/apis/placeholder.go" package apis EOF echo "Showing initial apis/go.mod..." cat "${OUTPUT_DIR}/apis/go.mod" | sed "s|^|cat-go-mod > |" # Generate a go.sum without changing the go.mod by running go mod download. echo "running go mod download in ${OUTPUT_DIR}/apis/go.mod to generate a go.sum file..." (cd "${OUTPUT_DIR}/apis" && go mod download all 2>&1 | sed "s|^|go-mod-download > |") # Starting in Kube 1.29, the code generator complains that we need to run go mod tidy first. if [[ "$KUBE_1_29_OR_NEWER" == "yes" ]]; then echo "running go mod tidy in ${OUTPUT_DIR}/apis..." (cd "${OUTPUT_DIR}/apis" && go mod tidy 2>&1 | sed "s|^|go-mod-tidy > |") echo "Showing updated apis/go.mod..." cat "${OUTPUT_DIR}/apis/go.mod" | sed "s|^|cat-updated-go-mod > |" fi # Make the generated client code its own Go module. echo "generating ${OUTPUT_DIR}/client/go.mod..." mkdir client mkdir client/concierge mkdir client/supervisor # Create a tools.go just to prevent "go mod tidy" from deleting these packages from the go.mod as unused deps. # We will delete this at the end of the script. cat << EOF > "${OUTPUT_DIR}/client/tools.go" //go:build tools package tools import ( _ "k8s.io/api" _ "k8s.io/apimachinery" _ "k8s.io/client-go" _ "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/apis" ) EOF # Create client/go.mod. cat << EOF > "${OUTPUT_DIR}/client/go.mod" // This go.mod file is generated by ./hack/update.sh. module ${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/client go $GO_LANG_LEVEL replace ${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/apis => ../apis require ( k8s.io/api ${KUBE_MODULE_VERSION} k8s.io/apimachinery ${KUBE_MODULE_VERSION} k8s.io/client-go ${KUBE_MODULE_VERSION} ${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/apis v0.0.0 ) EOF echo "Showing initial client/go.mod..." cat "${OUTPUT_DIR}/client/go.mod" | sed "s|^|cat-go-mod > |" # Generate a go.sum without changing the go.mod by running go mod download. echo "running go mod download in ${OUTPUT_DIR}/client/go.mod to generate a go.sum file..." (cd "${OUTPUT_DIR}/client" && go mod download all 2>&1 | sed "s|^|go-mod-download > |") # Starting in Kube 1.29, the code generator complains that we need to run go mod tidy first. if [[ "$KUBE_1_29_OR_NEWER" == "yes" ]]; then echo "running go mod tidy in ${OUTPUT_DIR}/client..." (cd "${OUTPUT_DIR}/client" && go mod tidy 2>&1 | sed "s|^|go-mod-tidy > |") echo "Showing updated client/go.mod..." cat "${OUTPUT_DIR}/client/go.mod" | sed "s|^|cat-updated-go-mod > |" fi # These files were only created above to satisfy the above usages of "go mod tidy" in this script. # We don't need to commit them. Delete them before the final "go mod tidy" so that unused # imports can be deleted by the usages of "go mod tidy" below. rm -f apis/placeholder.go client/tools.go chmod -R +x "${GOPATH}/src/k8s.io/code-generator/" if [[ "$KUBE_1_30_OR_NEWER" == "yes" ]]; then # Use the new code generator scripts from Kubernetes. # The old scripts were deprecated in 1.28 and removed in Kube 1.30. # The new script is called kube_codegen.sh. echo "Using new codegen tooling from kube_codegen.sh ..." # This is a dirty hack to avoid needing to rework much of this script. # Without this, kube::codegen::gen_client will resolve our symlink to its true path and then cause an error. # With this change, it will use whatever path we give it, so we can use our path which contains a symlink. # Once we are not using the old Kube codegen scripts anymore, we should probably consider rewriting this # whole script instead of using this dirty hack. sed -i -E -e 's/--input-base.*/--input-base "${in_dir}" \\/g' "${GOPATH}/src/k8s.io/code-generator/kube_codegen.sh" source "${GOPATH}/src/k8s.io/code-generator/kube_codegen.sh" # The functions from kube_codegen.sh pay attention to this env var. export KUBE_VERBOSE="$debug_level" echo "generating API helpers..." # In the old codegen scripts, you would specify which generators you want to run as arguments. # In this new codegen script, you instead specify the source code directory and it decides # which generators to run for each package entirely by looking for "+k8s:*-gen" annotations # in our source code. pushd "${OUTPUT_DIR}/apis" > /dev/null kube::codegen::gen_helpers . \ --boilerplate "${ROOT}/hack/boilerplate.go.txt" 2>&1 | sed "s|^|gen-helpers > |" popd > /dev/null # Tidy the apis module after codegen. echo "tidying ${OUTPUT_DIR}/apis/go.mod..." (cd apis && go mod tidy 2>&1 | sed "s|^|go-mod-tidy > |") echo "generating API clients and openapi..." # Note that --extra-pkgs can be set to generate openapi docs for other k8s APIs that our types depend upon # aside from the default k8s packages that are already added automatically by the kube::codegen::gen_openapi script # (meta/v1, runtime, and version). E.g. TokenCredentialRequestSpec uses corev1.TypedLocalObjectReference, so we # add core/v1 when running codegen for the Pinniped aggregated APIs here. concierge_gen_openapi_args=( "${OUTPUT_DIR}/apis/concierge" --update-report --extra-pkgs "k8s.io/api/core/v1" --output-dir "${OUTPUT_DIR}/client/concierge/openapi" --output-pkg "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/client/concierge" --boilerplate "${ROOT}/hack/boilerplate.go.txt" ) supervisor_gen_openapi_args=( "${OUTPUT_DIR}/apis/supervisor" --update-report --extra-pkgs "k8s.io/api/core/v1" --output-dir "${OUTPUT_DIR}/client/supervisor/openapi" --output-pkg "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/client/supervisor" --boilerplate "${ROOT}/hack/boilerplate.go.txt" ) if [[ "$KUBE_1_35_OR_NEWER" == "yes" ]]; then concierge_gen_openapi_args+=(--output-model-name-file "zz_generated.model_name.go") supervisor_gen_openapi_args+=(--output-model-name-file "zz_generated.model_name.go") fi pushd "${OUTPUT_DIR}/apis/concierge" > /dev/null kube::codegen::gen_client "${OUTPUT_DIR}/apis/concierge" \ --with-watch \ --output-dir "${OUTPUT_DIR}/client/concierge" \ --output-pkg "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/client/concierge" \ --boilerplate "${ROOT}/hack/boilerplate.go.txt" 2>&1 | sed "s|^|gen-client-concierge > |" kube::codegen::gen_openapi "${concierge_gen_openapi_args[@]}" 2>&1 | sed "s|^|gen-openapi-concierge > |" popd > /dev/null pushd "${OUTPUT_DIR}/apis/supervisor" > /dev/null kube::codegen::gen_client "${OUTPUT_DIR}/apis/supervisor" \ --with-watch \ --output-dir "${OUTPUT_DIR}/client/supervisor" \ --output-pkg "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/client/supervisor" \ --boilerplate "${ROOT}/hack/boilerplate.go.txt" 2>&1 | sed "s|^|gen-client-supervisor > |" kube::codegen::gen_openapi "${supervisor_gen_openapi_args[@]}" 2>&1 | sed "s|^|gen-openapi-supervisor > |" popd > /dev/null # Tidy the client module after codegen. echo "tidying ${OUTPUT_DIR}/client/go.mod..." (cd client && go mod tidy 2>&1 | sed "s|^|go-mod-tidy > |") else # For older Kube versions, use the old code generator scripts from Kubernetes: generate-groups.sh and generate-groups-internal.sh. # The old scripts were deprecated in 1.28 and removed in Kube 1.30, but for older versions we can still use them. # Generate API-related code for our public API groups echo "generating API-related code for our public API groups..." (cd apis && bash "${GOPATH}/src/k8s.io/code-generator/generate-groups.sh" \ "deepcopy" \ "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/apis" \ "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/apis" \ "supervisor/config:v1alpha1 supervisor/idp:v1alpha1 supervisor/clientsecret:v1alpha1 concierge/config:v1alpha1 concierge/authentication:v1alpha1 concierge/login:v1alpha1 concierge/identity:v1alpha1" \ --go-header-file "${ROOT}/hack/boilerplate.go.txt" -v "$debug_level" 2>&1 | sed "s|^|gen-api > |" ) # Generate API-related code for our internal API groups. # Note that OPENAPI_EXTRA_PACKAGES can be set to generate openapi docs for other k8s APIs that our types depend upon # aside from the default k8s packages that are already added automatically by the generate-internal-groups.sh script # (meta/v1, runtime, and version). E.g. TokenCredentialRequestSpec uses corev1.TypedLocalObjectReference, so we # add core/v1 when running codegen for the Concierge aggregated APIs here. echo "generating API-related code for our internal API groups..." (cd apis && OPENAPI_EXTRA_PACKAGES="k8s.io/api/core/v1" \ bash "${GOPATH}/src/k8s.io/code-generator/generate-internal-groups.sh" \ "deepcopy,defaulter,conversion,openapi" \ "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/client/concierge" \ "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/apis" \ "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/apis" \ "concierge/login:v1alpha1 concierge/identity:v1alpha1" \ --go-header-file "${ROOT}/hack/boilerplate.go.txt" -v "$debug_level" 2>&1 | sed "s|^|gen-concierge-int-api > |" ) (cd apis && bash "${GOPATH}/src/k8s.io/code-generator/generate-internal-groups.sh" \ "deepcopy,defaulter,conversion,openapi" \ "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/client/supervisor" \ "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/apis" \ "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/apis" \ "supervisor/clientsecret:v1alpha1" \ --go-header-file "${ROOT}/hack/boilerplate.go.txt" -v "$debug_level" 2>&1 | sed "s|^|gen-supervisor-int-api > |" ) # Tidy the apis module after codegen. echo "tidying ${OUTPUT_DIR}/apis/go.mod..." (cd apis && go mod tidy 2>&1 | sed "s|^|go-mod-tidy > |") # Generate client code for our public API groups echo "generating client code for our public API groups..." (cd client && bash "${GOPATH}/src/k8s.io/code-generator/generate-groups.sh" \ "client,lister,informer" \ "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/client/concierge" \ "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/apis" \ "concierge/config:v1alpha1 concierge/authentication:v1alpha1 concierge/login:v1alpha1 concierge/identity:v1alpha1" \ --go-header-file "${ROOT}/hack/boilerplate.go.txt" -v "$debug_level" 2>&1 | sed "s|^|gen-concierge-client > |" ) (cd client && bash "${GOPATH}/src/k8s.io/code-generator/generate-groups.sh" \ "client,lister,informer" \ "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/client/supervisor" \ "${BASE_PKG}/generated/${KUBE_MINOR_VERSION}/apis" \ "supervisor/config:v1alpha1 supervisor/idp:v1alpha1 supervisor/clientsecret:v1alpha1" \ --go-header-file "${ROOT}/hack/boilerplate.go.txt" -v "$debug_level" 2>&1 | sed "s|^|gen-supervisor-client > |" ) # Tidy the client module after codegen. echo "tidying ${OUTPUT_DIR}/client/go.mod..." (cd client && go mod tidy 2>&1 | sed "s|^|go-mod-tidy > |") fi # Generate API documentation sed "s|KUBE_MINOR_VERSION|${KUBE_MINOR_VERSION}|g" < "${ROOT}/hack/lib/docs/config.yaml" > /tmp/docs-config.yaml crd-ref-docs \ --source-path="${ROOT}/generated/${KUBE_MINOR_VERSION}/apis" \ --config=/tmp/docs-config.yaml \ --renderer=asciidoctor \ --templates-dir="${ROOT}/hack/lib/docs/templates" \ --output-path="${ROOT}/generated/${KUBE_MINOR_VERSION}/README.adoc" # Generate CRD YAML (cd apis && controller-gen paths=./supervisor/config/v1alpha1 crd output:crd:artifacts:config=../crds && controller-gen paths=./supervisor/idp/v1alpha1 crd output:crd:artifacts:config=../crds && controller-gen paths=./concierge/config/v1alpha1 crd output:crd:artifacts:config=../crds && controller-gen paths=./concierge/authentication/v1alpha1 crd output:crd:artifacts:config=../crds )