diff --git a/.github/workflows/build-nautobotop.yaml b/.github/workflows/build-nautobotop.yaml new file mode 100644 index 000000000..0e4cd25f7 --- /dev/null +++ b/.github/workflows/build-nautobotop.yaml @@ -0,0 +1,91 @@ +--- +name: build-nautobotop-images + +on: + workflow_dispatch: + push: + tags: + - nautobotop-v* + paths: + - "go/nautobotop/**" + +jobs: + build-nautobotop: + runs-on: ubuntu-latest + permissions: + packages: write + contents: write + id-token: write + steps: + - name: Checkout + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6 + with: + fetch-depth: 0 + + - name: Install Go + uses: actions/setup-go@v6 + with: + go-version: '1.24' + cache: true + + - name: Install syft + uses: anchore/sbom-action/download-syft@f8bdd1d8ac5e901a77a92f111440fdb1b593736b # v0.20.6 + + - name: Install Cosign + uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + + - name: Login to ghcr.io + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 + with: + registry: "ghcr.io" + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract tag name + id: extract_tag + run: echo "tag=${GITHUB_REF#refs/tags/nautobotop-v}" >> $GITHUB_OUTPUT + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: "~> v2" + args: release --clean --skip=validate + workdir: go/nautobotop + env: + CUSTOM_TAG: ${{ steps.extract_tag.outputs.tag }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + chart: + runs-on: ubuntu-latest + needs: + - build-nautobotop + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Log in to ghcr.io + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: '${{ github.actor }}' + password: '${{ secrets.GITHUB_TOKEN }}' + - name: Package and push Helm chart + working-directory: go/nautobotop + env: + PKG_VER: '${{ github.ref_name }}' + run: | + PKG_VER=${PKG_VER#nautobotop-v} + # update Chart.yaml with tag version + yq -i ".version = \"${PKG_VER}\"" helm/Chart.yaml + yq -i ".appVersion = \"${PKG_VER}\"" helm/Chart.yaml + # package chart + helm package -u -d ${{ github.workspace }} helm + # push chart to ghcr.io + helm push ${{ github.workspace }}/nautobotop-${PKG_VER}.tgz \ + oci://ghcr.io/${GITHUB_REPOSITORY_OWNER}/charts diff --git a/apps/appsets/project-understack-operators.yaml b/apps/appsets/project-understack-operators.yaml index 7a0ad930d..1af77cf25 100644 --- a/apps/appsets/project-understack-operators.yaml +++ b/apps/appsets/project-understack-operators.yaml @@ -18,6 +18,8 @@ spec: server: '*' - namespace: 'external-dns' server: '*' + - namespace: 'nautobotop' + server: '*' - namespace: 'external-secrets' server: '*' - namespace: 'rook-ceph' diff --git a/apps/operators/nautobotop.yaml b/apps/operators/nautobotop.yaml new file mode 100644 index 000000000..d613c15f8 --- /dev/null +++ b/apps/operators/nautobotop.yaml @@ -0,0 +1,9 @@ +--- +component: nautobotop +sources: + - repoURL: ghcr.io/rackerlabs/charts + chart: nautobotop + targetRevision: 0.0.1 + helm: + releaseName: nautobotop + ignoreMissingValueFiles: true diff --git a/go/rax/.dockerignore b/go/nautobotop/.dockerignore similarity index 100% rename from go/rax/.dockerignore rename to go/nautobotop/.dockerignore diff --git a/go/rax/.gitignore b/go/nautobotop/.gitignore similarity index 98% rename from go/rax/.gitignore rename to go/nautobotop/.gitignore index 8b0aab298..04bf9273e 100644 --- a/go/rax/.gitignore +++ b/go/nautobotop/.gitignore @@ -1,10 +1,12 @@ # Binaries for programs and plugins *.exe +.DS_Store *.exe~ *.dll *.so *.dylib bin/* +dist/* Dockerfile.cross # Test binary, built with `go test -c` @@ -24,5 +26,4 @@ go.work .vscode *.swp *.swo -.DS_Store *~ diff --git a/go/nautobotop/.golangci.yml b/go/nautobotop/.golangci.yml new file mode 100644 index 000000000..e5b21b0f1 --- /dev/null +++ b/go/nautobotop/.golangci.yml @@ -0,0 +1,52 @@ +version: "2" +run: + allow-parallel-runners: true +linters: + default: none + enable: + - copyloopvar + - dupl + - errcheck + - ginkgolinter + - goconst + - gocyclo + - govet + - ineffassign + - lll + - misspell + - nakedret + - prealloc + - revive + - staticcheck + - unconvert + - unparam + - unused + settings: + revive: + rules: + - name: comment-spacings + - name: import-shadowing + exclusions: + generated: lax + rules: + - linters: + - lll + path: api/* + - linters: + - dupl + - lll + path: internal/* + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/go/nautobotop/.goreleaser.Dockerfile b/go/nautobotop/.goreleaser.Dockerfile new file mode 100644 index 000000000..149292ea3 --- /dev/null +++ b/go/nautobotop/.goreleaser.Dockerfile @@ -0,0 +1,5 @@ +# goreleaser is making the binary dynamically linked so can't use the static container +FROM gcr.io/distroless/base-debian12:nonroot +COPY --chmod=555 nautobotop /usr/local/bin/nautobotop +USER 65532:65532 +ENTRYPOINT ["/usr/local/bin/nautobotop"] diff --git a/go/nautobotop/.goreleaser.yaml b/go/nautobotop/.goreleaser.yaml new file mode 100644 index 000000000..22fdcc965 --- /dev/null +++ b/go/nautobotop/.goreleaser.yaml @@ -0,0 +1,97 @@ +project_name: "nautobotop" +version: 2 + +before: + hooks: + - go mod tidy + +env: + - CUSTOM_TAG={{ .Env.CUSTOM_TAG }} + +builds: + - main: main.go + dir: ./cmd + binary: nautobotop + goos: ["linux"] + goarch: ["amd64", "arm64"] + flags: + - -trimpath + ldflags: + - -s + - -w + - -X main.version={{.Version}} + - -X main.commit={{.ShortCommit}} + env: + - CGO_ENABLED=0 + +changelog: + disable: true + +dockers: + - skip_push: false + use: buildx + dockerfile: .goreleaser.Dockerfile + image_templates: + - ghcr.io/rackerlabs/understack/{{ .ProjectName }}:{{ .Env.CUSTOM_TAG }}-amd64 + build_flag_templates: + - --platform=linux/amd64 + - --label=org.opencontainers.image.version={{ .Env.CUSTOM_TAG }} + - --label=org.opencontainers.image.revision={{ .Commit }} + - --label=org.opencontainers.image.title={{ .ProjectName }} + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description=Rackspace Cloud DNS support for cert-manager + - --label=org.opencontainers.image.vendor=rackspace + - --label=org.opencontainers.image.licenses=Apache License 2.0 + - --label=org.opencontainers.image.source=https://rackspace.com/ + - --label=org.opencontainers.image.authors=Rackspace + - skip_push: false + goarch: arm64 + use: buildx + dockerfile: .goreleaser.Dockerfile + image_templates: + - ghcr.io/rackerlabs/understack/{{ .ProjectName }}:{{ .Env.CUSTOM_TAG }}-arm64 + build_flag_templates: + - --platform=linux/arm64 + - --label=org.opencontainers.image.version={{ .Env.CUSTOM_TAG }} + - --label=org.opencontainers.image.revision={{ .Commit }} + - --label=org.opencontainers.image.title={{ .ProjectName }} + - --label=org.opencontainers.image.created={{ .Date }} + - --label=org.opencontainers.image.description=Rackspace Cloud DNS support for cert-manager + - --label=org.opencontainers.image.vendor=rackspace + - --label=org.opencontainers.image.licenses=Apache License 2.0 + - --label=org.opencontainers.image.source=https://rackspace.com/ + - --label=org.opencontainers.image.authors=Rackspace +docker_manifests: + - name_template: ghcr.io/rackerlabs/understack/{{ .ProjectName }}:{{ .Env.CUSTOM_TAG }} + image_templates: + - ghcr.io/rackerlabs/understack/{{ .ProjectName }}:{{ .Env.CUSTOM_TAG }}-amd64 + - ghcr.io/rackerlabs/understack/{{ .ProjectName }}:{{ .Env.CUSTOM_TAG }}-arm64 + - name_template: ghcr.io/rackerlabs/understack/{{ .ProjectName }}:latest + image_templates: + - ghcr.io/rackerlabs/understack/{{ .ProjectName }}:{{ .Env.CUSTOM_TAG }}-amd64 + - ghcr.io/rackerlabs/understack/{{ .ProjectName }}:{{ .Env.CUSTOM_TAG }}-arm64 + + +signs: + - cmd: cosign + signature: "${artifact}.sig" + certificate: "${artifact}.pem" + output: true + artifacts: checksum + args: + - sign-blob + - "--oidc-provider=github-actions" + - "--output-certificate=${certificate}" + - "--output-signature=${signature}" + - "${artifact}" + - --yes + +docker_signs: + - cmd: cosign + artifacts: manifests + output: true + args: + - "sign" + - "--oidc-provider=github-actions" + - "${artifact}@${digest}" + - --yes diff --git a/go/rax/Dockerfile b/go/nautobotop/Dockerfile similarity index 96% rename from go/rax/Dockerfile rename to go/nautobotop/Dockerfile index a37e22524..cb1b130fd 100644 --- a/go/rax/Dockerfile +++ b/go/nautobotop/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM docker.io/golang:1.25 AS builder +FROM golang:1.24 AS builder ARG TARGETOS ARG TARGETARCH diff --git a/go/rax/Makefile b/go/nautobotop/Makefile similarity index 57% rename from go/rax/Makefile rename to go/nautobotop/Makefile index 25c64a589..fef10e036 100644 --- a/go/rax/Makefile +++ b/go/nautobotop/Makefile @@ -1,3 +1,54 @@ +# VERSION defines the project version for the bundle. +# Update this value when you upgrade the version of your project. +# To re-generate a bundle for another specific version without changing the standard setup, you can: +# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) +# - use environment variables to overwrite this value (e.g export VERSION=0.0.2) +VERSION ?= 0.0.1 + +# CHANNELS define the bundle channels used in the bundle. +# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") +# To re-generate a bundle for other specific channels without changing the standard setup, you can: +# - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable) +# - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable") +ifneq ($(origin CHANNELS), undefined) +BUNDLE_CHANNELS := --channels=$(CHANNELS) +endif + +# DEFAULT_CHANNEL defines the default channel used in the bundle. +# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable") +# To re-generate a bundle for any other default channel without changing the default setup, you can: +# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable) +# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable") +ifneq ($(origin DEFAULT_CHANNEL), undefined) +BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) +endif +BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) + +# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images. +# This variable is used to construct full image tags for bundle and catalog images. +# +# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both +# rax.io/nautobotop-bundle:$VERSION and rax.io/nautobotop-catalog:$VERSION. +IMAGE_TAG_BASE ?= rax.io/nautobotop + +# BUNDLE_IMG defines the image:tag used for the bundle. +# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) +BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) + +# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command +BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + +# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests +# You can enable this value if you would like to use SHA Based Digests +# To enable set flag to true +USE_IMAGE_DIGESTS ?= false +ifeq ($(USE_IMAGE_DIGESTS), true) + BUNDLE_GEN_FLAGS += --use-image-digests +endif + +# Set the Operator SDK version to use. By default, what is installed on the system is used. +# This is useful for CI or a project to utilize a specific version of the operator-sdk toolkit. +OPERATOR_SDK_VERSION ?= v1.42.0 # Image URL to use all building/pushing image targets IMG ?= controller:latest @@ -65,17 +116,30 @@ test: manifests generate fmt vet setup-envtest ## Run tests. # The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally. # CertManager is installed by default; skip with: # - CERT_MANAGER_INSTALL_SKIP=true -.PHONY: test-e2e -test-e2e: manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. +KIND_CLUSTER ?= nautobotop-test-e2e + +.PHONY: setup-test-e2e +setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist @command -v $(KIND) >/dev/null 2>&1 || { \ echo "Kind is not installed. Please install Kind manually."; \ exit 1; \ } - @$(KIND) get clusters | grep -q 'kind' || { \ - echo "No Kind cluster is running. Please start a Kind cluster before running the e2e tests."; \ - exit 1; \ - } - go test ./test/e2e/ -v -ginkgo.v + @case "$$($(KIND) get clusters)" in \ + *"$(KIND_CLUSTER)"*) \ + echo "Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation." ;; \ + *) \ + echo "Creating Kind cluster '$(KIND_CLUSTER)'..."; \ + $(KIND) create cluster --name $(KIND_CLUSTER) ;; \ + esac + +.PHONY: test-e2e +test-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. + KIND_CLUSTER=$(KIND_CLUSTER) go test ./test/e2e/ -v -ginkgo.v + $(MAKE) cleanup-test-e2e + +.PHONY: cleanup-test-e2e +cleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests + @$(KIND) delete cluster --name $(KIND_CLUSTER) .PHONY: lint lint: golangci-lint ## Run golangci-lint linter @@ -121,10 +185,10 @@ PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le docker-buildx: ## Build and push docker image for the manager for cross-platform support # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross - - $(CONTAINER_TOOL) buildx create --name rax-builder - $(CONTAINER_TOOL) buildx use rax-builder + - $(CONTAINER_TOOL) buildx create --name nautobotop-builder + $(CONTAINER_TOOL) buildx use nautobotop-builder - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . - - $(CONTAINER_TOOL) buildx rm rax-builder + - $(CONTAINER_TOOL) buildx rm nautobotop-builder rm Dockerfile.cross .PHONY: build-installer @@ -173,12 +237,12 @@ GOLANGCI_LINT = $(LOCALBIN)/golangci-lint ## Tool Versions KUSTOMIZE_VERSION ?= v5.6.0 -CONTROLLER_TOOLS_VERSION ?= v0.17.2 +CONTROLLER_TOOLS_VERSION ?= v0.18.0 #ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20) ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}') #ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31) ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}') -GOLANGCI_LINT_VERSION ?= v1.63.4 +GOLANGCI_LINT_VERSION ?= v2.1.0 .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. @@ -206,7 +270,7 @@ $(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/v2/cmd/golangci-lint,$(GOLANGCI_LINT_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 @@ -223,3 +287,76 @@ mv $(1) $(1)-$(3) ;\ } ;\ ln -sf $(1)-$(3) $(1) endef + +.PHONY: operator-sdk +OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk +operator-sdk: ## Download operator-sdk locally if necessary. +ifeq (,$(wildcard $(OPERATOR_SDK))) +ifeq (, $(shell which operator-sdk 2>/dev/null)) + @{ \ + set -e ;\ + mkdir -p $(dir $(OPERATOR_SDK)) ;\ + OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ + curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$${OS}_$${ARCH} ;\ + chmod +x $(OPERATOR_SDK) ;\ + } +else +OPERATOR_SDK = $(shell which operator-sdk) +endif +endif + +.PHONY: bundle +bundle: manifests kustomize operator-sdk ## Generate bundle manifests and metadata, then validate generated files. + $(OPERATOR_SDK) generate kustomize manifests -q + cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) + $(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle $(BUNDLE_GEN_FLAGS) + $(OPERATOR_SDK) bundle validate ./bundle + +.PHONY: bundle-build +bundle-build: ## Build the bundle image. + $(CONTAINER_TOOL) build -f bundle.Dockerfile -t $(BUNDLE_IMG) . + +.PHONY: bundle-push +bundle-push: ## Push the bundle image. + $(MAKE) docker-push IMG=$(BUNDLE_IMG) + +.PHONY: opm +OPM = $(LOCALBIN)/opm +opm: ## Download opm locally if necessary. +ifeq (,$(wildcard $(OPM))) +ifeq (,$(shell which opm 2>/dev/null)) + @{ \ + set -e ;\ + mkdir -p $(dir $(OPM)) ;\ + OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ + curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.55.0/$${OS}-$${ARCH}-opm ;\ + chmod +x $(OPM) ;\ + } +else +OPM = $(shell which opm) +endif +endif + +# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0). +# These images MUST exist in a registry and be pull-able. +BUNDLE_IMGS ?= $(BUNDLE_IMG) + +# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0). +CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION) + +# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image. +ifneq ($(origin CATALOG_BASE_IMG), undefined) +FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG) +endif + +# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'. +# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see: +# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator +.PHONY: catalog-build +catalog-build: opm ## Build a catalog image. + $(OPM) index add --container-tool $(CONTAINER_TOOL) --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) + +# Push the catalog image. +.PHONY: catalog-push +catalog-push: ## Push a catalog image. + $(MAKE) docker-push IMG=$(CATALOG_IMG) diff --git a/go/rax/PROJECT b/go/nautobotop/PROJECT similarity index 53% rename from go/rax/PROJECT rename to go/nautobotop/PROJECT index dd05a4bdf..fdf63be46 100644 --- a/go/rax/PROJECT +++ b/go/nautobotop/PROJECT @@ -5,25 +5,18 @@ domain: rax.io layout: - go.kubebuilder.io/v4 -projectName: rax -repo: github.com/rackerlabs/understack/go/sync +plugins: + manifests.sdk.operatorframework.io/v2: {} + scorecard.sdk.operatorframework.io/v2: {} +projectName: nautobotop +repo: github.com/rackerlabs/understack/go/nautobotop resources: - api: crdVersion: v1 - namespaced: true controller: true domain: rax.io group: sync kind: Nautobot - path: github.com/rackerlabs/understack/go/sync/api/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1 - namespaced: true - controller: true - domain: rax.io - group: sync - kind: GitRepoWatcher - path: github.com/rackerlabs/understack/go/sync/api/v1alpha1 + path: github.com/rackerlabs/understack/go/nautobotop/api/v1alpha1 version: v1alpha1 version: "3" diff --git a/go/rax/README.md b/go/nautobotop/README.md similarity index 97% rename from go/rax/README.md rename to go/nautobotop/README.md index a087e3c25..29424f802 100644 --- a/go/rax/README.md +++ b/go/nautobotop/README.md @@ -101,7 +101,7 @@ kubebuilder edit --plugins=helm/v1-alpha ``` 2. See that a chart was generated under 'dist/chart', and users -can obtain this solution from there. + can obtain this solution from there. **NOTE:** If you change the project, you need to update the Helm Chart using the same command above to sync the latest changes. Furthermore, @@ -119,7 +119,7 @@ More information can be found via the [Kubebuilder Documentation](https://book.k ## License -Copyright 2025. +Copyright 2025 Rackspace Technology. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/go/nautobotop/api/v1alpha1/config_maps.go b/go/nautobotop/api/v1alpha1/config_maps.go new file mode 100644 index 000000000..024b49bdf --- /dev/null +++ b/go/nautobotop/api/v1alpha1/config_maps.go @@ -0,0 +1,25 @@ +package v1alpha1 + +// ConfigMapRef defines a reference to a specific ConfigMap +type ConfigMapRef struct { + // Name of this config set (logical name) + Name string `json:"name"` + + // The name of the ConfigMap resource being referred to + ConfigMapSelector ConfigMapKeySelector `json:"configMapSelector"` +} + +// ConfigMapKeySelector selects a specific key from a ConfigMap in a namespace +type ConfigMapKeySelector struct { + // The name of the ConfigMap + // +kubebuilder:validation:MinLength=1 + Name string `json:"name"` + + // The namespace where the ConfigMap resides + // +optional + Namespace *string `json:"namespace,omitempty"` + + // The key in the ConfigMap data + // +optional + Key string `json:"key,omitempty"` +} diff --git a/go/rax/api/v1alpha1/groupversion_info.go b/go/nautobotop/api/v1alpha1/groupversion_info.go similarity index 100% rename from go/rax/api/v1alpha1/groupversion_info.go rename to go/nautobotop/api/v1alpha1/groupversion_info.go diff --git a/go/rax/api/v1alpha1/nautobot_types.go b/go/nautobotop/api/v1alpha1/nautobot_types.go similarity index 76% rename from go/rax/api/v1alpha1/nautobot_types.go rename to go/nautobotop/api/v1alpha1/nautobot_types.go index 27c2e1f43..25c7bbeb5 100644 --- a/go/rax/api/v1alpha1/nautobot_types.go +++ b/go/nautobotop/api/v1alpha1/nautobot_types.go @@ -25,25 +25,23 @@ import ( // NautobotSpec defines the desired state of Nautobot. type NautobotSpec struct { - RepoWatcher string `json:"repoWatcher"` - ConfigFilePath string `json:"configFilePath"` // +kubebuilder:default=10 - SyncIntervalSeconds int `json:"syncIntervalSeconds,omitempty"` - Secrets []Secret `json:"secrets,omitempty"` + SyncIntervalSeconds int `json:"syncIntervalSeconds,omitempty"` + NautobotSecretRef SecretKeySelector `json:"nautobotSecretRef,omitempty"` + DeviceTypesRef []ConfigMapRef `json:"deviceTypeRef,omitempty"` } // NautobotStatus defines the observed state of Nautobot. type NautobotStatus struct { - ConfigFileSHA string `json:"configFileSHA,omitempty"` - GitCommitHash string `json:"gitCommitHash,omitempty"` - LastSyncedAt metav1.Time `json:"lastSyncedAt,omitempty"` - Ready bool `json:"ready,omitempty"` - Message string `json:"message,omitempty"` + LastSyncedAt metav1.Time `json:"lastSyncedAt,omitempty"` + Ready bool `json:"ready,omitempty"` + Message string `json:"message,omitempty"` + NautobotStatusReport map[string][]string `json:"nautobotStatusReport,omitempty"` } // +kubebuilder:object:root=true -// +kubebuilder:resource:scope=Cluster // +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster // Nautobot is the Schema for the nautobots API. type Nautobot struct { diff --git a/go/rax/api/v1alpha1/secret_type.go b/go/nautobotop/api/v1alpha1/secret_type.go similarity index 78% rename from go/rax/api/v1alpha1/secret_type.go rename to go/nautobotop/api/v1alpha1/secret_type.go index 41355bb90..be8fbf744 100644 --- a/go/rax/api/v1alpha1/secret_type.go +++ b/go/nautobotop/api/v1alpha1/secret_type.go @@ -1,15 +1,5 @@ package v1alpha1 -type Secret struct { - // Name of this secret in templates - Name string `json:"name"` - - // Secret ref to fill in credentials - SecretRef SecretKeySelector `json:"secretRef"` -} - -// A reference to a specific 'key' within a Secret resource. -// In some instances, `key` is a required field. type SecretKeySelector struct { // The name of the Secret resource being referred to. // +kubebuilder:validation:MinLength:=1 diff --git a/go/rax/api/v1alpha1/zz_generated.deepcopy.go b/go/nautobotop/api/v1alpha1/zz_generated.deepcopy.go similarity index 60% rename from go/rax/api/v1alpha1/zz_generated.deepcopy.go rename to go/nautobotop/api/v1alpha1/zz_generated.deepcopy.go index a20762de6..fafd4cf70 100644 --- a/go/rax/api/v1alpha1/zz_generated.deepcopy.go +++ b/go/nautobotop/api/v1alpha1/zz_generated.deepcopy.go @@ -25,98 +25,37 @@ import ( ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GitRepoWatcher) DeepCopyInto(out *GitRepoWatcher) { +func (in *ConfigMapKeySelector) DeepCopyInto(out *ConfigMapKeySelector) { *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepoWatcher. -func (in *GitRepoWatcher) DeepCopy() *GitRepoWatcher { - if in == nil { - return nil - } - out := new(GitRepoWatcher) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GitRepoWatcher) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GitRepoWatcherList) DeepCopyInto(out *GitRepoWatcherList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]GitRepoWatcher, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepoWatcherList. -func (in *GitRepoWatcherList) DeepCopy() *GitRepoWatcherList { - if in == nil { - return nil - } - out := new(GitRepoWatcherList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GitRepoWatcherList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GitRepoWatcherSpec) DeepCopyInto(out *GitRepoWatcherSpec) { - *out = *in - if in.Secrets != nil { - in, out := &in.Secrets, &out.Secrets - *out = make([]Secret, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } + if in.Namespace != nil { + in, out := &in.Namespace, &out.Namespace + *out = new(string) + **out = **in } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepoWatcherSpec. -func (in *GitRepoWatcherSpec) DeepCopy() *GitRepoWatcherSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapKeySelector. +func (in *ConfigMapKeySelector) DeepCopy() *ConfigMapKeySelector { if in == nil { return nil } - out := new(GitRepoWatcherSpec) + out := new(ConfigMapKeySelector) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GitRepoWatcherStatus) DeepCopyInto(out *GitRepoWatcherStatus) { +func (in *ConfigMapRef) DeepCopyInto(out *ConfigMapRef) { *out = *in - in.LastSyncedAt.DeepCopyInto(&out.LastSyncedAt) + in.ConfigMapSelector.DeepCopyInto(&out.ConfigMapSelector) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepoWatcherStatus. -func (in *GitRepoWatcherStatus) DeepCopy() *GitRepoWatcherStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapRef. +func (in *ConfigMapRef) DeepCopy() *ConfigMapRef { if in == nil { return nil } - out := new(GitRepoWatcherStatus) + out := new(ConfigMapRef) in.DeepCopyInto(out) return out } @@ -183,9 +122,10 @@ func (in *NautobotList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NautobotSpec) DeepCopyInto(out *NautobotSpec) { *out = *in - if in.Secrets != nil { - in, out := &in.Secrets, &out.Secrets - *out = make([]Secret, len(*in)) + in.NautobotSecretRef.DeepCopyInto(&out.NautobotSecretRef) + if in.DeviceTypesRef != nil { + in, out := &in.DeviceTypesRef, &out.DeviceTypesRef + *out = make([]ConfigMapRef, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -206,6 +146,22 @@ func (in *NautobotSpec) DeepCopy() *NautobotSpec { func (in *NautobotStatus) DeepCopyInto(out *NautobotStatus) { *out = *in in.LastSyncedAt.DeepCopyInto(&out.LastSyncedAt) + if in.NautobotStatusReport != nil { + in, out := &in.NautobotStatusReport, &out.NautobotStatusReport + *out = make(map[string][]string, len(*in)) + for key, val := range *in { + var outVal []string + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = make([]string, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NautobotStatus. @@ -218,22 +174,6 @@ func (in *NautobotStatus) DeepCopy() *NautobotStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Secret) DeepCopyInto(out *Secret) { - *out = *in - in.SecretRef.DeepCopyInto(&out.SecretRef) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Secret. -func (in *Secret) DeepCopy() *Secret { - if in == nil { - return nil - } - out := new(Secret) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) { *out = *in diff --git a/go/nautobotop/bundle/manifests/nautobotop.clusterserviceversion.yaml b/go/nautobotop/bundle/manifests/nautobotop.clusterserviceversion.yaml new file mode 100644 index 000000000..4cb85de07 --- /dev/null +++ b/go/nautobotop/bundle/manifests/nautobotop.clusterserviceversion.yaml @@ -0,0 +1,42 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + alm-examples: '[]' + capabilities: Basic Install + createdAt: "2025-11-27T12:46:15Z" + operators.operatorframework.io/builder: operator-sdk-v1.42.0 + operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 + name: nautobotop.v0.0.1 + namespace: placeholder +spec: + apiservicedefinitions: {} + customresourcedefinitions: {} + description: d + displayName: d + icon: + - base64data: "" + mediatype: "" + install: + spec: + deployments: [] + strategy: deployment + installModes: + - supported: false + type: OwnNamespace + - supported: false + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: true + type: AllNamespaces + keywords: + - d + links: + - name: Nautobotop + url: https://nautobotop.domain + maturity: alpha + provider: + name: d + url: d + version: 0.0.1 diff --git a/go/rax/cmd/main.go b/go/nautobotop/cmd/main.go similarity index 93% rename from go/rax/cmd/main.go rename to go/nautobotop/cmd/main.go index d163c8c85..180602207 100644 --- a/go/rax/cmd/main.go +++ b/go/nautobotop/cmd/main.go @@ -37,8 +37,8 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" - syncv1alpha1 "github.com/rackerlabs/understack/go/sync/api/v1alpha1" - "github.com/rackerlabs/understack/go/sync/internal/controller" + syncv1alpha1 "github.com/rackerlabs/understack/go/nautobotop/api/v1alpha1" + "github.com/rackerlabs/understack/go/nautobotop/internal/controller" // +kubebuilder:scaffold:imports ) @@ -135,7 +135,7 @@ func main() { // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. // More info: - // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/server + // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/metrics/server // - https://book.kubebuilder.io/reference/metrics.html metricsServerOptions := metricsserver.Options{ BindAddress: metricsAddr, @@ -147,7 +147,7 @@ func main() { // FilterProvider is used to protect the metrics endpoint with authn/authz. // These configurations ensure that only authorized users and service accounts // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: - // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/filters#WithAuthenticationAndAuthorization + // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/metrics/filters#WithAuthenticationAndAuthorization metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization } @@ -184,7 +184,7 @@ func main() { WebhookServer: webhookServer, HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, - LeaderElectionID: "f2ce84ca.rax.io", + LeaderElectionID: "6b30b50c.rax.io", // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily // when the Manager ends. This requires the binary to immediately end when the // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly @@ -202,20 +202,13 @@ func main() { os.Exit(1) } - if err = (&controller.NautobotReconciler{ + if err := (&controller.NautobotReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Nautobot") os.Exit(1) } - if err = (&controller.GitRepoWatcherReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "GitRepoWatcher") - os.Exit(1) - } // +kubebuilder:scaffold:builder if metricsCertWatcher != nil { diff --git a/go/rax/config/crd/bases/sync.rax.io_nautobots.yaml b/go/nautobotop/config/crd/bases/sync.rax.io_nautobots.yaml similarity index 55% rename from go/rax/config/crd/bases/sync.rax.io_nautobots.yaml rename to go/nautobotop/config/crd/bases/sync.rax.io_nautobots.yaml index 5267442a2..9dc179273 100644 --- a/go/rax/config/crd/bases/sync.rax.io_nautobots.yaml +++ b/go/nautobotop/config/crd/bases/sync.rax.io_nautobots.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.18.0 name: nautobots.sync.rax.io spec: group: sync.rax.io @@ -39,67 +39,78 @@ spec: spec: description: NautobotSpec defines the desired state of Nautobot. properties: - configFilePath: - type: string - repoWatcher: - type: string - secrets: + deviceTypeRef: items: + description: ConfigMapRef defines a reference to a specific ConfigMap properties: - name: - description: Name of this secret in templates - type: string - secretRef: - description: Secret ref to fill in credentials + configMapSelector: + description: The name of the ConfigMap resource being referred + to properties: key: - description: |- - A key in the referenced Secret. - Some instances of this field may be defaulted, in others it may be required. - maxLength: 253 - minLength: 1 - pattern: ^[-._a-zA-Z0-9]+$ + description: The key in the ConfigMap data type: string name: - description: The name of the Secret resource being referred - to. - maxLength: 253 + description: The name of the ConfigMap minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string namespace: - description: |- - The namespace of the Secret resource being referred to. - Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent. - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + description: The namespace where the ConfigMap resides type: string + required: + - name type: object + name: + description: Name of this config set (logical name) + type: string required: + - configMapSelector - name - - secretRef type: object type: array + nautobotSecretRef: + properties: + key: + description: |- + A key in the referenced Secret. + Some instances of this field may be defaulted, in others it may be required. + maxLength: 253 + minLength: 1 + pattern: ^[-._a-zA-Z0-9]+$ + type: string + name: + description: The name of the Secret resource being referred to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + namespace: + description: |- + The namespace of the Secret resource being referred to. + Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object syncIntervalSeconds: default: 10 type: integer - required: - - configFilePath - - repoWatcher type: object status: description: NautobotStatus defines the observed state of Nautobot. properties: - configFileSHA: - type: string - gitCommitHash: - type: string lastSyncedAt: format: date-time type: string message: type: string + nautobotStatusReport: + additionalProperties: + items: + type: string + type: array + type: object ready: type: boolean type: object diff --git a/go/nautobotop/config/crd/kustomization.yaml b/go/nautobotop/config/crd/kustomization.yaml new file mode 100644 index 000000000..819e34fb1 --- /dev/null +++ b/go/nautobotop/config/crd/kustomization.yaml @@ -0,0 +1,16 @@ +# This kustomization.yaml is not intended to be run by itself, +# since it depends on service name and namespace that are out of this kustomize package. +# It should be run by config/default +resources: +- bases/sync.rax.io_nautobots.yaml +# +kubebuilder:scaffold:crdkustomizeresource + +patches: +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. +# patches here are for enabling the conversion webhook for each CRD +# +kubebuilder:scaffold:crdkustomizewebhookpatch + +# [WEBHOOK] To enable webhook, uncomment the following section +# the following config is for teaching kustomize how to do kustomization for CRDs. +#configurations: +#- kustomizeconfig.yaml diff --git a/go/rax/config/crd/kustomizeconfig.yaml b/go/nautobotop/config/crd/kustomizeconfig.yaml similarity index 100% rename from go/rax/config/crd/kustomizeconfig.yaml rename to go/nautobotop/config/crd/kustomizeconfig.yaml diff --git a/go/rax/config/default/cert_metrics_manager_patch.yaml b/go/nautobotop/config/default/cert_metrics_manager_patch.yaml similarity index 100% rename from go/rax/config/default/cert_metrics_manager_patch.yaml rename to go/nautobotop/config/default/cert_metrics_manager_patch.yaml diff --git a/go/rax/config/default/kustomization.yaml b/go/nautobotop/config/default/kustomization.yaml similarity index 99% rename from go/rax/config/default/kustomization.yaml rename to go/nautobotop/config/default/kustomization.yaml index f688aee19..d3ac0ee06 100644 --- a/go/rax/config/default/kustomization.yaml +++ b/go/nautobotop/config/default/kustomization.yaml @@ -1,12 +1,12 @@ # Adds namespace to all resources. -namespace: rax-system +namespace: nautobotop-system # Value of this field is prepended to the # names of all resources, e.g. a deployment named # "wordpress" becomes "alices-wordpress". # Note that it should also match with the prefix (text before '-') of the namespace # field above. -namePrefix: rax- +namePrefix: nautobotop- # Labels to add to all resources and selectors. #labels: diff --git a/go/rax/config/default/manager_metrics_patch.yaml b/go/nautobotop/config/default/manager_metrics_patch.yaml similarity index 100% rename from go/rax/config/default/manager_metrics_patch.yaml rename to go/nautobotop/config/default/manager_metrics_patch.yaml diff --git a/go/rax/config/default/metrics_service.yaml b/go/nautobotop/config/default/metrics_service.yaml similarity index 80% rename from go/rax/config/default/metrics_service.yaml rename to go/nautobotop/config/default/metrics_service.yaml index 8a898307f..4985974fc 100644 --- a/go/rax/config/default/metrics_service.yaml +++ b/go/nautobotop/config/default/metrics_service.yaml @@ -3,7 +3,7 @@ kind: Service metadata: labels: control-plane: controller-manager - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop app.kubernetes.io/managed-by: kustomize name: controller-manager-metrics-service namespace: system @@ -15,4 +15,4 @@ spec: targetPort: 8443 selector: control-plane: controller-manager - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop diff --git a/go/rax/config/manager/kustomization.yaml b/go/nautobotop/config/manager/kustomization.yaml similarity index 100% rename from go/rax/config/manager/kustomization.yaml rename to go/nautobotop/config/manager/kustomization.yaml diff --git a/go/rax/config/manager/manager.yaml b/go/nautobotop/config/manager/manager.yaml similarity index 94% rename from go/rax/config/manager/manager.yaml rename to go/nautobotop/config/manager/manager.yaml index 92e37b093..37bbfe14c 100644 --- a/go/rax/config/manager/manager.yaml +++ b/go/nautobotop/config/manager/manager.yaml @@ -3,7 +3,7 @@ kind: Namespace metadata: labels: control-plane: controller-manager - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop app.kubernetes.io/managed-by: kustomize name: system --- @@ -14,13 +14,13 @@ metadata: namespace: system labels: control-plane: controller-manager - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop app.kubernetes.io/managed-by: kustomize spec: selector: matchLabels: control-plane: controller-manager - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop replicas: 1 template: metadata: @@ -28,7 +28,7 @@ spec: kubectl.kubernetes.io/default-container: manager labels: control-plane: controller-manager - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop spec: # TODO(user): Uncomment the following code to configure the nodeAffinity expression # according to the platforms which are supported by your solution. diff --git a/go/nautobotop/config/manifests/bases/nautobotop.clusterserviceversion.yaml b/go/nautobotop/config/manifests/bases/nautobotop.clusterserviceversion.yaml new file mode 100644 index 000000000..303dd97d9 --- /dev/null +++ b/go/nautobotop/config/manifests/bases/nautobotop.clusterserviceversion.yaml @@ -0,0 +1,45 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + alm-examples: '[]' + capabilities: Basic Install + name: nautobotop.v0.0.0 + namespace: placeholder +spec: + apiservicedefinitions: {} + customresourcedefinitions: + owned: + - description: Nautobot is the Schema for the nautobots API. + displayName: Nautobot + kind: Nautobot + name: nautobots.sync.rax.io + version: v1alpha1 + description: d + displayName: d + icon: + - base64data: "" + mediatype: "" + install: + spec: + deployments: null + strategy: "" + installModes: + - supported: false + type: OwnNamespace + - supported: false + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: true + type: AllNamespaces + keywords: + - d + links: + - name: Nautobotop + url: https://nautobotop.domain + maturity: alpha + provider: + name: d + url: d + version: 0.0.0 diff --git a/go/rax/config/network-policy/allow-metrics-traffic.yaml b/go/nautobotop/config/network-policy/allow-metrics-traffic.yaml similarity index 90% rename from go/rax/config/network-policy/allow-metrics-traffic.yaml rename to go/nautobotop/config/network-policy/allow-metrics-traffic.yaml index 8cc15add9..2d0d6b184 100644 --- a/go/rax/config/network-policy/allow-metrics-traffic.yaml +++ b/go/nautobotop/config/network-policy/allow-metrics-traffic.yaml @@ -5,7 +5,7 @@ apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: labels: - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop app.kubernetes.io/managed-by: kustomize name: allow-metrics-traffic namespace: system @@ -13,7 +13,7 @@ spec: podSelector: matchLabels: control-plane: controller-manager - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop policyTypes: - Ingress ingress: diff --git a/go/rax/config/network-policy/kustomization.yaml b/go/nautobotop/config/network-policy/kustomization.yaml similarity index 100% rename from go/rax/config/network-policy/kustomization.yaml rename to go/nautobotop/config/network-policy/kustomization.yaml diff --git a/go/rax/config/prometheus/kustomization.yaml b/go/nautobotop/config/prometheus/kustomization.yaml similarity index 100% rename from go/rax/config/prometheus/kustomization.yaml rename to go/nautobotop/config/prometheus/kustomization.yaml diff --git a/go/rax/config/prometheus/monitor.yaml b/go/nautobotop/config/prometheus/monitor.yaml similarity index 93% rename from go/rax/config/prometheus/monitor.yaml rename to go/nautobotop/config/prometheus/monitor.yaml index c998f13d1..34b7f1beb 100644 --- a/go/rax/config/prometheus/monitor.yaml +++ b/go/nautobotop/config/prometheus/monitor.yaml @@ -4,7 +4,7 @@ kind: ServiceMonitor metadata: labels: control-plane: controller-manager - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop app.kubernetes.io/managed-by: kustomize name: controller-manager-metrics-monitor namespace: system @@ -24,4 +24,4 @@ spec: selector: matchLabels: control-plane: controller-manager - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop diff --git a/go/rax/config/prometheus/monitor_tls_patch.yaml b/go/nautobotop/config/prometheus/monitor_tls_patch.yaml similarity index 100% rename from go/rax/config/prometheus/monitor_tls_patch.yaml rename to go/nautobotop/config/prometheus/monitor_tls_patch.yaml diff --git a/go/rax/config/rbac/kustomization.yaml b/go/nautobotop/config/rbac/kustomization.yaml similarity index 86% rename from go/rax/config/rbac/kustomization.yaml rename to go/nautobotop/config/rbac/kustomization.yaml index 7db020294..f33a339ae 100644 --- a/go/rax/config/rbac/kustomization.yaml +++ b/go/nautobotop/config/rbac/kustomization.yaml @@ -20,11 +20,8 @@ resources: - metrics_reader_role.yaml # For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by # default, aiding admins in cluster management. Those roles are -# not used by the {{ .ProjectName }} itself. You can comment the following lines +# not used by the nautobotop itself. You can comment the following lines # if you do not want those helpers be installed with your Project. -- gitrepowatcher_admin_role.yaml -- gitrepowatcher_editor_role.yaml -- gitrepowatcher_viewer_role.yaml - nautobot_admin_role.yaml - nautobot_editor_role.yaml - nautobot_viewer_role.yaml diff --git a/go/rax/config/rbac/leader_election_role.yaml b/go/nautobotop/config/rbac/leader_election_role.yaml similarity index 93% rename from go/rax/config/rbac/leader_election_role.yaml rename to go/nautobotop/config/rbac/leader_election_role.yaml index a94d4ec73..144318b41 100644 --- a/go/rax/config/rbac/leader_election_role.yaml +++ b/go/nautobotop/config/rbac/leader_election_role.yaml @@ -3,7 +3,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: labels: - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop app.kubernetes.io/managed-by: kustomize name: leader-election-role rules: diff --git a/go/rax/config/rbac/leader_election_role_binding.yaml b/go/nautobotop/config/rbac/leader_election_role_binding.yaml similarity index 89% rename from go/rax/config/rbac/leader_election_role_binding.yaml rename to go/nautobotop/config/rbac/leader_election_role_binding.yaml index d7b380760..1fc0ad22b 100644 --- a/go/rax/config/rbac/leader_election_role_binding.yaml +++ b/go/nautobotop/config/rbac/leader_election_role_binding.yaml @@ -2,7 +2,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop app.kubernetes.io/managed-by: kustomize name: leader-election-rolebinding roleRef: diff --git a/go/rax/config/rbac/metrics_auth_role.yaml b/go/nautobotop/config/rbac/metrics_auth_role.yaml similarity index 100% rename from go/rax/config/rbac/metrics_auth_role.yaml rename to go/nautobotop/config/rbac/metrics_auth_role.yaml diff --git a/go/rax/config/rbac/metrics_auth_role_binding.yaml b/go/nautobotop/config/rbac/metrics_auth_role_binding.yaml similarity index 100% rename from go/rax/config/rbac/metrics_auth_role_binding.yaml rename to go/nautobotop/config/rbac/metrics_auth_role_binding.yaml diff --git a/go/rax/config/rbac/metrics_reader_role.yaml b/go/nautobotop/config/rbac/metrics_reader_role.yaml similarity index 100% rename from go/rax/config/rbac/metrics_reader_role.yaml rename to go/nautobotop/config/rbac/metrics_reader_role.yaml diff --git a/go/rax/config/rbac/nautobot_admin_role.yaml b/go/nautobotop/config/rbac/nautobot_admin_role.yaml similarity index 86% rename from go/rax/config/rbac/nautobot_admin_role.yaml rename to go/nautobotop/config/rbac/nautobot_admin_role.yaml index ac458b022..761679471 100644 --- a/go/rax/config/rbac/nautobot_admin_role.yaml +++ b/go/nautobotop/config/rbac/nautobot_admin_role.yaml @@ -1,4 +1,4 @@ -# This rule is not used by the project rax itself. +# This rule is not used by the project nautobotop itself. # It is provided to allow the cluster admin to help manage permissions for users. # # Grants full permissions ('*') over sync.rax.io. @@ -9,7 +9,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop app.kubernetes.io/managed-by: kustomize name: nautobot-admin-role rules: diff --git a/go/rax/config/rbac/nautobot_editor_role.yaml b/go/nautobotop/config/rbac/nautobot_editor_role.yaml similarity index 87% rename from go/rax/config/rbac/nautobot_editor_role.yaml rename to go/nautobotop/config/rbac/nautobot_editor_role.yaml index 5ff4a68cd..8838e50d5 100644 --- a/go/rax/config/rbac/nautobot_editor_role.yaml +++ b/go/nautobotop/config/rbac/nautobot_editor_role.yaml @@ -1,4 +1,4 @@ -# This rule is not used by the project rax itself. +# This rule is not used by the project nautobotop itself. # It is provided to allow the cluster admin to help manage permissions for users. # # Grants permissions to create, update, and delete resources within the sync.rax.io. @@ -9,7 +9,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop app.kubernetes.io/managed-by: kustomize name: nautobot-editor-role rules: diff --git a/go/rax/config/rbac/nautobot_viewer_role.yaml b/go/nautobotop/config/rbac/nautobot_viewer_role.yaml similarity index 86% rename from go/rax/config/rbac/nautobot_viewer_role.yaml rename to go/nautobotop/config/rbac/nautobot_viewer_role.yaml index 173c84e79..258bde588 100644 --- a/go/rax/config/rbac/nautobot_viewer_role.yaml +++ b/go/nautobotop/config/rbac/nautobot_viewer_role.yaml @@ -1,4 +1,4 @@ -# This rule is not used by the project rax itself. +# This rule is not used by the project nautobotop itself. # It is provided to allow the cluster admin to help manage permissions for users. # # Grants read-only access to sync.rax.io resources. @@ -9,7 +9,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop app.kubernetes.io/managed-by: kustomize name: nautobot-viewer-role rules: diff --git a/go/rax/config/rbac/role.yaml b/go/nautobotop/config/rbac/role.yaml similarity index 84% rename from go/rax/config/rbac/role.yaml rename to go/nautobotop/config/rbac/role.yaml index c021cc166..6867df5e8 100644 --- a/go/rax/config/rbac/role.yaml +++ b/go/nautobotop/config/rbac/role.yaml @@ -7,7 +7,6 @@ rules: - apiGroups: - sync.rax.io resources: - - gitrepowatchers - nautobots verbs: - create @@ -20,14 +19,12 @@ rules: - apiGroups: - sync.rax.io resources: - - gitrepowatchers/finalizers - nautobots/finalizers verbs: - update - apiGroups: - sync.rax.io resources: - - gitrepowatchers/status - nautobots/status verbs: - get diff --git a/go/rax/config/rbac/role_binding.yaml b/go/nautobotop/config/rbac/role_binding.yaml similarity index 89% rename from go/rax/config/rbac/role_binding.yaml rename to go/nautobotop/config/rbac/role_binding.yaml index 2f40cc976..b0fb45dd2 100644 --- a/go/rax/config/rbac/role_binding.yaml +++ b/go/nautobotop/config/rbac/role_binding.yaml @@ -2,7 +2,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: labels: - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop app.kubernetes.io/managed-by: kustomize name: manager-rolebinding roleRef: diff --git a/go/rax/config/rbac/service_account.yaml b/go/nautobotop/config/rbac/service_account.yaml similarity index 79% rename from go/rax/config/rbac/service_account.yaml rename to go/nautobotop/config/rbac/service_account.yaml index 5148b0c0a..747a024fd 100644 --- a/go/rax/config/rbac/service_account.yaml +++ b/go/nautobotop/config/rbac/service_account.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: ServiceAccount metadata: labels: - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop app.kubernetes.io/managed-by: kustomize name: controller-manager namespace: system diff --git a/go/rax/config/samples/kustomization.yaml b/go/nautobotop/config/samples/kustomization.yaml similarity index 78% rename from go/rax/config/samples/kustomization.yaml rename to go/nautobotop/config/samples/kustomization.yaml index aa8ecc6f8..e77b3f292 100644 --- a/go/rax/config/samples/kustomization.yaml +++ b/go/nautobotop/config/samples/kustomization.yaml @@ -1,5 +1,4 @@ ## Append samples of your project ## resources: - sync_v1alpha1_nautobot.yaml -- sync_v1alpha1_gitrepowatcher.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/go/rax/config/samples/sync_v1alpha1_nautobot.yaml b/go/nautobotop/config/samples/sync_v1alpha1_nautobot.yaml similarity index 81% rename from go/rax/config/samples/sync_v1alpha1_nautobot.yaml rename to go/nautobotop/config/samples/sync_v1alpha1_nautobot.yaml index 2c8da62ee..3e803998b 100644 --- a/go/rax/config/samples/sync_v1alpha1_nautobot.yaml +++ b/go/nautobotop/config/samples/sync_v1alpha1_nautobot.yaml @@ -2,7 +2,7 @@ apiVersion: sync.rax.io/v1alpha1 kind: Nautobot metadata: labels: - app.kubernetes.io/name: rax + app.kubernetes.io/name: nautobotop app.kubernetes.io/managed-by: kustomize name: nautobot-sample spec: diff --git a/go/nautobotop/go.mod b/go/nautobotop/go.mod new file mode 100644 index 000000000..9464b347f --- /dev/null +++ b/go/nautobotop/go.mod @@ -0,0 +1,115 @@ +module github.com/rackerlabs/understack/go/nautobotop + +go 1.24.0 + +require ( + github.com/charmbracelet/log v0.4.2 + github.com/nautobot/go-nautobot/v2 v2.3.2-beta + github.com/onsi/ginkgo/v2 v2.22.0 + github.com/onsi/gomega v1.36.1 + github.com/samber/lo v1.52.0 + go.yaml.in/yaml/v3 v3.0.4 + k8s.io/api v0.33.0 + k8s.io/apimachinery v0.33.0 + k8s.io/client-go v0.33.0 + sigs.k8s.io/controller-runtime v0.21.0 +) + +require ( + cel.dev/expr v0.19.1 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/x/ansi v0.8.0 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + 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 v5.9.11 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/cel-go v0.23.2 // indirect + github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect + go.opentelemetry.io/otel v1.33.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 // indirect + go.opentelemetry.io/otel/metric v1.33.0 // indirect + go.opentelemetry.io/otel/sdk v1.33.0 // indirect + go.opentelemetry.io/otel/trace v1.33.0 // indirect + go.opentelemetry.io/proto/otlp v1.4.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.26.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/grpc v1.68.1 // indirect + google.golang.org/protobuf v1.36.5 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/validator.v2 v2.0.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.33.0 // indirect + k8s.io/apiserver v0.33.0 // indirect + k8s.io/component-base v0.33.0 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/go/rax/go.sum b/go/nautobotop/go.sum similarity index 50% rename from go/rax/go.sum rename to go/nautobotop/go.sum index 5379e5e25..b41f5e2d5 100644 --- a/go/rax/go.sum +++ b/go/nautobotop/go.sum @@ -1,22 +1,9 @@ -cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= -cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= -github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= +cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -25,22 +12,25 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= +github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= +github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= +github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= -github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 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/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/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= -github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= 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/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= @@ -51,21 +41,11 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= -github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= -github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM= -github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= -github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= -github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +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/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -82,49 +62,36 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g= -github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/cel-go v0.23.2 h1:UdEe3CvQh3Nv+E/j9r1Y//WO0K0cSyD7/y0bzyLIMI4= +github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg= -github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA= -github.com/google/go-github/v74 v74.0.0/go.mod h1:ubn/YdyftV80VPSI26nSJvaEsTOnsjrxG3o9kJhcyak= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= -github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -132,46 +99,51 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +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/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nautobot/go-nautobot/v2 v2.3.2-beta h1:w3sDcyt5CDSSWkKWUow08loxo6o72WjoDyclwkllotQ= github.com/nautobot/go-nautobot/v2 v2.3.2-beta/go.mod h1:T0emSo3w4TabGb4Wwb1o3NOEgbaVL1MLMDI9rFWaxtg= -github.com/onsi/ginkgo/v2 v2.25.2 h1:hepmgwx1D+llZleKQDMEvy8vIlCxMGt7W5ZxDjIEhsw= -github.com/onsi/ginkgo/v2 v2.25.2/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= -github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= -github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= -github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= -github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= +github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= 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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= 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.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= -github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -181,9 +153,9 @@ github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8w github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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= @@ -191,28 +163,28 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= -github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= -go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= +go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= +go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA= +go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= +go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= +go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= +go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= +go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= +go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= +go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= +go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -224,9 +196,6 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 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-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -235,59 +204,50 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn 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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 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.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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-20191026070338-33540a1f6037/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 h1:YcyjlL1PRr2Q17/I0dPk2JmYS5CDXfcdb2Z3YRioEbw= -google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= +google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= @@ -296,39 +256,37 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY= gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -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= -k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc= -k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k= -k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= -k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= -k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs= -k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/apiserver v0.32.1 h1:oo0OozRos66WFq87Zc5tclUX2r0mymoVHRq8JmR7Aak= -k8s.io/apiserver v0.32.1/go.mod h1:UcB9tWjBY7aryeI5zAgzVJB/6k7E97bkr1RgqDz0jPw= -k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU= -k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg= -k8s.io/component-base v0.32.1 h1:/5IfJ0dHIKBWysGV0yKTFfacZ5yNV1sulPh3ilJjRZk= -k8s.io/component-base v0.32.1/go.mod h1:j1iMMHi/sqAHeG5z+O9BFNCF698a1u0186zkjMZQ28w= +k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU= +k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM= +k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs= +k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc= +k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= +k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.0 h1:QqcM6c+qEEjkOODHppFXRiw/cE2zP85704YrQ9YaBbc= +k8s.io/apiserver v0.33.0/go.mod h1:EixYOit0YTxt8zrO2kBU7ixAtxFce9gKGq367nFmqI8= +k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98= +k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg= +k8s.io/component-base v0.33.0 h1:Ot4PyJI+0JAD9covDhwLp9UNkUja209OzsJ4FzScBNk= +k8s.io/component-base v0.33.0/go.mod h1:aXYZLbw3kihdkOPMDhWbjGCO6sg+luw554KP51t8qCU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= -sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= +sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= +sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/go/rax/hack/boilerplate.go.txt b/go/nautobotop/hack/boilerplate.go.txt similarity index 100% rename from go/rax/hack/boilerplate.go.txt rename to go/nautobotop/hack/boilerplate.go.txt diff --git a/go/nautobotop/helm/.helmignore b/go/nautobotop/helm/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/go/nautobotop/helm/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/go/nautobotop/helm/Chart.yaml b/go/nautobotop/helm/Chart.yaml new file mode 100644 index 000000000..1073964d2 --- /dev/null +++ b/go/nautobotop/helm/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: nautobotop +description: kubernetes operator to sync data in nautobot + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "v0.18.0" diff --git a/go/nautobotop/helm/crds/clients.yaml b/go/nautobotop/helm/crds/clients.yaml new file mode 100644 index 000000000..90f1f7c5a --- /dev/null +++ b/go/nautobotop/helm/crds/clients.yaml @@ -0,0 +1,121 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: nautobots.sync.rax.io +spec: + group: sync.rax.io + names: + kind: Nautobot + listKind: NautobotList + plural: nautobots + singular: nautobot + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Nautobot is the Schema for the nautobots API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NautobotSpec defines the desired state of Nautobot. + properties: + deviceTypeRef: + items: + description: ConfigMapRef defines a reference to a specific ConfigMap + properties: + configMapSelector: + description: The name of the ConfigMap resource being referred + to + properties: + key: + description: The key in the ConfigMap data + type: string + name: + description: The name of the ConfigMap + minLength: 1 + type: string + namespace: + description: The namespace where the ConfigMap resides + type: string + required: + - name + type: object + name: + description: Name of this config set (logical name) + type: string + required: + - configMapSelector + - name + type: object + type: array + nautobotSecretRef: + properties: + key: + description: |- + A key in the referenced Secret. + Some instances of this field may be defaulted, in others it may be required. + maxLength: 253 + minLength: 1 + pattern: ^[-._a-zA-Z0-9]+$ + type: string + name: + description: The name of the Secret resource being referred to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + namespace: + description: |- + The namespace of the Secret resource being referred to. + Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + type: object + syncIntervalSeconds: + default: 10 + type: integer + type: object + status: + description: NautobotStatus defines the observed state of Nautobot. + properties: + lastSyncedAt: + format: date-time + type: string + message: + type: string + nautobotStatusReport: + additionalProperties: + items: + type: string + type: array + type: object + ready: + type: boolean + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/go/nautobotop/helm/templates/NOTES.txt b/go/nautobotop/helm/templates/NOTES.txt new file mode 100644 index 000000000..6ecaaf080 --- /dev/null +++ b/go/nautobotop/helm/templates/NOTES.txt @@ -0,0 +1,35 @@ +1. Get the application URL by running these commands: +{{- if .Values.httpRoute.enabled }} +{{- if .Values.httpRoute.hostnames }} + export APP_HOSTNAME={{ .Values.httpRoute.hostnames | first }} +{{- else }} + export APP_HOSTNAME=$(kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o jsonpath="{.spec.listeners[0].hostname}") + {{- end }} +{{- if and .Values.httpRoute.rules (first .Values.httpRoute.rules).matches (first (first .Values.httpRoute.rules).matches).path.value }} + echo "Visit http://$APP_HOSTNAME{{ (first (first .Values.httpRoute.rules).matches).path.value }} to use your application" + + NOTE: Your HTTPRoute depends on the listener configuration of your gateway and your HTTPRoute rules. + The rules can be set for path, method, header and query parameters. + You can check the gateway configuration with 'kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o yaml' +{{- end }} +{{- else if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "nautobotop.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "nautobotop.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "nautobotop.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "nautobotop.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/go/nautobotop/helm/templates/_helpers.tpl b/go/nautobotop/helm/templates/_helpers.tpl new file mode 100644 index 000000000..4897d53e8 --- /dev/null +++ b/go/nautobotop/helm/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "nautobotop.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "nautobotop.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "nautobotop.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "nautobotop.labels" -}} +helm.sh/chart: {{ include "nautobotop.chart" . }} +{{ include "nautobotop.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "nautobotop.selectorLabels" -}} +app.kubernetes.io/name: {{ include "nautobotop.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "nautobotop.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "nautobotop.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/go/nautobotop/helm/templates/clusterrolebinding.yaml.tpl b/go/nautobotop/helm/templates/clusterrolebinding.yaml.tpl new file mode 100644 index 000000000..41a9d310e --- /dev/null +++ b/go/nautobotop/helm/templates/clusterrolebinding.yaml.tpl @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "nautobotop.fullname" . }}-clusterrolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "nautobotop.fullname" . }}-clusterrole +subjects: +- kind: ServiceAccount + name: {{ include "nautobotop.fullname" . | trunc 63 | trimSuffix "-" }} + namespace: {{ .Release.Namespace }} \ No newline at end of file diff --git a/go/nautobotop/helm/templates/clusterroles.yaml.tpl b/go/nautobotop/helm/templates/clusterroles.yaml.tpl new file mode 100644 index 000000000..0d0c08423 --- /dev/null +++ b/go/nautobotop/helm/templates/clusterroles.yaml.tpl @@ -0,0 +1,39 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "nautobotop.fullname" . }}-clusterrole +rules: +- apiGroups: + - sync.rax.io + resources: + - nautobots + - nautobots/status + - nautobots/finalizers + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: [""] + resources: + - pods + - services + - configmaps + - secrets + - ingress + - endpoints + verbs: + - get + - list + - watch +- apiGroups: + - apps + resources: + - deployments + verbs: + - get + - list + - watch diff --git a/go/nautobotop/helm/templates/deployment.yaml.tpl b/go/nautobotop/helm/templates/deployment.yaml.tpl new file mode 100644 index 000000000..91187d649 --- /dev/null +++ b/go/nautobotop/helm/templates/deployment.yaml.tpl @@ -0,0 +1,78 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "nautobotop.fullname" . }} + labels: + {{- include "nautobotop.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "nautobotop.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "nautobotop.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "nautobotop.serviceAccountName" . }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + {{- with .Values.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/go/nautobotop/helm/templates/hpa.yaml.tpl b/go/nautobotop/helm/templates/hpa.yaml.tpl new file mode 100644 index 000000000..6dbf7cb23 --- /dev/null +++ b/go/nautobotop/helm/templates/hpa.yaml.tpl @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "nautobotop.fullname" . }} + labels: + {{- include "nautobotop.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "nautobotop.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/go/nautobotop/helm/templates/httproute.yaml.tpl b/go/nautobotop/helm/templates/httproute.yaml.tpl new file mode 100644 index 000000000..e6a27936c --- /dev/null +++ b/go/nautobotop/helm/templates/httproute.yaml.tpl @@ -0,0 +1,38 @@ +{{- if .Values.httpRoute.enabled -}} +{{- $fullName := include "nautobotop.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ $fullName }} + labels: + {{- include "nautobotop.labels" . | nindent 4 }} + {{- with .Values.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- with .Values.httpRoute.parentRefs }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.httpRoute.hostnames }} + hostnames: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.httpRoute.rules }} + {{- with .matches }} + - matches: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .filters }} + filters: + {{- toYaml . | nindent 8 }} + {{- end }} + backendRefs: + - name: {{ $fullName }} + port: {{ $svcPort }} + weight: 1 + {{- end }} +{{- end }} diff --git a/go/nautobotop/helm/templates/ingress.yaml.tpl b/go/nautobotop/helm/templates/ingress.yaml.tpl new file mode 100644 index 000000000..52f2a0a18 --- /dev/null +++ b/go/nautobotop/helm/templates/ingress.yaml.tpl @@ -0,0 +1,43 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "nautobotop.fullname" . }} + labels: + {{- include "nautobotop.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- with .pathType }} + pathType: {{ . }} + {{- end }} + backend: + service: + name: {{ include "nautobotop.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/go/nautobotop/helm/templates/service.yaml.tpl b/go/nautobotop/helm/templates/service.yaml.tpl new file mode 100644 index 000000000..1c60fbb3e --- /dev/null +++ b/go/nautobotop/helm/templates/service.yaml.tpl @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "nautobotop.fullname" . }} + labels: + {{- include "nautobotop.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "nautobotop.selectorLabels" . | nindent 4 }} diff --git a/go/nautobotop/helm/templates/serviceaccount.yaml.tpl b/go/nautobotop/helm/templates/serviceaccount.yaml.tpl new file mode 100644 index 000000000..32f2a0200 --- /dev/null +++ b/go/nautobotop/helm/templates/serviceaccount.yaml.tpl @@ -0,0 +1,17 @@ +{{- /* +Create service account. Values already include serviceAccount.create and name. +*/ -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "nautobotop.fullname" . | trunc 63 | trimSuffix "-" }} + labels: + app.kubernetes.io/name: {{ include "nautobotop.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Values.serviceAccount.annotations }} + annotations: +{{ toYaml .Values.serviceAccount.annotations | indent 4 }} +{{- end }} +{{- if not .Values.serviceAccount.create }} +# Note: serviceAccount.create is false — expecting an existing SA with this name. +{{- end }} diff --git a/go/nautobotop/helm/values.yaml b/go/nautobotop/helm/values.yaml new file mode 100644 index 000000000..ffbaa761e --- /dev/null +++ b/go/nautobotop/helm/values.yaml @@ -0,0 +1,170 @@ +# Default values for nautobotop. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/ +replicaCount: 1 + +# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/ +image: + repository: ghcr.io/rackerlabs/understack/nautobotop + # This sets the pull policy for images. + pullPolicy: Never + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ +imagePullSecrets: [] +# This is to override the chart name. +nameOverride: "" +fullnameOverride: "" + +# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/ +serviceAccount: + # Specifies whether a service account should be created + create: true + # Automatically mount a ServiceAccount's API credentials? + automount: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +# This is for setting Kubernetes Annotations to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +podAnnotations: {} +# This is for setting Kubernetes Labels to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +podLabels: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/ +service: + # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: ClusterIP + # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports + port: 8081 + +# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +# -- Expose the service via gateway-api HTTPRoute +# Requires Gateway API resources and suitable controller installed within the cluster +# (see: https://gateway-api.sigs.k8s.io/guides/) +httpRoute: + # HTTPRoute enabled. + enabled: false + # HTTPRoute annotations. + annotations: {} + # Which Gateways this Route is attached to. + parentRefs: + - name: gateway + sectionName: http + # namespace: default + # Hostnames matching HTTP header. + hostnames: + - chart-example.local + # List of rules and filters applied. + rules: + - matches: + - path: + type: PathPrefix + value: /headers + # filters: + # - type: RequestHeaderModifier + # requestHeaderModifier: + # set: + # - name: My-Overwrite-Header + # value: this-is-the-only-value + # remove: + # - User-Agent + # - matches: + # - path: + # type: PathPrefix + # value: /echo + # headers: + # - name: version + # value: v2 + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ +livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 5 + +readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + +# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +# Additional volumes on the output Deployment definition. +volumes: [] +# - name: foo +# secret: +# secretName: mysecret +# optional: false + +# Additional volumeMounts on the output Deployment definition. +volumeMounts: [] +# - name: foo +# mountPath: "/etc/foo" +# readOnly: true + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/go/nautobotop/internal/controller/nautobot_controller.go b/go/nautobotop/internal/controller/nautobot_controller.go new file mode 100644 index 000000000..efd429d47 --- /dev/null +++ b/go/nautobotop/internal/controller/nautobot_controller.go @@ -0,0 +1,153 @@ +/* +Copyright 2025. + +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 controller + +import ( + "context" + "fmt" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/rackerlabs/understack/go/nautobotop/internal/nautobot" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + syncv1alpha1 "github.com/rackerlabs/understack/go/nautobotop/api/v1alpha1" +) + +// NautobotReconciler reconciles a Nautobot object +type NautobotReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=sync.rax.io,resources=nautobots,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=sync.rax.io,resources=nautobots/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=sync.rax.io,resources=nautobots/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Nautobot object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +func (r *NautobotReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := logf.FromContext(ctx) + var nautobotCR syncv1alpha1.Nautobot + if err := r.Get(ctx, req.NamespacedName, &nautobotCR); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + syncInterval := time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second + if syncInterval == 0 { + syncInterval = 4 * time.Hour // sane default + } + + lastSynced := nautobotCR.Status.LastSyncedAt.Time + if !lastSynced.IsZero() { + sinceLast := time.Since(lastSynced) + if sinceLast < syncInterval { + remaining := syncInterval - sinceLast + log.Info("Last sync was recent skipping and requeuing", "remaining", remaining) + return ctrl.Result{RequeueAfter: remaining}, nil + } + } + + // Fetch the Nautobot nautobotService to get its ClusterIP + var nautobotService corev1.Service + if err := r.Get(ctx, types.NamespacedName{Namespace: "default", Name: "nautobot-default"}, &nautobotService); err != nil { + log.Error(err, "failed to fetch Service nautobot-default") + return ctrl.Result{}, err + } + + // Retrieve the Nautobot authentication token from a secret or external source + nautobotAuthToken, err := r.getAuthTokenFromSecretRef(ctx, nautobotCR) + if err != nil { + log.Error(err, "failed parse find nautoBotAuthToken") + return ctrl.Result{}, err + } + + deviceTypeMap := make(map[string]string) + for _, ref := range nautobotCR.Spec.DeviceTypesRef { + var deviceTypeConfigMap corev1.ConfigMap + err = r.Get(ctx, types.NamespacedName{ + Namespace: *ref.ConfigMapSelector.Namespace, + Name: ref.ConfigMapSelector.Name, + }, &deviceTypeConfigMap) + if err != nil { + log.Error(err, "failed to fetch ConfigMap 'deviceType' in namespace 'nautobot'") + return ctrl.Result{}, err + } + for k, v := range deviceTypeConfigMap.Data { + deviceTypeMap[k] = v + } + } + + n := nautobot.NewNautobotClient(fmt.Sprintf("http://%s/api", nautobotService.Spec.ClusterIP), nautobotAuthToken) + if err := n.SyncAllDeviceTypes(ctx, deviceTypeMap); err != nil { + log.Error(err, "SyncAllDeviceTypes failed") + return ctrl.Result{}, err + } + // Update status + nautobotCR.Status.LastSyncedAt = metav1.Now() + nautobotCR.Status.Ready = true + nautobotCR.Status.NautobotStatusReport = n.Report + if len(n.Report) > 0 { + nautobotCR.Status.Message = "sync completed with some errors" + } else { + nautobotCR.Status.Message = "Sync Successful" + } + if err := r.Status().Update(ctx, &nautobotCR); err != nil { + log.Error(err, "failed to update status") + return ctrl.Result{}, err + } + // Successfully completed reconciliation; requeue after configured sync interval + log.Info("sync completed successfully") + return ctrl.Result{RequeueAfter: syncInterval}, nil +} + +// getAuthTokenFromSecretRef: this will fetch Nautobot auth token from the given refer. +func (r *NautobotReconciler) getAuthTokenFromSecretRef(ctx context.Context, nautobotCR syncv1alpha1.Nautobot) (string, error) { + secret := &corev1.Secret{} + err := r.Get(ctx, types.NamespacedName{Name: nautobotCR.Spec.NautobotSecretRef.Name, Namespace: *nautobotCR.Spec.NautobotSecretRef.Namespace}, secret) + if err != nil { + return "", err + } + // Read the secret value + if valBytes, ok := secret.Data[nautobotCR.Spec.NautobotSecretRef.Key]; ok { + return string(valBytes), nil + } + return "", fmt.Errorf("secret key %s not found in secret", nautobotCR.Spec.NautobotSecretRef.Key) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *NautobotReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&syncv1alpha1.Nautobot{}). + Named("nautobot"). + Complete(r) +} diff --git a/go/rax/internal/controller/nautobot_controller_test.go b/go/nautobotop/internal/controller/nautobot_controller_test.go similarity index 97% rename from go/rax/internal/controller/nautobot_controller_test.go rename to go/nautobotop/internal/controller/nautobot_controller_test.go index 5b23e82fb..62c507270 100644 --- a/go/rax/internal/controller/nautobot_controller_test.go +++ b/go/nautobotop/internal/controller/nautobot_controller_test.go @@ -27,7 +27,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - syncv1alpha1 "github.com/rackerlabs/understack/go/sync/api/v1alpha1" + syncv1alpha1 "github.com/rackerlabs/understack/go/nautobotop/api/v1alpha1" ) var _ = Describe("Nautobot Controller", func() { diff --git a/go/rax/internal/controller/suite_test.go b/go/nautobotop/internal/controller/suite_test.go similarity index 97% rename from go/rax/internal/controller/suite_test.go rename to go/nautobotop/internal/controller/suite_test.go index e3fb2b358..287ab7a48 100644 --- a/go/rax/internal/controller/suite_test.go +++ b/go/nautobotop/internal/controller/suite_test.go @@ -32,7 +32,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - syncv1alpha1 "github.com/rackerlabs/understack/go/sync/api/v1alpha1" + syncv1alpha1 "github.com/rackerlabs/understack/go/nautobotop/api/v1alpha1" // +kubebuilder:scaffold:imports ) diff --git a/go/nautobotop/internal/nautobot/consolePortTemplate.go b/go/nautobotop/internal/nautobot/consolePortTemplate.go new file mode 100644 index 000000000..c6b3e7205 --- /dev/null +++ b/go/nautobotop/internal/nautobot/consolePortTemplate.go @@ -0,0 +1,71 @@ +package nautobot + +import ( + "context" + + "github.com/charmbracelet/log" + nb "github.com/nautobot/go-nautobot/v2" +) + +func (n *NautobotClient) ListAllConsolePortTemplateByDeviceType(ctx context.Context, deviceTypeID string) []nb.ConsolePortTemplate { + list, resp, err := n.Client.DcimAPI.DcimConsolePortTemplatesList(ctx).Limit(10000).Depth(10).DeviceType([]string{deviceTypeID}).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("ListAllConsolePortTemplateByDeviceType", "failed to list console port templates", "device_type_id", deviceTypeID, "error", err.Error(), "response_body", bodyString) + return []nb.ConsolePortTemplate{} + } + if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { + log.Debug("no console port templates found", "device_type_id", deviceTypeID) + return []nb.ConsolePortTemplate{} + } + log.Debug("retrieved console port templates", "device_type_id", deviceTypeID, "count", len(list.Results)) + return list.Results +} + +func (n *NautobotClient) GetConsolePortTemplateByName(ctx context.Context, name, deviceTypeID string) nb.ConsolePortTemplate { + list, resp, err := n.Client.DcimAPI.DcimConsolePortTemplatesList(ctx).Limit(10000).Depth(10).Name([]string{name}).DeviceType([]string{deviceTypeID}).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("GetConsolePortTemplateByName", "failed to get console port template by name", "name", name, "device_type_id", deviceTypeID, "error", err.Error(), "response_body", bodyString) + return nb.ConsolePortTemplate{} + } + if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { + log.Debug("console port template not found", "name", name, "device_type_id", deviceTypeID) + return nb.ConsolePortTemplate{} + } + log.Debug("found console port template", "name", name, "device_type_id", deviceTypeID, "id", list.Results[0].Id) + return list.Results[0] +} + +func (n *NautobotClient) CreateNewConsolePortTemplate(ctx context.Context, req nb.WritableConsolePortTemplateRequest) (*nb.ConsolePortTemplate, error) { + consolePort, resp, err := n.Client.DcimAPI.DcimConsolePortTemplatesCreate(ctx).WritableConsolePortTemplateRequest(req).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("CreateNewConsolePortTemplate", "failed to create console port template", "name", req.Name, "error", err.Error(), "response_body", bodyString) + return nil, err + } + log.Info("successfully created console port template", "name", consolePort.Name, "id", consolePort.Id) + return consolePort, nil +} + +func (n *NautobotClient) UpdateConsolePortTemplate(ctx context.Context, id string, req nb.WritableConsolePortTemplateRequest) (*nb.ConsolePortTemplate, error) { + consolePort, resp, err := n.Client.DcimAPI.DcimConsolePortTemplatesUpdate(ctx, id).WritableConsolePortTemplateRequest(req).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("UpdateConsolePortTemplate", "failed to update console port template", "id", id, "name", req.Name, "error", err.Error(), "response_body", bodyString) + return nil, err + } + log.Info("successfully updated console port template", "id", id, "name", consolePort.Name) + return consolePort, nil +} + +func (n *NautobotClient) DestroyConsolePortTemplate(ctx context.Context, id string) error { + resp, err := n.Client.DcimAPI.DcimConsolePortTemplatesDestroy(ctx, id).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("DestroyConsolePortTemplate", "failed to destroy console port template", "id", id, "error", err.Error(), "response_body", bodyString) + return err + } + log.Info("successfully destroyed console port template", "id", id) + return err +} diff --git a/go/nautobotop/internal/nautobot/deviceBayTemplate.go b/go/nautobotop/internal/nautobot/deviceBayTemplate.go new file mode 100644 index 000000000..5f7fa2b0f --- /dev/null +++ b/go/nautobotop/internal/nautobot/deviceBayTemplate.go @@ -0,0 +1,71 @@ +package nautobot + +import ( + "context" + + "github.com/charmbracelet/log" + nb "github.com/nautobot/go-nautobot/v2" +) + +func (n *NautobotClient) ListAllDeviceBayTemplateByDeviceType(ctx context.Context, deviceTypeID string) []nb.DeviceBayTemplate { + list, resp, err := n.Client.DcimAPI.DcimDeviceBayTemplatesList(ctx).Limit(10000).Depth(10).DeviceType([]string{deviceTypeID}).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("ListAllDeviceBayTemplateByDeviceType", "failed to list device bay templates", "device_type_id", deviceTypeID, "error", err.Error(), "response_body", bodyString) + return []nb.DeviceBayTemplate{} + } + if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { + log.Debug("no device bay templates found", "device_type_id", deviceTypeID) + return []nb.DeviceBayTemplate{} + } + log.Debug("retrieved device bay templates", "device_type_id", deviceTypeID, "count", len(list.Results)) + return list.Results +} + +func (n *NautobotClient) GetDeviceBayTemplateByName(ctx context.Context, name, deviceTypeID string) nb.DeviceBayTemplate { + list, resp, err := n.Client.DcimAPI.DcimDeviceBayTemplatesList(ctx).Limit(10000).Depth(10).Name([]string{name}).DeviceType([]string{deviceTypeID}).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("GetDeviceBayTemplateByName", "failed to get device bay template by name", "name", name, "device_type_id", deviceTypeID, "error", err.Error(), "response_body", bodyString) + return nb.DeviceBayTemplate{} + } + if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { + log.Debug("device bay template not found", "name", name, "device_type_id", deviceTypeID) + return nb.DeviceBayTemplate{} + } + log.Debug("found device bay template", "name", name, "device_type_id", deviceTypeID, "id", list.Results[0].Id) + return list.Results[0] +} + +func (n *NautobotClient) CreateNewDeviceBayTemplate(ctx context.Context, req nb.DeviceBayTemplateRequest) (*nb.DeviceBayTemplate, error) { + consolePort, resp, err := n.Client.DcimAPI.DcimDeviceBayTemplatesCreate(ctx).DeviceBayTemplateRequest(req).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("CreateNewDeviceBayTemplate", "failed to create device bay template", "name", req.Name, "error", err.Error(), "response_body", bodyString) + return nil, err + } + log.Info("successfully created device bay template", "name", consolePort.Name, "id", consolePort.Id) + return consolePort, nil +} + +func (n *NautobotClient) UpdateDeviceBayTemplate(ctx context.Context, id string, req nb.DeviceBayTemplateRequest) (*nb.DeviceBayTemplate, error) { + consolePort, resp, err := n.Client.DcimAPI.DcimDeviceBayTemplatesUpdate(ctx, id).DeviceBayTemplateRequest(req).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("UpdateDeviceBayTemplate", "failed to update device bay template", "id", id, "name", req.Name, "error", err.Error(), "response_body", bodyString) + return nil, err + } + log.Info("successfully updated device bay template", "id", id, "name", consolePort.Name) + return consolePort, nil +} + +func (n *NautobotClient) DestroyDeviceBayTemplate(ctx context.Context, id string) error { + resp, err := n.Client.DcimAPI.DcimDeviceBayTemplatesDestroy(ctx, id).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("DestroyDeviceBayTemplate", "failed to destroy device bay template", "id", id, "error", err.Error(), "response_body", bodyString) + return err + } + log.Info("successfully destroyed device bay template", "id", id) + return err +} diff --git a/go/nautobotop/internal/nautobot/deviceTypes.go b/go/nautobotop/internal/nautobot/deviceTypes.go new file mode 100644 index 000000000..d50744025 --- /dev/null +++ b/go/nautobotop/internal/nautobot/deviceTypes.go @@ -0,0 +1,332 @@ +package nautobot + +import ( + "context" + + "go.yaml.in/yaml/v3" + + "github.com/charmbracelet/log" + "github.com/samber/lo" + + nb "github.com/nautobot/go-nautobot/v2" +) + +type DeviceTypes struct { + DeviceTypes []DeviceType +} +type DeviceType struct { + Manufacturer string `yaml:"manufacturer"` + PartNumber string `yaml:"part_number"` + Model string `yaml:"model"` + UHeight int `yaml:"u_height"` + IsFullDepth bool `yaml:"is_full_depth"` + Comments string `yaml:"comments"` + ConsolePorts []ConsolePort `yaml:"console-ports"` + PowerPorts []PowerPort `yaml:"power-ports"` + Interfaces []Interface `yaml:"interfaces"` + ModuleBays []ModuleBay `yaml:"module-bays"` + Class string `yaml:"class"` + ResourceClass []ResourceClass `yaml:"resource_class"` +} + +type ConsolePort struct { + Name string `yaml:"name"` + Type string `yaml:"type"` +} + +type PowerPort struct { + Name string `yaml:"name"` + Type string `yaml:"type"` + MaximumDraw int `yaml:"maximum_draw"` + AllocatedDraw int `yaml:"allocated_draw"` +} + +type Interface struct { + Name string `yaml:"name"` + Type string `yaml:"type"` + MgmtOnly bool `yaml:"mgmt_only"` +} + +type ModuleBay struct { + Name string `yaml:"name"` + Position string `yaml:"position"` + Label string `yaml:"label,omitempty"` +} + +type ResourceClass struct { + Name string `yaml:"name"` + CPU CPU `yaml:"cpu"` + Memory Memory `yaml:"memory"` + Drives []Disk `yaml:"drives"` + NicCount int `yaml:"nic_count"` +} + +type CPU struct { + Cores int `yaml:"cores"` + Model string `yaml:"model"` +} + +type Memory struct { + Size int `yaml:"size"` +} + +type Disk struct { + Size int `yaml:"size"` +} + +func (n *NautobotClient) SyncAllDeviceTypes(ctx context.Context, data map[string]string) error { + var deviceTypes DeviceTypes + for _, f := range data { + var yml DeviceType + + if err := yaml.Unmarshal([]byte(f), &yml); err != nil { + n.AddReport("yamlFailed", err.Error()) + return err + } + deviceTypes.DeviceTypes = append(deviceTypes.DeviceTypes, yml) + + manufacturer := n.GetManufacturerByName(context.Background(), yml.Manufacturer) + if manufacturer.Id == "" { + cm, _ := n.CreateNewManufacturer(context.Background(), nb.ManufacturerRequest{ + Name: yml.Manufacturer, + Description: nb.PtrString(yml.Manufacturer), + }) + manufacturer = *cm + } + + deviceType := n.GetDeviceTypeByName(context.Background(), yml.Model) + if deviceType.Id == "" { + dt, _ := n.CreateNewDeviceType(context.Background(), nb.WritableDeviceTypeRequest{ + Model: yml.Model, + PartNumber: nb.PtrString(yml.PartNumber), + UHeight: nb.PtrInt32(int32(yml.UHeight)), + IsFullDepth: nb.PtrBool(yml.IsFullDepth), + Comments: nb.PtrString(yml.Comments), + Manufacturer: *buildBulkWritableCableRequestStatus(manufacturer.Id), + }) + deviceType = *dt + } + + n.syncDeviceTypeInterfaceTemplate(ctx, yml, deviceType) + n.syncDeviceTypeConsolePortTemplate(ctx, yml, deviceType) + n.syncDeviceTypePowerPortTemplate(ctx, yml, deviceType) + n.syncDeviceTypeModuleBayTemplate(ctx, yml, deviceType) + } + + desiredDeviceTypes := make(map[string]DeviceType) + for _, deviceType := range deviceTypes.DeviceTypes { + desiredDeviceTypes[deviceType.Model] = deviceType + } + existingDeviceTypes := n.ListAllDeviceTypes(ctx) + existingMap := make(map[string]nb.DeviceType, len(existingDeviceTypes)) + for _, template := range existingDeviceTypes { + existingMap[template.Model] = template + } + obsoleteDeviceTypes := lo.OmitByKeys(existingMap, lo.Keys(desiredDeviceTypes)) + for _, obsoleteDeviceType := range obsoleteDeviceTypes { + _ = n.DestroyDeviceType(ctx, obsoleteDeviceType.Id) + } + + log.Info("SyncAllDevice Completed") + return nil +} + +func (n *NautobotClient) syncDeviceTypePowerPortTemplate(ctx context.Context, yml DeviceType, deviceType nb.DeviceType) { + // Build map of desired power ports from YAML configuration + desiredPorts := make(map[string]PowerPort) + for _, port := range yml.PowerPorts { + desiredPorts[port.Name] = port + } + + // Build map of existing power port templates from Nautobot + existingTemplates := n.ListAllPowerPortTemplate(ctx, deviceType.Id) + existingMap := make(map[string]nb.PowerPortTemplate) + for _, template := range existingTemplates { + existingMap[template.Name] = template + } + + // Process each desired power port: create new or update existing + for portName, desiredPort := range desiredPorts { + powerPortTypeChoice, _ := nb.NewPowerPortTypeChoicesFromValue(desiredPort.Type) + + var maximumDraw nb.NullableInt32 + if desiredPort.MaximumDraw > 0 { + maximumDraw = *nb.NewNullableInt32(nb.PtrInt32(int32(desiredPort.MaximumDraw))) + } + + var allocatedDraw nb.NullableInt32 + if desiredPort.AllocatedDraw > 0 { + allocatedDraw = *nb.NewNullableInt32(nb.PtrInt32(int32(desiredPort.AllocatedDraw))) + } + + templateRequest := nb.WritablePowerPortTemplateRequest{ + Name: desiredPort.Name, + Type: &nb.PatchedWritablePowerPortTemplateRequestType{ + PowerPortTypeChoices: powerPortTypeChoice, + }, + MaximumDraw: maximumDraw, + AllocatedDraw: allocatedDraw, + DeviceType: buildNullableBulkWritableCircuitRequestTenant(deviceType.Id), + } + + if existingTemplate, exists := existingMap[portName]; exists { + _, _ = n.UpdatePowerPortTemplate(ctx, existingTemplate.Id, templateRequest) + } else { + _, _ = n.CreateNewPowerPortTemplate(ctx, templateRequest) + } + } + + obsoleteTemplates := lo.OmitByKeys(existingMap, lo.Keys(desiredPorts)) + for _, obsoleteTemplate := range obsoleteTemplates { + _ = n.DestroyPowerPortTemplate(ctx, obsoleteTemplate.Id) + } +} + +func (n *NautobotClient) syncDeviceTypeConsolePortTemplate(ctx context.Context, yml DeviceType, deviceType nb.DeviceType) { + // Build map of desired console ports from YAML configuration + desiredPorts := make(map[string]ConsolePort) + for _, port := range yml.ConsolePorts { + desiredPorts[port.Name] = port + } + + existingTemplates := n.ListAllConsolePortTemplateByDeviceType(ctx, deviceType.Id) + existingMap := make(map[string]nb.ConsolePortTemplate) + for _, template := range existingTemplates { + existingMap[template.Name] = template + } + + for portName, desiredPort := range desiredPorts { + consolePortTypeChoice, _ := nb.NewConsolePortTypeChoicesFromValue(desiredPort.Type) + templateRequest := nb.WritableConsolePortTemplateRequest{ + Name: portName, + Type: &nb.PatchedWritableConsolePortTemplateRequestType{ + ConsolePortTypeChoices: consolePortTypeChoice, + }, + DeviceType: buildNullableBulkWritableCircuitRequestTenant(deviceType.Id), + } + if existingTemplate, exists := existingMap[portName]; exists { + _, _ = n.UpdateConsolePortTemplate(ctx, existingTemplate.Id, templateRequest) + } else { + _, _ = n.CreateNewConsolePortTemplate(ctx, templateRequest) + } + } + obsoleteTemplates := lo.OmitByKeys(existingMap, lo.Keys(desiredPorts)) + for _, obsoleteTemplate := range obsoleteTemplates { + _ = n.DestroyConsolePortTemplate(ctx, obsoleteTemplate.Id) + } +} + +func (n *NautobotClient) syncDeviceTypeInterfaceTemplate(ctx context.Context, yml DeviceType, deviceType nb.DeviceType) { + // Build map of desired console ports from YAML configuration + desiredInterfaceTemplate := make(map[string]Interface) + for _, interfaceTmpl := range yml.Interfaces { + desiredInterfaceTemplate[interfaceTmpl.Name] = interfaceTmpl + } + + existingTemplates := n.ListAllInterfaceTemplateByDeviceType(ctx, deviceType.Id) + existingMap := make(map[string]nb.InterfaceTemplate) + for _, template := range existingTemplates { + existingMap[template.Display] = template + } + for portName, interfaceTmpl := range desiredInterfaceTemplate { + interfaceTemplateChoice, _ := nb.NewInterfaceTypeChoicesFromValue(interfaceTmpl.Type) + + templateRequest := nb.WritableInterfaceTemplateRequest{ + Name: portName, + Type: *interfaceTemplateChoice, + MgmtOnly: nb.PtrBool(interfaceTmpl.MgmtOnly), + DeviceType: buildNullableBulkWritableCircuitRequestTenant(deviceType.Id), + } + if existingTemplate, exists := existingMap[portName]; exists { + _, _ = n.UpdateInterfaceTemplate(ctx, existingTemplate.Id, templateRequest) + } else { + _, _ = n.CreateNewInterfaceTemplate(ctx, templateRequest) + } + } + obsoleteTemplates := lo.OmitByKeys(existingMap, lo.Keys(desiredInterfaceTemplate)) + for _, obsoleteTemplate := range obsoleteTemplates { + _ = n.DestroyInterfaceTemplate(ctx, obsoleteTemplate.Id) + } +} + +func (n *NautobotClient) syncDeviceTypeModuleBayTemplate(ctx context.Context, yml DeviceType, deviceType nb.DeviceType) { + // Build map of desired power ports from YAML configuration + desiredModuleBays := make(map[string]ModuleBay) + for _, moduleBay := range yml.ModuleBays { + desiredModuleBays[moduleBay.Name] = moduleBay + } + + // Build map of existing power port templates from Nautobot + existingTemplates := n.ListAllModuleBayTemplateByDeviceType(ctx, deviceType.Id) + existingMap := make(map[string]nb.ModuleBayTemplate) + for _, template := range existingTemplates { + existingMap[template.Name] = template + } + + // Process each desired power port: create new or update existing + for moduleBayName, moduleBay := range desiredModuleBays { + templateRequest := nb.ModuleBayTemplateRequest{ + Name: moduleBay.Name, + Label: nb.PtrString(moduleBay.Label), + DeviceType: buildNullableBulkWritableCircuitRequestTenant(deviceType.Id), + } + + if existingTemplate, exists := existingMap[moduleBayName]; exists { + _, _ = n.UpdateModuleBayTemplate(ctx, existingTemplate.Id, templateRequest) + } else { + _, _ = n.CreateNewModuleBayTemplate(ctx, templateRequest) + } + } + + obsoleteTemplates := lo.OmitByKeys(existingMap, lo.Keys(desiredModuleBays)) + for _, obsoleteTemplate := range obsoleteTemplates { + _ = n.DestroyModuleBayTemplate(ctx, obsoleteTemplate.Id) + } +} + +func (n *NautobotClient) CreateNewDeviceType(ctx context.Context, req nb.WritableDeviceTypeRequest) (*nb.DeviceType, error) { + deviceType, resp, err := n.Client.DcimAPI.DcimDeviceTypesCreate(ctx).WritableDeviceTypeRequest(req).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("createNewDeviceType", "failed to create", "model", req.Model, "error", err.Error(), "response_body", bodyString) + return nil, err + } + log.Printf("Created manufacture: %s", deviceType.Display) + return deviceType, nil +} + +func (n *NautobotClient) GetDeviceTypeByName(ctx context.Context, name string) nb.DeviceType { + list, resp, err := n.Client.DcimAPI.DcimDeviceTypesList(ctx).Model([]string{name}).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("GetDeviceTypeByName", "failed to get", "name", name, "error", err.Error(), "response_body", bodyString) + return nb.DeviceType{} + } + if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { + return nb.DeviceType{} + } + return list.Results[0] +} + +func (n *NautobotClient) ListAllDeviceTypes(ctx context.Context) []nb.DeviceType { + list, resp, err := n.Client.DcimAPI.DcimDeviceTypesList(ctx).Depth(10).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("ListAllDeviceTypes", "failed to list", "error", err.Error(), "response_body", bodyString) + return []nb.DeviceType{} + } + if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { + return []nb.DeviceType{} + } + return list.Results +} + +func (n *NautobotClient) DestroyDeviceType(ctx context.Context, id string) error { + resp, err := n.Client.DcimAPI.DcimDeviceTypesDestroy(ctx, id).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("DestroyDeviceType", "failed to destroy", "id", id, "error", err.Error(), "response_body", bodyString) + return err + } + return nil +} diff --git a/go/nautobotop/internal/nautobot/helpers.go b/go/nautobotop/internal/nautobot/helpers.go new file mode 100644 index 000000000..c94c02567 --- /dev/null +++ b/go/nautobotop/internal/nautobot/helpers.go @@ -0,0 +1,92 @@ +package nautobot + +import ( + "fmt" + "io" + "io/fs" + "net/http" + "os" + "path/filepath" + + "github.com/charmbracelet/log" + + nb "github.com/nautobot/go-nautobot/v2" + "go.yaml.in/yaml/v3" +) + +func ListYAMLFiles(dir string) ([]string, error) { + var files []string + + err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("error accessing %s: %w", path, err) + } + + if d.IsDir() { + return nil + } + + ext := filepath.Ext(path) + if ext == ".yaml" || ext == ".yml" { + files = append(files, path) + } + return nil + }) + + if err != nil { + return nil, err + } + + return files, nil +} + +func ParseYAMLToStruct[T any](path string) (T, error) { + var result T + + data, err := os.ReadFile(path) + if err != nil { + return result, fmt.Errorf("failed to read file %s: %w", path, err) + } + + if err := yaml.Unmarshal(data, &result); err != nil { + return result, fmt.Errorf("failed to parse YAML %s: %w", path, err) + } + + return result, nil +} + +func buildBulkWritableCableRequestStatus(uuid string) *nb.BulkWritableCableRequestStatus { + return &nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: nb.PtrString(uuid), + }, + } +} + +func buildNullableBulkWritableCircuitRequestTenant(uuid string) nb.NullableBulkWritableCircuitRequestTenant { + return *nb.NewNullableBulkWritableCircuitRequestTenant(&nb.BulkWritableCircuitRequestTenant{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: nb.PtrString(uuid), + }, + }) +} + +// readResponseBody safely reads and closes the response body. +// Returns the body content as a string. If resp is nil, returns empty string. +func readResponseBody(resp *http.Response) string { + if resp == nil || resp.Body == nil { + return "" + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + log.Info("failed to close response body", "error", err) + } + }(resp.Body) + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Sprintf("failed to read response body: %v", err) + } + return string(bodyBytes) +} diff --git a/go/nautobotop/internal/nautobot/interfaceTemplate.go b/go/nautobotop/internal/nautobot/interfaceTemplate.go new file mode 100644 index 000000000..3a2198098 --- /dev/null +++ b/go/nautobotop/internal/nautobot/interfaceTemplate.go @@ -0,0 +1,71 @@ +package nautobot + +import ( + "context" + + "github.com/charmbracelet/log" + nb "github.com/nautobot/go-nautobot/v2" +) + +func (n *NautobotClient) ListAllInterfaceTemplateByDeviceType(ctx context.Context, deviceTypeID string) []nb.InterfaceTemplate { + list, resp, err := n.Client.DcimAPI.DcimInterfaceTemplatesList(ctx).Limit(10000).Depth(10).DeviceType([]string{deviceTypeID}).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("ListAllInterfaceTemplateByDeviceType", "failed to list interface templates", "device_type_id", deviceTypeID, "error", err.Error(), "response_body", bodyString) + return []nb.InterfaceTemplate{} + } + if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { + log.Debug("no console port templates found", "device_type_id", deviceTypeID) + return []nb.InterfaceTemplate{} + } + log.Debug("retrieved console port templates", "device_type_id", deviceTypeID, "count", len(list.Results)) + return list.Results +} + +func (n *NautobotClient) GetInterfaceTemplateByName(ctx context.Context, name, deviceTypeID string) nb.InterfaceTemplate { + list, resp, err := n.Client.DcimAPI.DcimInterfaceTemplatesList(ctx).Limit(10000).Depth(10).Name([]string{name}).DeviceType([]string{deviceTypeID}).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("GetInterfaceTemplateByName", "failed to get interface template by name", "name", name, "device_type_id", deviceTypeID, "error", err.Error(), "response_body", bodyString) + return nb.InterfaceTemplate{} + } + if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { + log.Debug("console port template not found", "name", name, "device_type_id", deviceTypeID) + return nb.InterfaceTemplate{} + } + log.Debug("found console port template", "name", name, "device_type_id", deviceTypeID, "id", list.Results[0].Id) + return list.Results[0] +} + +func (n *NautobotClient) CreateNewInterfaceTemplate(ctx context.Context, req nb.WritableInterfaceTemplateRequest) (*nb.InterfaceTemplate, error) { + consolePort, resp, err := n.Client.DcimAPI.DcimInterfaceTemplatesCreate(ctx).WritableInterfaceTemplateRequest(req).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("CreateNewInterfaceTemplate", "failed to create interface template", "name", req.Name, "error", err.Error(), "response_body", bodyString) + return nil, err + } + log.Info("successfully created console port template", "name", consolePort.Name, "id", consolePort.Id) + return consolePort, nil +} + +func (n *NautobotClient) UpdateInterfaceTemplate(ctx context.Context, id string, req nb.WritableInterfaceTemplateRequest) (*nb.InterfaceTemplate, error) { + consolePort, resp, err := n.Client.DcimAPI.DcimInterfaceTemplatesUpdate(ctx, id).WritableInterfaceTemplateRequest(req).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("UpdateInterfaceTemplate", "failed to update interface template", "id", id, "name", req.Name, "error", err.Error(), "response_body", bodyString) + return nil, err + } + log.Info("successfully updated console port template", "id", id, "name", consolePort.Name) + return consolePort, nil +} + +func (n *NautobotClient) DestroyInterfaceTemplate(ctx context.Context, id string) error { + resp, err := n.Client.DcimAPI.DcimInterfaceTemplatesDestroy(ctx, id).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("DestroyInterfaceTemplate", "failed to destroy interface template", "id", id, "error", err.Error(), "response_body", bodyString) + return err + } + log.Info("successfully destroyed console port template", "id", id) + return err +} diff --git a/go/nautobotop/internal/nautobot/manufacturers.go b/go/nautobotop/internal/nautobot/manufacturers.go new file mode 100644 index 000000000..05ff6cb64 --- /dev/null +++ b/go/nautobotop/internal/nautobot/manufacturers.go @@ -0,0 +1,32 @@ +package nautobot + +import ( + "context" + "log" + + nb "github.com/nautobot/go-nautobot/v2" +) + +func (n *NautobotClient) GetManufacturerByName(ctx context.Context, name string) nb.Manufacturer { + list, resp, err := n.Client.DcimAPI.DcimManufacturersList(ctx).Limit(10000).Depth(10).Name([]string{name}).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("GetManufacturerByName", "failed to get manufacturer by name", "name", name, "error", err.Error(), "response_body", bodyString) + return nb.Manufacturer{} + } + if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { + return nb.Manufacturer{} + } + return list.Results[0] +} + +func (n *NautobotClient) CreateNewManufacturer(ctx context.Context, req nb.ManufacturerRequest) (*nb.Manufacturer, error) { + manufacture, resp, err := n.Client.DcimAPI.DcimManufacturersCreate(ctx).ManufacturerRequest(req).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("CreateNewManufacturer", "failed to create manufacturer", "name", req.Name, "error", err.Error(), "response_body", bodyString) + return nil, err + } + log.Printf("Created manufacture: %s", manufacture.Display) + return manufacture, nil +} diff --git a/go/nautobotop/internal/nautobot/moduleBayTemplate.go b/go/nautobotop/internal/nautobot/moduleBayTemplate.go new file mode 100644 index 000000000..3ad7a3234 --- /dev/null +++ b/go/nautobotop/internal/nautobot/moduleBayTemplate.go @@ -0,0 +1,71 @@ +package nautobot + +import ( + "context" + + "github.com/charmbracelet/log" + nb "github.com/nautobot/go-nautobot/v2" +) + +func (n *NautobotClient) ListAllModuleBayTemplateByDeviceType(ctx context.Context, deviceTypeID string) []nb.ModuleBayTemplate { + list, resp, err := n.Client.DcimAPI.DcimModuleBayTemplatesList(ctx).Limit(10000).Depth(10).DeviceType([]string{deviceTypeID}).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("ListAllModuleBayTemplateByDeviceType", "failed to list module bay templates", "device_type_id", deviceTypeID, "error", err.Error(), "response_body", bodyString) + return []nb.ModuleBayTemplate{} + } + if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { + log.Debug("no module bay templates found", "device_type_id", deviceTypeID) + return []nb.ModuleBayTemplate{} + } + log.Debug("retrieved module bay templates", "device_type_id", deviceTypeID, "count", len(list.Results)) + return list.Results +} + +func (n *NautobotClient) GetModuleBayTemplateByName(ctx context.Context, name, deviceTypeID string) nb.ModuleBayTemplate { + list, resp, err := n.Client.DcimAPI.DcimModuleBayTemplatesList(ctx).Limit(10000).Depth(10).Name([]string{name}).DeviceType([]string{deviceTypeID}).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("GetModuleBayTemplateByName", "failed to get module bay template by name", "name", name, "device_type_id", deviceTypeID, "error", err.Error(), "response_body", bodyString) + return nb.ModuleBayTemplate{} + } + if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { + log.Debug("module bay template not found", "name", name, "device_type_id", deviceTypeID) + return nb.ModuleBayTemplate{} + } + log.Debug("found module bay template", "name", name, "device_type_id", deviceTypeID, "id", list.Results[0].Id) + return list.Results[0] +} + +func (n *NautobotClient) CreateNewModuleBayTemplate(ctx context.Context, req nb.ModuleBayTemplateRequest) (*nb.ModuleBayTemplate, error) { + consolePort, resp, err := n.Client.DcimAPI.DcimModuleBayTemplatesCreate(ctx).ModuleBayTemplateRequest(req).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("CreateNewModuleBayTemplate", "failed to create module bay template", "name", req.Name, "error", err.Error(), "response_body", bodyString) + return nil, err + } + log.Info("successfully created module bay template", "name", consolePort.Name, "id", consolePort.Id) + return consolePort, nil +} + +func (n *NautobotClient) UpdateModuleBayTemplate(ctx context.Context, id string, req nb.ModuleBayTemplateRequest) (*nb.ModuleBayTemplate, error) { + consolePort, resp, err := n.Client.DcimAPI.DcimModuleBayTemplatesUpdate(ctx, id).ModuleBayTemplateRequest(req).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("UpdateModuleBayTemplate", "failed to update module bay template", "id", id, "name", req.Name, "error", err.Error(), "response_body", bodyString) + return nil, err + } + log.Info("successfully updated module bay template", "id", id, "name", consolePort.Name) + return consolePort, nil +} + +func (n *NautobotClient) DestroyModuleBayTemplate(ctx context.Context, id string) error { + resp, err := n.Client.DcimAPI.DcimModuleBayTemplatesDestroy(ctx, id).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("DestroyModuleBayTemplate", "failed to destroy module bay template", "id", id, "error", err.Error(), "response_body", bodyString) + return err + } + log.Info("successfully destroyed module bay template", "id", id) + return err +} diff --git a/go/rax/internal/nautobot/nautobot.go b/go/nautobotop/internal/nautobot/nautobot.go similarity index 74% rename from go/rax/internal/nautobot/nautobot.go rename to go/nautobotop/internal/nautobot/nautobot.go index 539bcb8f0..50925f047 100644 --- a/go/rax/internal/nautobot/nautobot.go +++ b/go/nautobotop/internal/nautobot/nautobot.go @@ -1,6 +1,9 @@ package nautobot import ( + "strings" + + "github.com/charmbracelet/log" nb "github.com/nautobot/go-nautobot/v2" ) @@ -8,14 +11,14 @@ import ( type NautobotClient struct { Config *nb.Configuration Client *nb.APIClient + Report map[string][]string } -// NautobotYAML defines the structure for loading Nautobot configuration from YAML. -type NautobotYAML struct { - InstanceLocations []Location `yaml:"instance_locations"` - LocationTypes []LocationType `yaml:"location_types"` - RackGroup []RackGroup `yaml:"instance_rack_groups"` - Rack []Rack `yaml:"instance_racks"` +// AddReport appends one or more lines to the current reconciliation report. +func (n *NautobotClient) AddReport(key string, line ...string) { + combined := strings.Join(line, " ") + n.Report[key] = append(n.Report[key], combined) + log.Error(key, combined) } // NewNautobotClient creates and configures a new Nautobot API client. @@ -45,5 +48,6 @@ func NewNautobotClient(apiURL string, authToken string) *NautobotClient { return &NautobotClient{ Config: config, Client: client, + Report: make(map[string][]string), } } diff --git a/go/nautobotop/internal/nautobot/powerPortTemplate.go b/go/nautobotop/internal/nautobot/powerPortTemplate.go new file mode 100644 index 000000000..6698a64b6 --- /dev/null +++ b/go/nautobotop/internal/nautobot/powerPortTemplate.go @@ -0,0 +1,71 @@ +package nautobot + +import ( + "context" + + "github.com/charmbracelet/log" + nb "github.com/nautobot/go-nautobot/v2" +) + +func (n *NautobotClient) ListAllPowerPortTemplate(ctx context.Context, deviceTypeID string) []nb.PowerPortTemplate { + list, resp, err := n.Client.DcimAPI.DcimPowerPortTemplatesList(ctx).Limit(10000).Depth(10).DeviceType([]string{deviceTypeID}).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("ListAllPowerPortTemplate", "failed to list power port templates", "device_type_id", deviceTypeID, "error", err.Error(), "response_body", bodyString) + return []nb.PowerPortTemplate{} + } + if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { + log.Debug("no power port templates found", "device_type_id", deviceTypeID) + return []nb.PowerPortTemplate{} + } + log.Debug("retrieved power port templates", "device_type_id", deviceTypeID, "count", len(list.Results)) + return list.Results +} + +func (n *NautobotClient) GetPowerPortTemplateByName(ctx context.Context, name, deviceTypeID string) nb.PowerPortTemplate { + list, resp, err := n.Client.DcimAPI.DcimPowerPortTemplatesList(ctx).Limit(10000).Depth(10).Name([]string{name}).DeviceType([]string{deviceTypeID}).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("GetPowerPortTemplateByName", "failed to get power port template by name", "name", name, "device_type_id", deviceTypeID, "error", err.Error(), "response_body", bodyString) + return nb.PowerPortTemplate{} + } + if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { + log.Debug("power port template not found", "name", name, "device_type_id", deviceTypeID) + return nb.PowerPortTemplate{} + } + log.Debug("found power port template", "name", name, "device_type_id", deviceTypeID, "id", list.Results[0].Id) + return list.Results[0] +} + +func (n *NautobotClient) CreateNewPowerPortTemplate(ctx context.Context, req nb.WritablePowerPortTemplateRequest) (*nb.PowerPortTemplate, error) { + powerPort, resp, err := n.Client.DcimAPI.DcimPowerPortTemplatesCreate(ctx).WritablePowerPortTemplateRequest(req).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("CreateNewPowerPortTemplate", "failed to create power port template", "name", req.Name, "error", err.Error(), "response_body", bodyString) + return nil, err + } + log.Info("successfully created power port template", "name", powerPort.Name, "id", powerPort.Id) + return powerPort, nil +} + +func (n *NautobotClient) UpdatePowerPortTemplate(ctx context.Context, id string, req nb.WritablePowerPortTemplateRequest) (*nb.PowerPortTemplate, error) { + powerPort, resp, err := n.Client.DcimAPI.DcimPowerPortTemplatesUpdate(ctx, id).WritablePowerPortTemplateRequest(req).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("UpdatePowerPortTemplate", "failed to update power port template", "id", id, "name", req.Name, "error", err.Error(), "response_body", bodyString) + return nil, err + } + log.Info("successfully updated power port template", "id", id, "name", powerPort.Name) + return powerPort, nil +} + +func (n *NautobotClient) DestroyPowerPortTemplate(ctx context.Context, id string) error { + resp, err := n.Client.DcimAPI.DcimPowerPortTemplatesDestroy(ctx, id).Execute() + if err != nil { + bodyString := readResponseBody(resp) + n.AddReport("DestroyPowerPortTemplate", "failed to destroy power port template", "id", id, "error", err.Error(), "response_body", bodyString) + return err + } + log.Info("successfully destroyed power port template", "id", id) + return nil +} diff --git a/go/rax/test/e2e/e2e_suite_test.go b/go/nautobotop/test/e2e/e2e_suite_test.go similarity index 94% rename from go/rax/test/e2e/e2e_suite_test.go rename to go/nautobotop/test/e2e/e2e_suite_test.go index 580f77a51..6382ce6a1 100644 --- a/go/rax/test/e2e/e2e_suite_test.go +++ b/go/nautobotop/test/e2e/e2e_suite_test.go @@ -25,7 +25,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/rackerlabs/understack/go/sync/test/utils" + "github.com/rackerlabs/understack/go/nautobotop/test/utils" ) var ( @@ -39,7 +39,7 @@ var ( // projectImage is the name of the image which will be build and loaded // with the code source changes to be tested. - projectImage = "example.com/rax:v0.0.1" + projectImage = "example.com/nautobotop:v0.0.1" ) // TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, @@ -48,7 +48,7 @@ var ( // CertManager. func TestE2E(t *testing.T) { RegisterFailHandler(Fail) - _, _ = fmt.Fprintf(GinkgoWriter, "Starting rax integration test suite\n") + _, _ = fmt.Fprintf(GinkgoWriter, "Starting nautobotop integration test suite\n") RunSpecs(t, "e2e suite") } diff --git a/go/rax/test/e2e/e2e_test.go b/go/nautobotop/test/e2e/e2e_test.go similarity index 97% rename from go/rax/test/e2e/e2e_test.go rename to go/nautobotop/test/e2e/e2e_test.go index 1530ef74e..c16784140 100644 --- a/go/rax/test/e2e/e2e_test.go +++ b/go/nautobotop/test/e2e/e2e_test.go @@ -27,20 +27,20 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/rackerlabs/understack/go/sync/test/utils" + "github.com/rackerlabs/understack/go/nautobotop/test/utils" ) // namespace where the project is deployed in -const namespace = "rax-system" +const namespace = "nautobotop-system" // serviceAccountName created for the project -const serviceAccountName = "rax-controller-manager" +const serviceAccountName = "nautobotop-controller-manager" // metricsServiceName is the name of the metrics service of the project -const metricsServiceName = "rax-controller-manager-metrics-service" +const metricsServiceName = "nautobotop-controller-manager-metrics-service" // metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data -const metricsRoleBindingName = "rax-metrics-binding" +const metricsRoleBindingName = "nautobotop-metrics-binding" var _ = Describe("Manager", Ordered, func() { var controllerPodName string @@ -173,7 +173,7 @@ var _ = Describe("Manager", Ordered, func() { It("should ensure the metrics endpoint is serving metrics", func() { By("creating a ClusterRoleBinding for the service account to allow access to metrics") cmd := exec.Command("kubectl", "create", "clusterrolebinding", metricsRoleBindingName, - "--clusterrole=rax-metrics-reader", + "--clusterrole=nautobotop-metrics-reader", fmt.Sprintf("--serviceaccount=%s:%s", namespace, serviceAccountName), ) _, err := utils.Run(cmd) diff --git a/go/rax/test/utils/utils.go b/go/nautobotop/test/utils/utils.go similarity index 85% rename from go/rax/test/utils/utils.go rename to go/nautobotop/test/utils/utils.go index 73cde64ba..841683609 100644 --- a/go/rax/test/utils/utils.go +++ b/go/nautobotop/test/utils/utils.go @@ -24,7 +24,7 @@ import ( "os/exec" "strings" - . "github.com/onsi/ginkgo/v2" //nolint:golint,revive + . "github.com/onsi/ginkgo/v2" // nolint:revive,staticcheck ) const ( @@ -46,15 +46,15 @@ func Run(cmd *exec.Cmd) (string, error) { cmd.Dir = dir if err := os.Chdir(cmd.Dir); err != nil { - _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err) + _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %q\n", err) } cmd.Env = append(os.Environ(), "GO111MODULE=on") command := strings.Join(cmd.Args, " ") - _, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command) + _, _ = fmt.Fprintf(GinkgoWriter, "running: %q\n", command) output, err := cmd.CombinedOutput() if err != nil { - return string(output), fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output)) + return string(output), fmt.Errorf("%q failed with error %q: %w", command, string(output), err) } return string(output), nil @@ -195,7 +195,7 @@ func GetNonEmptyLines(output string) []string { func GetProjectDir() (string, error) { wd, err := os.Getwd() if err != nil { - return wd, err + return wd, fmt.Errorf("failed to get current working directory: %w", err) } wd = strings.ReplaceAll(wd, "/test/e2e", "") return wd, nil @@ -208,19 +208,19 @@ func UncommentCode(filename, target, prefix string) error { // nolint:gosec content, err := os.ReadFile(filename) if err != nil { - return err + return fmt.Errorf("failed to read file %q: %w", filename, err) } strContent := string(content) idx := strings.Index(strContent, target) if idx < 0 { - return fmt.Errorf("unable to find the code %s to be uncomment", target) + return fmt.Errorf("unable to find the code %q to be uncomment", target) } out := new(bytes.Buffer) _, err = out.Write(content[:idx]) if err != nil { - return err + return fmt.Errorf("failed to write to output: %w", err) } scanner := bufio.NewScanner(bytes.NewBufferString(target)) @@ -228,24 +228,27 @@ func UncommentCode(filename, target, prefix string) error { return nil } for { - _, err := out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)) - if err != nil { - return err + if _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil { + return fmt.Errorf("failed to write to output: %w", err) } // Avoid writing a newline in case the previous line was the last in target. if !scanner.Scan() { break } - if _, err := out.WriteString("\n"); err != nil { - return err + if _, err = out.WriteString("\n"); err != nil { + return fmt.Errorf("failed to write to output: %w", err) } } - _, err = out.Write(content[idx+len(target):]) - if err != nil { - return err + if _, err = out.Write(content[idx+len(target):]); err != nil { + return fmt.Errorf("failed to write to output: %w", err) } + // false positive // nolint:gosec - return os.WriteFile(filename, out.Bytes(), 0644) + if err = os.WriteFile(filename, out.Bytes(), 0644); err != nil { + return fmt.Errorf("failed to write file %q: %w", filename, err) + } + + return nil } diff --git a/go/rax/.golangci.yml b/go/rax/.golangci.yml deleted file mode 100644 index 2443485e0..000000000 --- a/go/rax/.golangci.yml +++ /dev/null @@ -1,49 +0,0 @@ -version: "2" -run: - allow-parallel-runners: true -linters: - default: none - enable: - - dupl - - errcheck - - goconst - - gocyclo - - govet - - ineffassign - - lll - - misspell - - nakedret - - prealloc - - staticcheck - - unconvert - - unparam - - unused - exclusions: - generated: lax - rules: - - linters: - - lll - path: api/* - - linters: - - dupl - - lll - path: internal/* - paths: - - third_party$ - - builtin$ - - examples$ - settings: - staticcheck: - dot-import-whitelist: - - fmt - - github.com/onsi/ginkgo/v2 -formatters: - enable: - - gofmt - - goimports - exclusions: - generated: lax - paths: - - third_party$ - - builtin$ - - examples$ diff --git a/go/rax/api/v1alpha1/gitrepowatcher_types.go b/go/rax/api/v1alpha1/gitrepowatcher_types.go deleted file mode 100644 index d7583bc24..000000000 --- a/go/rax/api/v1alpha1/gitrepowatcher_types.go +++ /dev/null @@ -1,69 +0,0 @@ -/* -Copyright 2025. - -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 v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. - -// GitRepoWatcherSpec defines the desired state of GitRepoWatcher. -type GitRepoWatcherSpec struct { - RepoURL string `json:"repoURL"` - GitOrgName string `json:"gitOrgName"` - Ref string `json:"ref"` // branch or tag - // +kubebuilder:default=60 - SyncIntervalSeconds int `json:"syncIntervalSeconds,omitempty"` - Secrets []Secret `json:"secrets,omitempty"` -} - -// GitRepoWatcherStatus defines the observed state of GitRepoWatcher. -type GitRepoWatcherStatus struct { - GitCommitHash string `json:"gitCommitHash,omitempty"` - RepoClonePath string `json:"repoClonePath,omitempty"` - LastSyncedAt metav1.Time `json:"lastSyncedAt,omitempty"` - Ready bool `json:"ready,omitempty"` - Message string `json:"message,omitempty"` -} - -// +kubebuilder:object:root=true -// +kubebuilder:resource:scope=Cluster -// +kubebuilder:subresource:status - -// GitRepoWatcher is the Schema for the gitrepowatchers API. -type GitRepoWatcher struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec GitRepoWatcherSpec `json:"spec,omitempty"` - Status GitRepoWatcherStatus `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// GitRepoWatcherList contains a list of GitRepoWatcher. -type GitRepoWatcherList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []GitRepoWatcher `json:"items"` -} - -func init() { - SchemeBuilder.Register(&GitRepoWatcher{}, &GitRepoWatcherList{}) -} diff --git a/go/rax/config/crd/bases/sync.rax.io_gitrepowatchers.yaml b/go/rax/config/crd/bases/sync.rax.io_gitrepowatchers.yaml deleted file mode 100644 index c7d21d7a6..000000000 --- a/go/rax/config/crd/bases/sync.rax.io_gitrepowatchers.yaml +++ /dev/null @@ -1,113 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.17.2 - name: gitrepowatchers.sync.rax.io -spec: - group: sync.rax.io - names: - kind: GitRepoWatcher - listKind: GitRepoWatcherList - plural: gitrepowatchers - singular: gitrepowatcher - scope: Cluster - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: GitRepoWatcher is the Schema for the gitrepowatchers API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: GitRepoWatcherSpec defines the desired state of GitRepoWatcher. - properties: - gitOrgName: - type: string - ref: - type: string - repoURL: - type: string - secrets: - items: - properties: - name: - description: Name of this secret in templates - type: string - secretRef: - description: Secret ref to fill in credentials - properties: - key: - description: |- - A key in the referenced Secret. - Some instances of this field may be defaulted, in others it may be required. - maxLength: 253 - minLength: 1 - pattern: ^[-._a-zA-Z0-9]+$ - type: string - name: - description: The name of the Secret resource being referred - to. - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - namespace: - description: |- - The namespace of the Secret resource being referred to. - Ignored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent. - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - type: object - required: - - name - - secretRef - type: object - type: array - syncIntervalSeconds: - default: 60 - type: integer - required: - - gitOrgName - - ref - - repoURL - type: object - status: - description: GitRepoWatcherStatus defines the observed state of GitRepoWatcher. - properties: - gitCommitHash: - type: string - lastSyncedAt: - format: date-time - type: string - message: - type: string - ready: - type: boolean - repoClonePath: - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/go/rax/config/crd/kustomization.yaml b/go/rax/config/crd/kustomization.yaml deleted file mode 100644 index 08122befd..000000000 --- a/go/rax/config/crd/kustomization.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# This kustomization.yaml is not intended to be run by itself, -# since it depends on service name and namespace that are out of this kustomize package. -# It should be run by config/default -resources: -- bases/sync.rax.io_nautobots.yaml -- bases/sync.rax.io_gitrepowatchers.yaml -# +kubebuilder:scaffold:crdkustomizeresource - -patches: - # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. - # patches here are for enabling the conversion webhook for each CRD - # +kubebuilder:scaffold:crdkustomizewebhookpatch - - # [WEBHOOK] To enable webhook, uncomment the following section - # the following config is for teaching kustomize how to do kustomization for CRDs. - #configurations: - #- kustomizeconfig.yaml diff --git a/go/rax/config/rbac/gitrepowatcher_admin_role.yaml b/go/rax/config/rbac/gitrepowatcher_admin_role.yaml deleted file mode 100644 index ea3022703..000000000 --- a/go/rax/config/rbac/gitrepowatcher_admin_role.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# This rule is not used by the project rax itself. -# It is provided to allow the cluster admin to help manage permissions for users. -# -# Grants full permissions ('*') over sync.rax.io. -# This role is intended for users authorized to modify roles and bindings within the cluster, -# enabling them to delegate specific permissions to other users or groups as needed. - -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/name: rax - app.kubernetes.io/managed-by: kustomize - name: gitrepowatcher-admin-role -rules: -- apiGroups: - - sync.rax.io - resources: - - gitrepowatchers - verbs: - - '*' -- apiGroups: - - sync.rax.io - resources: - - gitrepowatchers/status - verbs: - - get diff --git a/go/rax/config/rbac/gitrepowatcher_editor_role.yaml b/go/rax/config/rbac/gitrepowatcher_editor_role.yaml deleted file mode 100644 index 1d321335a..000000000 --- a/go/rax/config/rbac/gitrepowatcher_editor_role.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# This rule is not used by the project rax itself. -# It is provided to allow the cluster admin to help manage permissions for users. -# -# Grants permissions to create, update, and delete resources within the sync.rax.io. -# This role is intended for users who need to manage these resources -# but should not control RBAC or manage permissions for others. - -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/name: rax - app.kubernetes.io/managed-by: kustomize - name: gitrepowatcher-editor-role -rules: -- apiGroups: - - sync.rax.io - resources: - - gitrepowatchers - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - sync.rax.io - resources: - - gitrepowatchers/status - verbs: - - get diff --git a/go/rax/config/rbac/gitrepowatcher_viewer_role.yaml b/go/rax/config/rbac/gitrepowatcher_viewer_role.yaml deleted file mode 100644 index 7de1eab29..000000000 --- a/go/rax/config/rbac/gitrepowatcher_viewer_role.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# This rule is not used by the project rax itself. -# It is provided to allow the cluster admin to help manage permissions for users. -# -# Grants read-only access to sync.rax.io resources. -# This role is intended for users who need visibility into these resources -# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing. - -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/name: rax - app.kubernetes.io/managed-by: kustomize - name: gitrepowatcher-viewer-role -rules: -- apiGroups: - - sync.rax.io - resources: - - gitrepowatchers - verbs: - - get - - list - - watch -- apiGroups: - - sync.rax.io - resources: - - gitrepowatchers/status - verbs: - - get diff --git a/go/rax/config/samples/sync_v1alpha1_gitrepowatcher.yaml b/go/rax/config/samples/sync_v1alpha1_gitrepowatcher.yaml deleted file mode 100644 index 2b5a48ba7..000000000 --- a/go/rax/config/samples/sync_v1alpha1_gitrepowatcher.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: sync.rax.io/v1alpha1 -kind: GitRepoWatcher -metadata: - labels: - app.kubernetes.io/name: rax - app.kubernetes.io/managed-by: kustomize - name: gitrepowatcher-sample -spec: - # TODO(user): Add fields here diff --git a/go/rax/go.mod b/go/rax/go.mod deleted file mode 100644 index bd7c5ef20..000000000 --- a/go/rax/go.mod +++ /dev/null @@ -1,128 +0,0 @@ -module github.com/rackerlabs/understack/go/sync - -go 1.23.0 - -godebug default=go1.23 - -require ( - github.com/go-git/go-git/v5 v5.16.4 - github.com/golang-jwt/jwt/v5 v5.3.0 - github.com/google/go-github/v55 v55.0.0 - github.com/google/go-github/v74 v74.0.0 - github.com/nautobot/go-nautobot/v2 v2.3.2-beta - github.com/onsi/ginkgo/v2 v2.25.2 - github.com/onsi/gomega v1.38.2 - golang.org/x/oauth2 v0.30.0 - gopkg.in/yaml.v2 v2.4.0 - k8s.io/api v0.32.1 - k8s.io/apimachinery v0.32.1 - k8s.io/client-go v0.32.1 - sigs.k8s.io/controller-runtime v0.20.4 -) - -require ( - cel.dev/expr v0.18.0 // indirect - dario.cat/mergo v1.0.0 // indirect - github.com/Masterminds/semver/v3 v3.4.0 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProtonMail/go-crypto v1.1.6 // indirect - github.com/antlr4-go/antlr/v4 v4.13.0 // indirect - github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/blang/semver/v4 v4.0.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cloudflare/circl v1.6.1 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/emirpasic/gods v1.18.1 // indirect - github.com/evanphx/json-patch/v5 v5.9.11 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.2 // indirect - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.22.0 // indirect - github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/google/go-querystring v1.1.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pjbgf/sha1cd v0.3.2 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect - github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/skeema/knownhosts v1.3.1 // indirect - github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/stoewer/go-strcase v1.3.0 // indirect - github.com/x448/float16 v0.8.4 // indirect - github.com/xanzy/ssh-agent v0.3.3 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - go.uber.org/automaxprocs v1.6.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.41.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.43.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/term v0.34.0 // indirect - golang.org/x/text v0.28.0 // indirect - golang.org/x/time v0.7.0 // indirect - golang.org/x/tools v0.36.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.36.7 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/validator.v2 v2.0.1 // indirect - gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.32.1 // indirect - k8s.io/apiserver v0.32.1 // indirect - k8s.io/component-base v0.32.1 // indirect - k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect -) diff --git a/go/rax/internal/controller/gitrepowatcher_controller.go b/go/rax/internal/controller/gitrepowatcher_controller.go deleted file mode 100644 index 6e1b51013..000000000 --- a/go/rax/internal/controller/gitrepowatcher_controller.go +++ /dev/null @@ -1,130 +0,0 @@ -/* -Copyright 2025. - -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 controller - -import ( - "context" - "fmt" - "time" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - logf "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/rackerlabs/understack/go/sync/internal/git" - - syncv1alpha1 "github.com/rackerlabs/understack/go/sync/api/v1alpha1" -) - -// GitRepoWatcherReconciler reconciles a GitRepoWatcher object -type GitRepoWatcherReconciler struct { - client.Client - Scheme *runtime.Scheme -} - -// +kubebuilder:rbac:groups=sync.rax.io,resources=gitrepowatchers,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=sync.rax.io,resources=gitrepowatchers/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=sync.rax.io,resources=gitrepowatchers/finalizers,verbs=update - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the GitRepoWatcher object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile -func (r *GitRepoWatcherReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := logf.FromContext(ctx) - log.Info("Reconciling", "name", req.Name) - - var watcher syncv1alpha1.GitRepoWatcher - if err := r.Get(ctx, types.NamespacedName{Name: req.Name}, &watcher); err != nil { - return ctrl.Result{RequeueAfter: time.Duration(watcher.Spec.SyncIntervalSeconds) * time.Second}, client.IgnoreNotFound(err) - } - - repoClonePath := fmt.Sprintf("gitcache/%s", watcher.Name) - - appId, err := r.getAuthTokenFromSecretRef(ctx, watcher, "username") - if err != nil { - return ctrl.Result{RequeueAfter: time.Duration(watcher.Spec.SyncIntervalSeconds) * time.Second}, err - } - privateKeyPEM, err := r.getAuthTokenFromSecretRef(ctx, watcher, "password") - if err != nil { - return ctrl.Result{RequeueAfter: time.Duration(watcher.Spec.SyncIntervalSeconds) * time.Second}, err - } - githubToken, err := git.NewGithub(appId, watcher.Spec.GitOrgName, privateKeyPEM).GetToken(ctx) - if err != nil { - return ctrl.Result{RequeueAfter: time.Duration(watcher.Spec.SyncIntervalSeconds) * time.Second}, err - } - git := git.NewGit(repoClonePath, watcher.Spec.RepoURL, watcher.Spec.Ref, appId, githubToken) - repo, err := git.Sync() - if err != nil { - return ctrl.Result{RequeueAfter: time.Duration(watcher.Spec.SyncIntervalSeconds) * time.Second}, err - } - // Get latest commit SHA - head, err := repo.Head() - if err != nil { - return ctrl.Result{RequeueAfter: time.Duration(watcher.Spec.SyncIntervalSeconds) * time.Second}, err - } - - sha := head.Hash().String() - log.Info("Repository sync complete", "commit", sha) - - // Update status - watcher.Status.GitCommitHash = sha - watcher.Status.LastSyncedAt = metav1.Now() - watcher.Status.Ready = true - watcher.Status.RepoClonePath = repoClonePath - watcher.Status.Message = "Sync successful" - if err := r.Status().Update(ctx, &watcher); err != nil { - return ctrl.Result{RequeueAfter: time.Duration(watcher.Spec.SyncIntervalSeconds) * time.Second}, err - } - - return ctrl.Result{RequeueAfter: time.Duration(watcher.Spec.SyncIntervalSeconds) * time.Second}, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *GitRepoWatcherReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&syncv1alpha1.GitRepoWatcher{}). - Named("gitrepowatcher"). - Complete(r) -} - -func (r *GitRepoWatcherReconciler) getAuthTokenFromSecretRef(ctx context.Context, gitRepoWatcher syncv1alpha1.GitRepoWatcher, name string) (string, error) { - for _, v := range gitRepoWatcher.Spec.Secrets { - if v.Name == name { - secret := &corev1.Secret{} - err := r.Get(ctx, types.NamespacedName{Name: v.SecretRef.Name, Namespace: *v.SecretRef.Namespace}, secret) - if err != nil { - return "", err - } - // Read the secret value - if valBytes, ok := secret.Data[v.SecretRef.Key]; ok { - return string(valBytes), nil - } - return "", fmt.Errorf("secret key %s not found in secret", v.SecretRef.Key) - } - } - return "", fmt.Errorf("failed to find %s", name) -} diff --git a/go/rax/internal/controller/gitrepowatcher_controller_test.go b/go/rax/internal/controller/gitrepowatcher_controller_test.go deleted file mode 100644 index 9ae934b57..000000000 --- a/go/rax/internal/controller/gitrepowatcher_controller_test.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -Copyright 2025. - -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 controller - -import ( - "context" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - syncv1alpha1 "github.com/rackerlabs/understack/go/sync/api/v1alpha1" -) - -var _ = Describe("GitRepoWatcher Controller", func() { - Context("When reconciling a resource", func() { - const resourceName = "test-resource" - - ctx := context.Background() - - typeNamespacedName := types.NamespacedName{ - Name: resourceName, - Namespace: "default", // TODO(user):Modify as needed - } - gitrepowatcher := &syncv1alpha1.GitRepoWatcher{} - - BeforeEach(func() { - By("creating the custom resource for the Kind GitRepoWatcher") - err := k8sClient.Get(ctx, typeNamespacedName, gitrepowatcher) - if err != nil && errors.IsNotFound(err) { - resource := &syncv1alpha1.GitRepoWatcher{ - ObjectMeta: metav1.ObjectMeta{ - Name: resourceName, - Namespace: "default", - }, - // TODO(user): Specify other spec details if needed. - } - Expect(k8sClient.Create(ctx, resource)).To(Succeed()) - } - }) - - AfterEach(func() { - // TODO(user): Cleanup logic after each test, like removing the resource instance. - resource := &syncv1alpha1.GitRepoWatcher{} - err := k8sClient.Get(ctx, typeNamespacedName, resource) - Expect(err).NotTo(HaveOccurred()) - - By("Cleanup the specific resource instance GitRepoWatcher") - Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) - }) - It("should successfully reconcile the resource", func() { - By("Reconciling the created resource") - controllerReconciler := &GitRepoWatcherReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), - } - - _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ - NamespacedName: typeNamespacedName, - }) - Expect(err).NotTo(HaveOccurred()) - // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. - // Example: If you expect a certain status condition after reconciliation, verify it here. - }) - }) -}) diff --git a/go/rax/internal/controller/nautobot_controller.go b/go/rax/internal/controller/nautobot_controller.go deleted file mode 100644 index 21338372c..000000000 --- a/go/rax/internal/controller/nautobot_controller.go +++ /dev/null @@ -1,236 +0,0 @@ -/* -Copyright 2025. - -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 controller - -import ( - "context" - "crypto/sha256" - "encoding/hex" - "fmt" - "io" - "os" - "time" - - "gopkg.in/yaml.v2" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - - "sigs.k8s.io/controller-runtime/pkg/client" - logf "sigs.k8s.io/controller-runtime/pkg/log" - - syncv1alpha1 "github.com/rackerlabs/understack/go/sync/api/v1alpha1" - "github.com/rackerlabs/understack/go/sync/internal/nautobot" -) - -// NautobotReconciler reconciles a Nautobot object -type NautobotReconciler struct { - client.Client - Scheme *runtime.Scheme -} - -// +kubebuilder:rbac:groups=sync.rax.io,resources=nautobots,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=sync.rax.io,resources=nautobots/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=sync.rax.io,resources=nautobots/finalizers,verbs=update - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the Nautobot object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile -func (r *NautobotReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := logf.FromContext(ctx) - - // Fetch the Nautobot custom resource instance - var nautobotCR syncv1alpha1.Nautobot - if err := r.Get(ctx, types.NamespacedName{Name: req.Name}, &nautobotCR); err != nil { - return ctrl.Result{RequeueAfter: time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second}, client.IgnoreNotFound(err) - } - - // Fetch the GitRepoWatcher referenced in the Nautobot CR - var gitRepoWatcher syncv1alpha1.GitRepoWatcher - if err := r.Get(ctx, types.NamespacedName{Name: nautobotCR.Spec.RepoWatcher}, &gitRepoWatcher); err != nil { - return ctrl.Result{RequeueAfter: time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second}, client.IgnoreNotFound(err) - } - - // Fetch the Nautobot nautobotService to get its ClusterIP - var nautobotService corev1.Service - if err := r.Get(ctx, types.NamespacedName{Namespace: "default", Name: "nautobot-default"}, &nautobotService); err != nil { - log.Error(err, "failed to fetch Service nautobot-default") - return ctrl.Result{RequeueAfter: time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second}, err - } - - // Wait for the GitRepoWatcher to report readiness before proceeding - if !gitRepoWatcher.Status.Ready { - log.Info("git sync is not ready will retry") - return ctrl.Result{RequeueAfter: time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second}, nil - } - - // Retrieve the Nautobot authentication token from a secret or external source - nautobotAuthToken, err := r.getAuthTokenFromSecretRef(ctx, nautobotCR, "NAUTOBOT_TOKEN") - if err != nil { - log.Error(err, "failed parse find nautoBotAuthToken") - return ctrl.Result{RequeueAfter: time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second}, err - } - - configFilePath := fmt.Sprintf("%s/%s", gitRepoWatcher.Status.RepoClonePath, nautobotCR.Spec.ConfigFilePath) - - fileSha, err := sha(configFilePath) - if err != nil { - log.Error(err, "failed get sha value of file") - return ctrl.Result{RequeueAfter: time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second}, err - } - - // If last sync file sha value is same as that of current. - // That means no changes to file we will skip and retry. - if nautobotCR.Status.ConfigFileSHA == fileSha { - return ctrl.Result{RequeueAfter: time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second}, nil - } - - // Read and parse the config YAML file from the Git repo - file, err := os.ReadFile(configFilePath) - if err != nil { - log.Error(err, "failed to read file content of config file") - return ctrl.Result{RequeueAfter: time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second}, err - } - - // Parse yaml file - root, err := parseYAML(file) - if err != nil { - log.Error(err, "failed parse YAML") - return ctrl.Result{RequeueAfter: time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second}, err - } - - for i := range root.InstanceLocations { - setDisplayPath(&root.InstanceLocations[i], "") - } - - nb := nautobot.NewNautobotClient(fmt.Sprintf("http://%s/api", nautobotService.Spec.ClusterIP), nautobotAuthToken) - - // Sync all instance locations with Nautobot - err = nb.SyncAllLocationTypes(ctx, root.LocationTypes) - if err != nil { - return ctrl.Result{RequeueAfter: time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second}, err - } - - // Sync all rack groups with Nautobot - err = nb.SyncAllLocations(ctx, root.InstanceLocations) - if err != nil { - return ctrl.Result{RequeueAfter: time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second}, err - } - - // Sync all rack groups with Nautobot - err = nb.SyncRackGroup(ctx, root.RackGroup) - if err != nil { - return ctrl.Result{RequeueAfter: time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second}, err - } - - // Sync all racks with Nautobot - err = nb.SyncRack(ctx, root.Rack) - if err != nil { - return ctrl.Result{RequeueAfter: time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second}, err - } - - // Update status - nautobotCR.Status.ConfigFileSHA = fileSha - nautobotCR.Status.GitCommitHash = gitRepoWatcher.Status.GitCommitHash - nautobotCR.Status.LastSyncedAt = metav1.Now() - nautobotCR.Status.Ready = true - nautobotCR.Status.Message = "Sync successful" - if err := r.Status().Update(ctx, &nautobotCR); err != nil { - return ctrl.Result{RequeueAfter: time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second}, err - } - // Successfully completed reconciliation; requeue after configured sync interval - return ctrl.Result{RequeueAfter: time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second}, nil -} - -// getAuthTokenFromSecretRef: this will fetch Nautobot auth token from the given refer. -func (r *NautobotReconciler) getAuthTokenFromSecretRef(ctx context.Context, nautobotCR syncv1alpha1.Nautobot, name string) (string, error) { - for _, v := range nautobotCR.Spec.Secrets { - if v.Name == name { - secret := &corev1.Secret{} - err := r.Get(ctx, types.NamespacedName{Name: v.SecretRef.Name, Namespace: *v.SecretRef.Namespace}, secret) - if err != nil { - return "", err - } - // Read the secret value - if valBytes, ok := secret.Data[v.SecretRef.Key]; ok { - return string(valBytes), nil - } - return "", fmt.Errorf("secret key %s not found in secret", v.SecretRef.Key) - } - } - return "", fmt.Errorf("failed to find %s", name) -} - -// SetupWithManager sets up the controller with the Manager. -func (r *NautobotReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&syncv1alpha1.Nautobot{}). - Named("nautobot"). - Complete(r) -} - -func parseYAML(data []byte) (*nautobot.NautobotYAML, error) { - var cfg nautobot.NautobotYAML - if err := yaml.Unmarshal(data, &cfg); err != nil { - return nil, err - } - return &cfg, nil -} - -func setDisplayPath(loc *nautobot.Location, parentPath string) { - if parentPath == "" { - loc.Display = loc.Name - } else { - loc.Display = parentPath + " → " + loc.Name - } - for i := range loc.Children { - setDisplayPath(&loc.Children[i], loc.Display) - } -} - -// IsGitRepoReady checks if the GitRepoWatcher has Ready condition set to True -func IsGitRepoReady(watcher *syncv1alpha1.GitRepoWatcher) bool { - if watcher == nil { - return false - } - - return watcher.Status.Ready -} - -func sha(filepath string) (string, error) { - file, err := os.Open(filepath) - if err != nil { - return "", fmt.Errorf("failed to open file: %w", err) - } - defer file.Close() //nolint:errcheck - - hash := sha256.New() - if _, err := io.Copy(hash, file); err != nil { - return "", fmt.Errorf("failed to read file: %w", err) - } - - sum := hash.Sum(nil) - return hex.EncodeToString(sum), nil -} diff --git a/go/rax/internal/git/git.go b/go/rax/internal/git/git.go deleted file mode 100644 index d2fa5685a..000000000 --- a/go/rax/internal/git/git.go +++ /dev/null @@ -1,173 +0,0 @@ -package git - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" - githttp "github.com/go-git/go-git/v5/plumbing/transport/http" -) - -type Git struct { - clonePath string - remoteURL string - ref string - username string - password string -} - -func NewGit(clonePath, remoteURL, ref, username, password string) *Git { - return &Git{ - clonePath: clonePath, - remoteURL: remoteURL, - ref: ref, - username: username, - password: password, - } -} - -// Sync clones if missing or dirty, then pulls latest changes. -func (g *Git) Sync() (*git.Repository, error) { - repo, err := git.PlainOpen(g.clonePath) - if err == git.ErrRepositoryNotExists { - if err := g.Clone(); err != nil { - return nil, fmt.Errorf("failed to clone repo: %w", err) - } - return git.PlainOpen(g.clonePath) - } else if err != nil { - return nil, fmt.Errorf("failed to open repo: %w", err) - } - - dirty, err := g.IsDirty() - if err != nil { - return nil, fmt.Errorf("failed to check if repo is dirty: %w", err) - } - if dirty { - // Remove the existing clone - if err := os.RemoveAll(g.clonePath); err != nil { - return nil, fmt.Errorf("failed to delete dirty repo: %w", err) - } - // Reclone fresh - if err := g.Clone(); err != nil { - return nil, fmt.Errorf("failed to re-clone dirty repo: %w", err) - } - return git.PlainOpen(g.clonePath) - } - - if err := g.Pull(); err != nil && err != git.NoErrAlreadyUpToDate { - return nil, fmt.Errorf("failed to pull repo: %w", err) - } - - return repo, nil -} - -// Clone performs a shallow clone of the specified branch. -func (g *Git) Clone() error { - _, err := git.PlainClone(g.clonePath, false, &git.CloneOptions{ - URL: g.remoteURL, - ReferenceName: plumbing.NewBranchReferenceName(g.ref), - SingleBranch: true, - Depth: 1, - Auth: &githttp.BasicAuth{ - Username: g.username, - Password: g.password, - }, - }) - return err -} - -// Pull updates the local repo by pulling changes from remote. -func (g *Git) Pull() error { - repo, err := git.PlainOpen(g.clonePath) - if err != nil { - return fmt.Errorf("open repo for pull: %w", err) - } - - worktree, err := repo.Worktree() - if err != nil { - return fmt.Errorf("get worktree: %w", err) - } - - return worktree.Pull(&git.PullOptions{ - RemoteName: "origin", - ReferenceName: plumbing.NewBranchReferenceName(g.ref), - SingleBranch: true, - Depth: 1, - Force: true, - Auth: &githttp.BasicAuth{ - Username: g.username, - Password: g.password, - }, - }) -} - -// GetLastFileCommitSHA returns the last commit SHA that modified a file. -func (g *Git) GetLastFileCommitSHA(filePath string) (string, error) { - repo, err := git.PlainOpen(g.clonePath) - if err != nil { - return "", fmt.Errorf("open repo: %w", err) - } - - ref, err := repo.Head() - if err != nil { - return "", fmt.Errorf("get HEAD: %w", err) - } - - logIter, err := repo.Log(&git.LogOptions{From: ref.Hash()}) - if err != nil { - return "", fmt.Errorf("get commit log: %w", err) - } - defer logIter.Close() //nolint:errcheck - - normalizedPath := filepath.ToSlash(filePath) - - for { - commit, err := logIter.Next() - if err != nil { - break - } - - files, err := commit.Files() - if err != nil { - return "", fmt.Errorf("get commit files: %w", err) - } - - var found bool - _ = files.ForEach(func(f *object.File) error { - if f.Name == normalizedPath { - found = true - return nil - } - return nil - }) - - if found { - return commit.Hash.String(), nil - } - } - - return "", fmt.Errorf("no commit found for file: %s", filePath) -} - -// IsDirty returns true if the working directory has uncommitted changes. -func (g *Git) IsDirty() (bool, error) { - repo, err := git.PlainOpen(g.clonePath) - if err != nil { - return false, fmt.Errorf("open repo: %w", err) - } - - worktree, err := repo.Worktree() - if err != nil { - return false, fmt.Errorf("get worktree: %w", err) - } - - status, err := worktree.Status() - if err != nil { - return false, fmt.Errorf("get status: %w", err) - } - - return !status.IsClean(), nil -} diff --git a/go/rax/internal/git/github.go b/go/rax/internal/git/github.go deleted file mode 100644 index de5bee59b..000000000 --- a/go/rax/internal/git/github.go +++ /dev/null @@ -1,82 +0,0 @@ -package git - -import ( - "context" - "crypto/rsa" - "encoding/pem" - "fmt" - "time" - - "github.com/golang-jwt/jwt/v5" - "github.com/google/go-github/v55/github" - "golang.org/x/oauth2" -) - -type Github struct { - appID string - orgName string - privateKeyPEM string -} - -func NewGithub(appID, orgName, privateKeyPEM string) *Github { - return &Github{ - appID: appID, - orgName: orgName, - privateKeyPEM: privateKeyPEM, - } -} - -func (g *Github) GetToken(ctx context.Context) (string, error) { - _, token, err := g.getGHClientToken(ctx) - return token, err -} - -func (g *Github) parsePEMPrivateKey(pemStr string) (*rsa.PrivateKey, error) { - block, _ := pem.Decode([]byte(pemStr)) - if block == nil { - return nil, fmt.Errorf("invalid PEM format") - } - return jwt.ParseRSAPrivateKeyFromPEM([]byte(pemStr)) -} - -func (g *Github) generateJWT(appID string, key *rsa.PrivateKey) (string, error) { - now := time.Now() - claims := jwt.RegisteredClaims{ - Issuer: appID, - IssuedAt: jwt.NewNumericDate(now), - ExpiresAt: jwt.NewNumericDate(now.Add(time.Minute * 10)), - } - token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) - return token.SignedString(key) -} - -func (g *Github) getGHClientToken(ctx context.Context) (*github.Client, string, error) { - privateKey, err := g.parsePEMPrivateKey(g.privateKeyPEM) - if err != nil { - return nil, "", fmt.Errorf("error parsing private key: %v", err) - } - - jwtToken, err := g.generateJWT(g.appID, privateKey) - if err != nil { - return nil, "", fmt.Errorf("error generating JWT: %v", err) - } - - ghClient := github.NewTokenClient(ctx, jwtToken) - installation, _, err := ghClient.Apps.FindOrganizationInstallation(ctx, g.orgName) - if err != nil { - return nil, "", fmt.Errorf("error listing installation for %s: %v", g.orgName, err) - } - - installationID := installation.GetID() - installationToken, _, err := ghClient.Apps.CreateInstallationToken(ctx, installationID, nil) - if err != nil { - return nil, "", fmt.Errorf("error getting installation token: %v", err) - } - - token := installationToken.GetToken() - tokenSrc := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}) - oauthClient := oauth2.NewClient(ctx, tokenSrc) - client := github.NewClient(oauthClient) - - return client, token, nil -} diff --git a/go/rax/internal/nautobot/extras.go b/go/rax/internal/nautobot/extras.go deleted file mode 100644 index 9eb888e0f..000000000 --- a/go/rax/internal/nautobot/extras.go +++ /dev/null @@ -1,36 +0,0 @@ -package nautobot - -import ( - "context" - "fmt" - "log" - - nb "github.com/nautobot/go-nautobot/v2" -) - -// GetAllContentTypes retrieves all content types from Nautobot. -// It uses a default limit of 1000 and a depth of 1. -// Returns a slice of ContentType objects or nil if an error occurs. -func (n *NautobotClient) GetAllContentTypes(ctx context.Context) ([]nb.ContentType, error) { - // Execute the API request to list content types. - list, resp, err := n.Client.ExtrasAPI.ExtrasContentTypesList(ctx). - Limit(1000). - Depth(0). - Execute() - - // Handle potential errors from the API request. - if err != nil { - // Log the error and the response body for debugging. - log.Printf("error fetching content types: %v", err) - logResponseBody(resp) - return nil, fmt.Errorf("failed to list content types: %w", err) - } - - // Check if results are present. - if list == nil || list.Results == nil { - log.Println("no content types found or results are nil.") - return []nb.ContentType{}, nil // Return an empty slice instead of nil for no results. - } - - return list.Results, nil -} diff --git a/go/rax/internal/nautobot/helpers.go b/go/rax/internal/nautobot/helpers.go deleted file mode 100644 index 2be59ad9e..000000000 --- a/go/rax/internal/nautobot/helpers.go +++ /dev/null @@ -1,27 +0,0 @@ -package nautobot - -import nb "github.com/nautobot/go-nautobot/v2" - -func buildBulkWritableCableRequestStatus(uuid string) *nb.BulkWritableCableRequestStatus { - return &nb.BulkWritableCableRequestStatus{ - Id: &nb.BulkWritableCableRequestStatusId{ - String: nb.PtrString(uuid), - }, - } -} - -func buildNullableBulkWritableCircuitRequestTenant(uuid string) nb.NullableBulkWritableCircuitRequestTenant { - return *nb.NewNullableBulkWritableCircuitRequestTenant(&nb.BulkWritableCircuitRequestTenant{ - Id: &nb.BulkWritableCableRequestStatusId{ - String: nb.PtrString(uuid), - }, - }) -} - -func buildNullableBulkWritableRackRequestRackGroup(uuid string) *nb.NullableBulkWritableRackRequestRackGroup { - return nb.NewNullableBulkWritableRackRequestRackGroup(&nb.BulkWritableRackRequestRackGroup{ - Id: &nb.BulkWritableCableRequestStatusId{ - String: nb.PtrString(uuid), - }, - }) -} diff --git a/go/rax/internal/nautobot/locationType.go b/go/rax/internal/nautobot/locationType.go deleted file mode 100644 index 19e47e88a..000000000 --- a/go/rax/internal/nautobot/locationType.go +++ /dev/null @@ -1,153 +0,0 @@ -package nautobot - -import ( - "context" - "fmt" - "log" - "strings" - - nb "github.com/nautobot/go-nautobot/v2" -) - -// LocationType defines the structure for a location type, including its children for hierarchical setup. -type LocationType struct { - ID string `yaml:"id,omitempty"` - Name string `yaml:"name"` - Description string `yaml:"description"` - Nestable bool `yaml:"nestable"` - Status string `yaml:"status"` // Note: Status field is defined but not used in create/update logic below. - ContentTypes []string `yaml:"content_types"` - Children []LocationType `yaml:"children,omitempty"` -} - -func (n *NautobotClient) SyncAllLocationTypes(ctx context.Context, rootLocations []LocationType) error { - for _, locationType := range rootLocations { - if err := n.syncChildLocationTypesRecursive(ctx, &locationType, nil); err != nil { - return fmt.Errorf("failed to sync root location types %s: %w", locationType.Name, err) - } - } - return nil -} - -// CreateLocationType creates a new location type in Nautobot. -func (n *NautobotClient) CreateLocationType(ctx context.Context, req nb.LocationTypeRequest) (*nb.LocationType, error) { - locationType, resp, err := n.Client.DcimAPI.DcimLocationTypesCreate(ctx).LocationTypeRequest(req).Execute() - if err != nil { - logResponseBody(resp) - return nil, fmt.Errorf("API error creating location type %s: %w", req.Name, err) - } - log.Printf("Created location type: %s (ID: %s)", locationType.Name, locationType.Id) - return locationType, nil -} - -// UpdateLocationType updates an existing location type in Nautobot. -func (n *NautobotClient) UpdateLocationType(ctx context.Context, id string, req nb.PatchedLocationTypeRequest) (*nb.LocationType, error) { - locationType, resp, err := n.Client.DcimAPI.DcimLocationTypesPartialUpdate(ctx, id).PatchedLocationTypeRequest(req).Execute() - if err != nil { - logResponseBody(resp) - return nil, fmt.Errorf("API error updating location type ID %s: %w", id, err) - } - log.Printf("Updated location type: %s (ID: %s)", locationType.Name, id) - return locationType, nil -} - -// FindLocationType retrieves the ID of a location type by its name using an API call. -// Note: For bulk operations, consider using GetAllLocationTypesMap. -func (n *NautobotClient) FindLocationType(ctx context.Context, name string) nb.LocationType { - list, _, err := n.Client.DcimAPI.DcimLocationTypesList(ctx).Name([]string{name}).Depth(0).Limit(1).Execute() // Filter by name - if err != nil { - log.Printf("Error fetching location type ID for name '%s': %v", name, err) - return nb.LocationType{} - } - if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { - return nb.LocationType{} - } - return list.Results[0] -} - -func (n *NautobotClient) syncChildLocationTypesRecursive(ctx context.Context, inputLocations *LocationType, parentLocation *nb.LocationType) error { - location, err := n.syncOrUpdateLocationTypes(ctx, inputLocations, parentLocation) - if err != nil { - return err - } - for _, childLocation := range inputLocations.Children { - if err := n.syncChildLocationTypesRecursive(ctx, &childLocation, location); err != nil { - return err - } - } - return nil -} - -func (n *NautobotClient) syncOrUpdateLocationTypes(ctx context.Context, input *LocationType, parent *nb.LocationType) (*nb.LocationType, error) { - existing := n.FindLocationType(ctx, input.Name) - if existing.Id == "" { - req := n.newCreateLocationTypeRequest(ctx, input, parent) - newLocType, err := n.CreateLocationType(ctx, req) - if err != nil { - return nil, fmt.Errorf("failed to create location type %q: %w", input.Name, err) - } - return newLocType, nil - } - - req := n.newUpdateLocationTypeRequest(ctx, input, parent) - updatedLocType, err := n.UpdateLocationType(ctx, existing.Id, req) - if err != nil { - return nil, fmt.Errorf("failed to update location type %q: %w", input.Name, err) - } - return updatedLocType, nil -} - -func (n *NautobotClient) newCreateLocationTypeRequest(ctx context.Context, loc *LocationType, parent *nb.LocationType) nb.LocationTypeRequest { - var contentTypesIds []string - getAllContentType, _ := n.GetAllContentTypes(ctx) - for _, v := range loc.ContentTypes { - c := filterContentTypeID(getAllContentType, v) - if c != nil { - contentTypesIds = append(contentTypesIds, fmt.Sprintf("%s.%s", c.AppLabel, c.Model)) - } - } - - payload := nb.LocationTypeRequest{ - Name: loc.Name, - Description: &loc.Description, - ContentTypes: contentTypesIds, - } - if parent != nil { - payload.Parent = buildNullableBulkWritableCircuitRequestTenant(parent.Id) - } - return payload -} - -func (n *NautobotClient) newUpdateLocationTypeRequest(ctx context.Context, loc *LocationType, parent *nb.LocationType) nb.PatchedLocationTypeRequest { - var contentTypes []string - getAllContentType, _ := n.GetAllContentTypes(ctx) - for _, v := range loc.ContentTypes { - c := filterContentTypeID(getAllContentType, v) - if c != nil { - contentTypes = append(contentTypes, fmt.Sprintf("%s.%s", c.AppLabel, c.Model)) - } - } - - payload := nb.PatchedLocationTypeRequest{ - Description: &loc.Description, - Nestable: &loc.Nestable, - ContentTypes: contentTypes, - } - if parent != nil { - payload.Parent = buildNullableBulkWritableCircuitRequestTenant(parent.Id) - } - return payload -} - -// filterContentTypeID searches for a content type by various name formats within a list of content types. -func filterContentTypeID(list []nb.ContentType, name string) *nb.ContentType { - for i, v := range list { - // Check common ways a content type might be referenced. - if strings.EqualFold(v.Display, name) || // e.g., "DCIM | Interface" - strings.EqualFold(fmt.Sprintf("%s | %s", v.AppLabel, v.Model), name) || // e.g., "dcim | interface" - strings.EqualFold(fmt.Sprintf("%s.%s", v.AppLabel, v.Model), name) { // e.g., "dcim.interface" - return &list[i] - } - } - return nil -} diff --git a/go/rax/internal/nautobot/locations.go b/go/rax/internal/nautobot/locations.go deleted file mode 100644 index 724e7757c..000000000 --- a/go/rax/internal/nautobot/locations.go +++ /dev/null @@ -1,197 +0,0 @@ -package nautobot - -import ( - "context" - "fmt" - "io" - "log" - "net/http" - - nb "github.com/nautobot/go-nautobot/v2" -) - -type Location struct { - ID string `yaml:"id,omitempty"` - Name string `yaml:"name"` - Description string `yaml:"description"` - LocationType string `yaml:"location_type"` - Status string `yaml:"status"` - Children []Location `yaml:"children,omitempty"` - Display string `yaml:"-"` -} - -func (n *NautobotClient) SyncAllLocations(ctx context.Context, rootLocations []Location) error { - for _, location := range rootLocations { - if err := n.syncChildLocationsRecursive(ctx, &location, nil); err != nil { - return fmt.Errorf("failed to sync root location %s: %w", location.Name, err) - } - } - - return n.DeleteOrphanedLocations(ctx, rootLocations) -} - -func (n *NautobotClient) CreateNewLocation(ctx context.Context, req nb.LocationRequest) (*nb.Location, error) { - loc, resp, err := n.Client.DcimAPI.DcimLocationsCreate(ctx).LocationRequest(req).Execute() - if err != nil { - logResponseBody(resp) - return nil, err - } - log.Printf("Created location: %s", loc.Display) - return loc, nil -} - -func (n *NautobotClient) UpdateLocation(ctx context.Context, id string, req nb.PatchedLocationRequest) (*nb.Location, error) { - loc, resp, err := n.Client.DcimAPI.DcimLocationsPartialUpdate(ctx, id).PatchedLocationRequest(req).Execute() - if err != nil { - logResponseBody(resp) - return nil, err - } - log.Printf("Updated location: id %s: Name:%s (%s)", id, loc.Name, loc.Display) - return loc, nil -} - -func (n *NautobotClient) FindLocationByName(ctx context.Context, name string) nb.Location { - list, _, err := n.Client.DcimAPI.DcimLocationsList(ctx).Depth(10).Name([]string{name}).Execute() - if err != nil { - log.Printf("Error fetching location type ID for name '%s': %v", name, err) - return nb.Location{} - } - if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { - return nb.Location{} - } - return list.Results[0] -} - -func (n *NautobotClient) FindLocationByNameAndDisplayName(ctx context.Context, name, display string) nb.Location { - list, _, err := n.Client.DcimAPI.DcimLocationsList(ctx).Depth(10).Execute() - if err != nil { - return nb.Location{} - } - - for _, v := range list.Results { - if v.Name == name && v.Display == display { - return v - } - } - - return nb.Location{} -} - -func (n *NautobotClient) GetAllNautobotLocations(ctx context.Context) ([]nb.Location, error) { - offset := int32(0) - limit := int32(1000) - - resp, _, err := n.Client.DcimAPI.DcimLocationsList(ctx). - Limit(limit). - Offset(offset). - Execute() - - if err != nil { - return nil, err - } - return resp.Results, nil -} - -func (n *NautobotClient) DeleteOrphanedLocations(ctx context.Context, yamlRootLocations []Location) error { - displayPaths := make(map[string]struct{}) - for _, root := range yamlRootLocations { - collectDisplayPaths(&root, displayPaths) - } - - existingLocations, err := n.GetAllNautobotLocations(ctx) - if err != nil { - return fmt.Errorf("failed to fetch Nautobot locations: %w", err) - } - - for _, location := range existingLocations { - if _, exists := displayPaths[location.Display]; !exists { - log.Printf("Deleting orphaned location: %s", location.Name) - if _, err := n.Client.DcimAPI.DcimLocationsDestroy(ctx, location.Id).Execute(); err != nil { - log.Printf("Failed to delete location %s: %v", location.Name, err) - } - } - } - - return nil -} - -func (n *NautobotClient) syncChildLocationsRecursive(ctx context.Context, inputLocations *Location, parentLocation *nb.Location) error { - location, err := n.syncOrUpdate(ctx, inputLocations, parentLocation) - if err != nil { - return err - } - for _, childLocation := range inputLocations.Children { - if err := n.syncChildLocationsRecursive(ctx, &childLocation, location); err != nil { - return err - } - } - return nil -} - -func newCreateLocationRequest(loc *Location, parent *nb.Location, statusID, typeID string) nb.LocationRequest { - payload := nb.LocationRequest{ - Name: loc.Name, - Description: &loc.Description, - Status: *buildBulkWritableCableRequestStatus(statusID), - LocationType: *buildBulkWritableCableRequestStatus(typeID), - } - if parent != nil { - payload.Parent = buildNullableBulkWritableCircuitRequestTenant(parent.Id) - } - return payload -} - -func newUpdateLocationRequest(loc *Location, parent *nb.Location, statusID, typeID string) nb.PatchedLocationRequest { - payload := nb.PatchedLocationRequest{ - Description: &loc.Description, - Status: buildBulkWritableCableRequestStatus(statusID), - LocationType: buildBulkWritableCableRequestStatus(typeID), - } - if parent != nil { - payload.Parent = buildNullableBulkWritableCircuitRequestTenant(parent.Id) - } - return payload -} - -func collectDisplayPaths(loc *Location, displayPathSet map[string]struct{}) { - displayPathSet[loc.Display] = struct{}{} - for i := range loc.Children { - collectDisplayPaths(&loc.Children[i], displayPathSet) - } -} - -func (n *NautobotClient) syncOrUpdate(ctx context.Context, input *Location, parent *nb.Location) (*nb.Location, error) { - existingLocation := n.FindLocationByNameAndDisplayName(ctx, input.Name, input.Display) - status := n.FindStatus(ctx, input.Status) - locationType := n.FindLocationType(ctx, input.LocationType) - - if existingLocation.Name == "" { - createRequest := newCreateLocationRequest(input, parent, status.Id, locationType.Id) - newLocation, err := n.CreateNewLocation(ctx, createRequest) - if err != nil { - return nil, fmt.Errorf("create failed for location %s: %w", input.Name, err) - } - return newLocation, nil - } - - updateRequest := newUpdateLocationRequest(input, parent, status.Id, locationType.Id) - updatedLocation, err := n.UpdateLocation(ctx, existingLocation.Id, updateRequest) - if err != nil { - return nil, fmt.Errorf("update failed for location %s: %w", existingLocation.Name, err) - } - - return updatedLocation, nil -} - -func logResponseBody(resp *http.Response) { - if resp.Body == nil { - return - } - body, err := io.ReadAll(resp.Body) - if err != nil { - log.Printf("failed to read response body") - return - } - defer resp.Body.Close() //nolint:errcheck - log.Printf("Create error: %s", string(body)) -} diff --git a/go/rax/internal/nautobot/rack.go b/go/rax/internal/nautobot/rack.go deleted file mode 100644 index e96709e0f..000000000 --- a/go/rax/internal/nautobot/rack.go +++ /dev/null @@ -1,94 +0,0 @@ -package nautobot - -import ( - "context" - "fmt" - "log" - - nb "github.com/nautobot/go-nautobot/v2" -) - -type Rack struct { - Name string `yaml:"name"` - Description string `yaml:"description"` - Location string `yaml:"location"` - Group string `yaml:"group"` - Role string `yaml:"role"` - Height int `yaml:"u_height"` - FacilityID string `yaml:"facility_id"` - Status string `yaml:"status"` -} - -func (n *NautobotClient) SyncRack(ctx context.Context, racks []Rack) error { - for _, rack := range racks { - _, err := n.syncOrUpdateRack(ctx, &rack) - if err != nil { - log.Printf("failed to sync rack %s: %v", rack.Name, err) - } - } - return nil -} - -func (n *NautobotClient) syncOrUpdateRack(ctx context.Context, input *Rack) (*nb.Rack, error) { - existing := n.FindRackByName(ctx, input.Name) - location := n.FindLocationByName(ctx, input.Location) - group := n.FindRackGroupByName(ctx, input.Group) - status := n.FindStatus(ctx, input.Status) - - if existing.Id == "" { - req := newCreateRackRequest(input, location.Id, group.Id, status.Id) - return n.CreateRack(ctx, req) - } - - req := newUpdateRackRequest(input, location.Id, group.Id, status.Id) - return n.UpdateRack(ctx, existing.Id, req) -} - -func (n *NautobotClient) CreateRack(ctx context.Context, req nb.WritableRackRequest) (*nb.Rack, error) { - rack, resp, err := n.Client.DcimAPI.DcimRacksCreate(ctx).WritableRackRequest(req).Execute() - if err != nil { - logResponseBody(resp) - return nil, fmt.Errorf("api error creating rack %s: %w", req.Name, err) - } - log.Printf("created rack: %s (ID: %s)", rack.Name, rack.Id) - return rack, nil -} - -func (n *NautobotClient) UpdateRack(ctx context.Context, id string, req nb.PatchedWritableRackRequest) (*nb.Rack, error) { - rack, resp, err := n.Client.DcimAPI.DcimRacksPartialUpdate(ctx, id).PatchedWritableRackRequest(req).Execute() - if err != nil { - logResponseBody(resp) - return nil, err - } - log.Printf("updated rack: id %s: Name:%s (%s)", id, rack.Name, rack.Display) - return rack, nil -} - -func (n *NautobotClient) FindRackByName(ctx context.Context, name string) nb.Rack { - list, _, err := n.Client.DcimAPI.DcimRacksList(ctx).Limit(1000).Depth(0).Name([]string{name}).Execute() - if err != nil || len(list.Results) == 0 { - return nb.Rack{} - } - return list.Results[0] -} - -// Helpers to build nullable/bulk objects for Nautobot API payloads - -func newCreateRackRequest(r *Rack, locationID, groupID, statusID string) nb.WritableRackRequest { - return nb.WritableRackRequest{ - Name: r.Name, - Location: *buildBulkWritableCableRequestStatus(locationID), - RackGroup: *buildNullableBulkWritableRackRequestRackGroup(groupID), - Status: *buildBulkWritableCableRequestStatus(statusID), - UHeight: nb.PtrInt32(int32(r.Height)), - } -} - -func newUpdateRackRequest(r *Rack, locationID, groupID, statusID string) nb.PatchedWritableRackRequest { - return nb.PatchedWritableRackRequest{ - Location: buildBulkWritableCableRequestStatus(locationID), - RackGroup: *buildNullableBulkWritableRackRequestRackGroup(groupID), - Status: buildBulkWritableCableRequestStatus(statusID), - UHeight: nb.PtrInt32(int32(r.Height)), - } -} diff --git a/go/rax/internal/nautobot/rackGroup.go b/go/rax/internal/nautobot/rackGroup.go deleted file mode 100644 index 0250768dc..000000000 --- a/go/rax/internal/nautobot/rackGroup.go +++ /dev/null @@ -1,102 +0,0 @@ -package nautobot - -import ( - "context" - "fmt" - "log" - - nb "github.com/nautobot/go-nautobot/v2" -) - -type RackGroup struct { - Name string `yaml:"name"` - Description string `yaml:"description"` - Location string `yaml:"location"` -} - -func (n *NautobotClient) SyncRackGroup(ctx context.Context, rootLocations []RackGroup) error { - for _, location := range rootLocations { - _, err := n.syncOrUpdateRackGroup(ctx, &location, nil) - if err != nil { - return err - } - } - return nil -} - -func (n *NautobotClient) CreateRackGroup(ctx context.Context, req nb.RackGroupRequest) (*nb.RackGroup, error) { - rackGroup, resp, err := n.Client.DcimAPI.DcimRackGroupsCreate(ctx).RackGroupRequest(req).Execute() - if err != nil { - logResponseBody(resp) - return nil, fmt.Errorf("api error rack group %s: %w", req.Name, err) - } - log.Printf("created rack group: %s (ID: %s)", rackGroup.Name, rackGroup.Id) - return rackGroup, nil -} - -func (n *NautobotClient) UpdateRackGroup(ctx context.Context, id string, req nb.PatchedRackGroupRequest) (*nb.RackGroup, error) { - loc, resp, err := n.Client.DcimAPI.DcimRackGroupsPartialUpdate(ctx, id).PatchedRackGroupRequest(req).Execute() - if err != nil { - logResponseBody(resp) - return nil, err - } - log.Printf("updated rackgroup: id %s: Name:%s (%s)", id, loc.Name, loc.Display) - return loc, nil -} - -func (n *NautobotClient) FindRackGroupByName(ctx context.Context, name string) nb.RackGroup { - list, _, err := n.Client.DcimAPI.DcimRackGroupsList(ctx).Limit(1000).Depth(0).Name([]string{name}).Execute() - if err != nil { - log.Printf("error fetching rack group by name '%s': %v", name, err) - return nb.RackGroup{} - } - if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" { - return nb.RackGroup{} - } - return list.Results[0] -} - -func (n *NautobotClient) syncOrUpdateRackGroup(ctx context.Context, input *RackGroup, parent *nb.RackGroup) (*nb.RackGroup, error) { - existingRackGroupId := n.FindRackGroupByName(ctx, input.Name).Id - existingLocationId := n.FindLocationByName(ctx, input.Location).Id - - if existingRackGroupId == "" { - createRequest := newCreateRackGroupRequest(input, parent, existingLocationId) - newLocation, err := n.CreateRackGroup(ctx, createRequest) - if err != nil { - return nil, fmt.Errorf("create failed for rack group %s: %w", input.Name, err) - } - return newLocation, nil - } - - updateRequest := newUpdateRackGroupRequest(input, parent, existingLocationId) - updatedLocation, err := n.UpdateRackGroup(ctx, existingRackGroupId, updateRequest) - if err != nil { - return nil, fmt.Errorf("update failed for rack group %s: %w", updatedLocation.Name, err) - } - - return updatedLocation, nil -} - -func newCreateRackGroupRequest(loc *RackGroup, parent *nb.RackGroup, location string) nb.RackGroupRequest { - payload := nb.RackGroupRequest{ - Name: loc.Name, - Description: &loc.Description, - Location: *buildBulkWritableCableRequestStatus(location), - } - if parent != nil { - payload.Parent = buildNullableBulkWritableCircuitRequestTenant(parent.Id) - } - return payload -} - -func newUpdateRackGroupRequest(loc *RackGroup, parent *nb.RackGroup, location string) nb.PatchedRackGroupRequest { - payload := nb.PatchedRackGroupRequest{ - Description: &loc.Description, - Location: buildBulkWritableCableRequestStatus(location), - } - if parent != nil { - payload.Parent = buildNullableBulkWritableCircuitRequestTenant(parent.Id) - } - return payload -} diff --git a/go/rax/internal/nautobot/status.go b/go/rax/internal/nautobot/status.go deleted file mode 100644 index 2dfde4277..000000000 --- a/go/rax/internal/nautobot/status.go +++ /dev/null @@ -1,25 +0,0 @@ -package nautobot - -import ( - "context" - "log" - - nb "github.com/nautobot/go-nautobot/v2" -) - -// FindStatusID retrieves the ID of a status by its name via an API call. -// Returns an empty string if the status is not found or if an error occurs. -func (n *NautobotClient) FindStatus(ctx context.Context, name string) nb.Status { - list, _, err := n.Client.ExtrasAPI.ExtrasStatusesList(ctx).Depth(0).Name([]string{name}).Execute() - if err != nil { - log.Printf("failed to fetch status by name '%s': %v", name, err) - return nb.Status{} - } - - if list == nil || list.Results == nil || len(list.Results) == 0 { - log.Printf("status with name '%s' not found.", name) // Optional: log if not found - return nb.Status{} - } - - return list.Results[0] -}