diff --git a/.github/workflows/validate-openapi.yml b/.github/workflows/validate-openapi.yml index be9fc3e9..a099af9a 100644 --- a/.github/workflows/validate-openapi.yml +++ b/.github/workflows/validate-openapi.yml @@ -31,6 +31,9 @@ jobs: run: | if [[ `git status --porcelain` ]]; then echo "### Error: Changes detected after running make generate-api" + echo "### git status --porcelain" + git status --porcelain + echo "### git diff" git diff echo "### Error: Changes detected after running make generate-api" >> $GITHUB_STEP_SUMMARY exit 1 diff --git a/Makefile b/Makefile index 94006a74..1243d671 100644 --- a/Makefile +++ b/Makefile @@ -290,8 +290,8 @@ helm-clean: ## Clean helm chart build annotations. .PHONY: helm-test helm-test: ## Template the charts. - for d in $(HELM_DIRS); do \ - helm template intel $$d; \ + @for d in $(HELM_DIRS); do \ + helm --debug template --namespace orch-cluster intel $$d; \ done .PHONY: helm-build @@ -504,9 +504,9 @@ redeploy: docker-build docker-load ## Redeploy the pod with the latest codes. generate-api: check-oapi-codegen-version ## Generate Go client, server, client and types from OpenAPI spec with oapi-codegen @echo "Generating..." oapi-codegen -generate spec -o pkg/api/spec.gen.go -package api api/openapi/openapi.yaml - oapi-codegen -generate client -o pkg/api/client.gen.go -package api api/openapi/openapi.yaml - oapi-codegen -generate types -o pkg/api/types.gen.go -package api api/openapi/openapi.yaml - oapi-codegen -generate std-http,strict-server -o pkg/api/server.gen.go -package api api/openapi/openapi.yaml + oapi-codegen -generate client -o pkg/api/client.gen.go -exclude-tags metrics -package api api/openapi/openapi.yaml + oapi-codegen -generate types -o pkg/api/types.gen.go -exclude-tags metrics -package api api/openapi/openapi.yaml + oapi-codegen -generate std-http,strict-server -exclude-tags metrics -o pkg/api/server.gen.go -package api api/openapi/openapi.yaml .PHONY: check-oapi-codegen-version check-oapi-codegen-version: ## Check oapi-codegen version @@ -538,8 +538,8 @@ dev-image: ## Build dev image and push to sandbox -f deployment/images/Dockerfile.cluster-manager ${DOCKER_ENV} docker push ${DOCKER_DEV_IMG} -.PHONY: dev-helm # Build dev helm chart and push to sandbox -dev-helm: ## Build dev helm chart and push to sandbox +.PHONY: dev-chart # Build dev helm chart and push to sandbox +dev-chart: ## Build dev helm chart and push to sandbox @if test -z $(DEV_TAG); \ then echo "Please specify dev tag, make dev DEV_TAG= " && exit 1; \ fi diff --git a/VERSION b/VERSION index c10edc3f..7ec1d6db 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1.0-dev +2.1.0 diff --git a/api/openapi/openapi.yaml b/api/openapi/openapi.yaml index a54576b7..d8a9ec30 100644 --- a/api/openapi/openapi.yaml +++ b/api/openapi/openapi.yaml @@ -6,7 +6,7 @@ openapi: 3.0.3 info: title: Cluster Manager 2.0 description: This document defines the schema for the Cluster Manager 2.0 REST API. - version: 2.0.0 + version: 2.1.0 security: - HTTP: [] @@ -143,7 +143,6 @@ paths: "500": $ref: '#/components/responses/500-InternalServerError' - /v2/clusters/{name}: parameters: - $ref: '#/components/parameters/ActiveProjectIdHeader' @@ -186,6 +185,7 @@ paths: $ref: '#/components/responses/404-NotFound' "500": $ref: '#/components/responses/500-InternalServerError' + /v2/clusters/{nodeId}/clusterdetail: parameters: - $ref: '#/components/parameters/ActiveProjectIdHeader' @@ -211,6 +211,7 @@ paths: $ref: '#/components/responses/400-BadRequest' "404": $ref: '#/components/responses/404-NotFound' + /v2/clusters/{name}/nodes: parameters: - $ref: '#/components/parameters/ActiveProjectIdHeader' @@ -387,21 +388,6 @@ paths: $ref: '#/components/responses/404-NotFound' "500": $ref: '#/components/responses/500-InternalServerError' - /v2/healthz: - get: - description: Gets the Cluster Manager REST API healthz status. - security: [] # skips authentication - tags: - - Health Check - responses: - "200": - description: OK - content: - application/json: - schema: - type: string - "500": - $ref: '#/components/responses/500-InternalServerError' /v2/templates: parameters: @@ -478,7 +464,6 @@ paths: $ref: '#/components/responses/404-NotFound' "500": $ref: '#/components/responses/500-InternalServerError' - post: description: Import templates tags: @@ -553,6 +538,7 @@ paths: $ref: '#/components/responses/404-NotFound' "500": $ref: '#/components/responses/500-InternalServerError' + /v2/templates/{name}/versions: parameters: - $ref: '#/components/parameters/ActiveProjectIdHeader' @@ -583,6 +569,7 @@ paths: $ref: '#/components/responses/404-NotFound' "500": $ref: '#/components/responses/500-InternalServerError' + /v2/templates/{name}/default: parameters: - $ref: '#/components/parameters/ActiveProjectIdHeader' @@ -615,6 +602,38 @@ paths: "500": $ref: '#/components/responses/500-InternalServerError' + /v2/healthz: + get: + description: Gets the Cluster Manager REST API healthz status. + security: [] # skips authentication + tags: + - Health Check + responses: + "200": + description: OK + content: + application/json: + schema: + type: string + "500": + $ref: '#/components/responses/500-InternalServerError' + + /metrics: + get: + description: Gets the Cluster Manager REST API prometheus metrics. + security: [] # skips authentication + tags: + - metrics + responses: + "200": + description: OK + content: + application/json: + schema: + type: string + "500": + $ref: '#/components/responses/500-InternalServerError' + components: securitySchemes: HTTP: diff --git a/deployment/charts/cluster-manager/Chart.yaml b/deployment/charts/cluster-manager/Chart.yaml index 7f6c47f1..768cedf0 100644 --- a/deployment/charts/cluster-manager/Chart.yaml +++ b/deployment/charts/cluster-manager/Chart.yaml @@ -16,6 +16,6 @@ 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: 2.1.0-dev -appVersion: 2.1.0-dev +version: 2.1.0 +appVersion: 2.1.0 annotations: {} diff --git a/deployment/charts/cluster-manager/templates/_helpers.tpl b/deployment/charts/cluster-manager/templates/_helpers.tpl index 83f3735d..0ebfbc4c 100644 --- a/deployment/charts/cluster-manager/templates/_helpers.tpl +++ b/deployment/charts/cluster-manager/templates/_helpers.tpl @@ -63,21 +63,3 @@ Create the name of the service account to use {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} - -{{/* -Metrics service labels -*/}} -{{- define "templateController.metricsServiceLabels" -}} -{{- with .Values.templateController.metrics.service.labels }} -{{ toYaml . }} -{{- end }} -{{- end }} - -{{/* -Service monitor labels -*/}} -{{- define "templateController.serviceMonitorLabels" -}} -{{- with .Values.templateController.metrics.serviceMonitor.labels }} -{{- toYaml . }} -{{- end }} -{{- end }} diff --git a/deployment/charts/cluster-manager/templates/deployment-cluster-manager.yaml b/deployment/charts/cluster-manager/templates/deployment-cluster-manager.yaml index ccacf71f..9c1483d0 100644 --- a/deployment/charts/cluster-manager/templates/deployment-cluster-manager.yaml +++ b/deployment/charts/cluster-manager/templates/deployment-cluster-manager.yaml @@ -50,7 +50,9 @@ spec: - -{{ $key }}={{ $value }} {{- end }} ports: - - containerPort: {{ .Values.clusterManager.service.rest.port }} + - name: rest + containerPort: {{ .Values.clusterManager.service.rest.port }} + protocol: TCP readinessProbe: httpGet: path: {{ .Values.clusterManager.readinessProbe.httpGet.path }} diff --git a/deployment/charts/cluster-manager/templates/deployment-controller.yaml b/deployment/charts/cluster-manager/templates/deployment-controller.yaml index 0348e851..df547ded 100644 --- a/deployment/charts/cluster-manager/templates/deployment-controller.yaml +++ b/deployment/charts/cluster-manager/templates/deployment-controller.yaml @@ -41,8 +41,8 @@ spec: - --leader-elect - --health-probe-bind-address=:8081 - --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs - {{- if .Values.templateController.metrics.service.enabled }} - - --metrics-bind-address=:{{ .Values.templateController.metrics.service.port }} + {{- if .Values.metrics.enabled }} + - --metrics-bind-address=:{{ .Values.metrics.service.port }} - --metrics-secure=false {{- end }} {{- with .Values.templateController.extraArgs }} @@ -63,8 +63,8 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} ports: - {{if .Values.templateController.metrics.service.enabled }} - - containerPort: {{ .Values.templateController.metrics.service.port }} + {{if .Values.metrics.enabled }} + - containerPort: {{ .Values.metrics.service.port }} name: metrics protocol: TCP {{- end }} diff --git a/deployment/charts/cluster-manager/templates/metrics-service.yaml b/deployment/charts/cluster-manager/templates/metrics-service.yaml new file mode 100644 index 00000000..ba58fa2b --- /dev/null +++ b/deployment/charts/cluster-manager/templates/metrics-service.yaml @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +{{ if .Values.metrics.enabled -}} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: cluster-manager-metrics + namespace: {{ .Release.Namespace }} + labels: + {{- toYaml .Values.metrics.serviceMonitor.labels | indent 4 }} +spec: + endpoints: + - port: metrics + path: /metrics + scheme: http + - port: rest + path: /metrics + scheme: http + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchExpressions: + - key: prometheus.io/service-monitor + operator: NotIn + values: + - "false" + - key: app + operator: In + values: + - "{{.Chart.Name}}-cm" + - {{ .Values.metrics.service.labels.templateController.app | quote }} +--- +apiVersion: v1 +kind: Service +metadata: + name: templates-metrics + namespace: {{ .Release.Namespace }} + labels: + {{- toYaml .Values.metrics.service.labels.templateController | nindent 4 }} +spec: + ports: + - name: metrics + protocol: TCP + port: {{ .Values.metrics.service.port }} + targetPort: {{ .Values.metrics.service.port }} + selector: + app: "{{.Chart.Name}}-controller" +{{- end -}} diff --git a/deployment/charts/cluster-manager/templates/metrics_service.yaml b/deployment/charts/cluster-manager/templates/metrics_service.yaml deleted file mode 100644 index 5c4b60c5..00000000 --- a/deployment/charts/cluster-manager/templates/metrics_service.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-FileCopyrightText: (C) 2025 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -{{ if .Values.templateController.metrics.service.enabled -}} -apiVersion: v1 -kind: Service -metadata: - name: template-controller-metrics - namespace: {{ .Release.Namespace }} - labels: - {{- include "templateController.metricsServiceLabels" . | nindent 4 }} -spec: - ports: - - name: metrics - port: {{ .Values.templateController.metrics.service.port }} - protocol: TCP - targetPort: {{ .Values.templateController.metrics.service.port }} - selector: - app: "{{.Chart.Name}}-controller" -{{- end -}} diff --git a/deployment/charts/cluster-manager/templates/service.yaml b/deployment/charts/cluster-manager/templates/service.yaml index b7d32e55..33dab91a 100644 --- a/deployment/charts/cluster-manager/templates/service.yaml +++ b/deployment/charts/cluster-manager/templates/service.yaml @@ -4,17 +4,19 @@ apiVersion: v1 kind: Service metadata: - name: {{template "cluster-manager.fullname" .}} + name: {{ include "cluster-manager.fullname" . }} namespace: {{.Release.Namespace}} labels: app: "{{.Chart.Name}}-cm" spec: selector: app: "{{.Chart.Name}}-cm" - type: {{.Values.clusterManager.service.type}} + type: {{ .Values.clusterManager.service.type }} ports: - - name: "rest" - port: {{.Values.clusterManager.service.rest.port}} + - name: rest + port: {{ .Values.clusterManager.service.rest.port }} + targetPort: {{ .Values.clusterManager.service.rest.port }} + protocol: TCP {{- if .Values.openpolicyagent.enabled -}} {{- if .Values.service.opa.enabled }} @@ -23,15 +25,16 @@ apiVersion: v1 kind: Service metadata: name: {{ include "cluster-manager.fullname" . }}-opa + namespace: {{.Release.Namespace}} labels: {{- include "cluster-manager.labels" . | nindent 4 }} spec: type: {{ .Values.service.opa.type }} ports: - port: {{ .Values.service.opa.port }} - targetPort: opa + targetPort: {{ .Values.service.opa.port }} protocol: TCP - name: http-opa + name: opa selector: {{- include "cluster-manager.selectorLabels" . | nindent 4 }} {{- end}} diff --git a/deployment/charts/cluster-manager/templates/service_monitor.yaml b/deployment/charts/cluster-manager/templates/service_monitor.yaml deleted file mode 100644 index 3e957188..00000000 --- a/deployment/charts/cluster-manager/templates/service_monitor.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# SPDX-FileCopyrightText: (C) 2025 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -{{- if .Values.templateController.metrics.serviceMonitor.enabled -}} -# Prometheus Monitor Service (Metrics) -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - name: template-controller-metrics - namespace: {{ .Release.Namespace }} - labels: - {{- include "templateController.serviceMonitorLabels" . | nindent 4 }} -spec: - endpoints: - - path: /metrics - port: metrics - scheme: http - namespaceSelector: - matchNames: - - {{ .Release.Namespace }} - selector: - matchExpressions: - - key: prometheus.io/service-monitor - operator: NotIn - values: - - "false" - matchLabels: - {{- include "templateController.metricsServiceLabels" . | nindent 6 }} -{{- end -}} diff --git a/deployment/charts/cluster-manager/templates/serviceaccount.yaml b/deployment/charts/cluster-manager/templates/serviceaccount.yaml index 4cfe6c38..158e60b7 100644 --- a/deployment/charts/cluster-manager/templates/serviceaccount.yaml +++ b/deployment/charts/cluster-manager/templates/serviceaccount.yaml @@ -4,5 +4,5 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: {{template "cluster-manager.fullname" .}} - namespace: {{.Release.Namespace}} + name: {{ include "cluster-manager.fullname" . }} + namespace: {{ .Release.Namespace }} diff --git a/deployment/charts/cluster-manager/values.yaml b/deployment/charts/cluster-manager/values.yaml index acafa5bf..52b2aeba 100644 --- a/deployment/charts/cluster-manager/values.yaml +++ b/deployment/charts/cluster-manager/values.yaml @@ -74,6 +74,7 @@ clusterManager: disable-auth: false disable-mt: false disable-inventory: false + disable-metrics: true extraEnv: [] @@ -134,26 +135,21 @@ templateController: extraArgs: - "--webhook-enabled=true" - extraEnv: [] args: loglevel: "-loglevel=0" logformat: "-logformat=human" - metrics: - service: - # Whether to create a service for scraping metrics - enabled: false - port: 8080 - # Labels to add to the service - labels: +metrics: + enabled: false + service: + port: 8080 + labels: + templateController: app: "template-controller-metrics-svc" - serviceMonitor: - # Whether to create a service monitor for scraping metrics - enabled: false - # Labels to add to the service monitor - labels: {} + serviceMonitor: + labels: {} webhookService: enabled: true diff --git a/deployment/charts/cluster-template-crd/Chart.yaml b/deployment/charts/cluster-template-crd/Chart.yaml index 1b2be2ea..3576cd09 100644 --- a/deployment/charts/cluster-template-crd/Chart.yaml +++ b/deployment/charts/cluster-template-crd/Chart.yaml @@ -6,6 +6,6 @@ apiVersion: v2 name: cluster-template-crd description: A Helm chart for the ClusterTemplate CRD type: application -version: 2.1.0-dev -appVersion: 2.1.0-dev +version: 2.1.0 +appVersion: 2.1.0 annotations: {} diff --git a/go.mod b/go.mod index dcfec898..2e07a80b 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,13 @@ require ( sigs.k8s.io/controller-runtime v0.20.4 ) +require ( + github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect + github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 // indirect + github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect +) + require ( ariga.io/atlas v0.32.0 // indirect buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250307204501-0409229c3780.1 // indirect @@ -137,7 +144,7 @@ require ( github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.21.1 // indirect + github.com/prometheus/client_golang v1.21.1 github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.63.0 // indirect github.com/prometheus/procfs v0.16.0 // indirect @@ -204,3 +211,5 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) + +tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen diff --git a/go.sum b/go.sum index b4ea6f19..b6158609 100644 --- a/go.sum +++ b/go.sum @@ -494,6 +494,9 @@ github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7c github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elliotchance/orderedmap v1.8.0 h1:TrOREecvh3JbS+NCgwposXG5ZTFHtEsQiCGOhPElnMw= @@ -524,6 +527,8 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= @@ -558,6 +563,7 @@ github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDsl github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= @@ -652,6 +658,7 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -714,6 +721,7 @@ github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3q github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/vault/api v1.16.0 h1:nbEYGJiAPGzT9U4oWgaaB0g+Rj8E59QuHKyA5LhwQN4= github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= @@ -807,12 +815,28 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 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/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oapi-codegen/nethttp-middleware v1.0.2 h1:A5tfAcKJhWIbIPnlQH+l/DtfVE1i5TFgPlQAiW+l1vQ= github.com/oapi-codegen/nethttp-middleware v1.0.2/go.mod h1:DfDalonSO+eRQ3RTb8kYoWZByCCPFRxm9WKq1UbY0E4= +github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 h1:ykgG34472DWey7TSjd8vIfNykXgjOgYJZoQbKfEeY/Q= +github.com/oapi-codegen/oapi-codegen/v2 v2.4.1/go.mod h1:N5+lY1tiTDV3V1BeHtOxeWXHoPVeApvsvjJqegfoaz8= github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/open-edge-platform/cluster-api-provider-intel v1.0.1 h1:LmH5r15y5G1EQgvL/UF52XBp+dSdQhjywu28TjCn7YY= @@ -863,12 +887,17 @@ github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkB github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/speakeasy-api/openapi-overlay v0.9.0 h1:Wrz6NO02cNlLzx1fB093lBlYxSI54VRhy1aSutx0PQg= +github.com/speakeasy-api/openapi-overlay v0.9.0/go.mod h1:f5FloQrHA7MsxYg9djzMD5h6dxrHjVVByWKh7an8TRc= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= @@ -905,6 +934,8 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI= github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= 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/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= @@ -1028,6 +1059,7 @@ golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1048,6 +1080,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -1060,6 +1093,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -1122,6 +1156,7 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1131,7 +1166,10 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1152,6 +1190,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1270,6 +1309,7 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= @@ -1523,18 +1563,26 @@ google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9x google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/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-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/config/config.go b/internal/config/config.go index aabd39d0..5cc79171 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -17,15 +17,18 @@ import ( ) type Config struct { - // DisableAuth disables authentication/authorization, should be true for production and false in integration without keycloak + // DisableAuth disables authentication/authorization, should be false for production and true in integration without keycloak DisableAuth bool - // DisableMultitenancy disables multi-tenancy integration, should be true for production and false in integration without multi-tenancy + // DisableMultitenancy disables multi-tenancy integration, should be false for production and true in integration without multi-tenancy DisableMultitenancy bool - // DisableInventory disables inventory integration, should be true for production and false in integration without infra-manager's inventory + // DisableInventory disables inventory integration, should be false for production and true in integration without infra-manager's inventory DisableInventory bool + // DisableMetrics disables metrics, should be false for production and true in integration without prometheus + DisableMetrics bool + OidcUrl string OpaEnabled bool OpaPort int @@ -42,6 +45,7 @@ func ParseConfig() *Config { disableAuth := flag.Bool("disable-auth", false, "(optional) disable rest authentication/authorization") disableMt := flag.Bool("disable-mt", false, "(optional) disable multi-tenancy integration") disableInv := flag.Bool("disable-inventory", false, "(optional) disable inventory integration") + disableMetrics := flag.Bool("disable-metrics", false, "(optional) disable prometheus metrics handler") logLevel := flag.Int("loglevel", 0, "(optional) log level [trace:-8|debug:-4|info:0|warn:4|error:8]") logFormat := flag.String("logformat", "json", "(optional) log format [json|human]") prefixes := flag.String("system-labels-prefixes", "", "(optional) comma separated list of system labels prefixes; if not provided, sane defaults are used") @@ -54,6 +58,7 @@ func ParseConfig() *Config { DisableAuth: *disableAuth, DisableMultitenancy: *disableMt, DisableInventory: *disableInv, + DisableMetrics: *disableMetrics, LogLevel: *logLevel, LogFormat: strings.ToLower(*logFormat), ClusterDomain: *clusterDomain, diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go new file mode 100644 index 00000000..7ed1787c --- /dev/null +++ b/internal/metrics/metrics.go @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +var ( + ResponseTime = prometheus.NewHistogram(prometheus.HistogramOpts{ + Name: "cluster_manager_http_response_time_seconds_histogram", + Help: "Response time to HTTP requests in seconds", + Buckets: prometheus.DefBuckets, + }) + + HttpResponseCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "cluster_manager_http_response_codes_counter", + Help: "Count of HTTP response codes per endpoint", + }, + []string{"method", "path", "code"}, + ) +) + +func GetRegistry() *prometheus.Registry { + registry := prometheus.NewRegistry() + registry.MustRegister(ResponseTime) + registry.MustRegister(HttpResponseCounter) + + return registry +} diff --git a/internal/metrics/metrics_test/metrics_test.go b/internal/metrics/metrics_test/metrics_test.go new file mode 100644 index 00000000..e25dfe5e --- /dev/null +++ b/internal/metrics/metrics_test/metrics_test.go @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +package metrics_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/open-edge-platform/cluster-manager/v2/internal/metrics" + + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/stretchr/testify/assert" +) + +func TestMetrics(t *testing.T) { + cases := []struct { + name string + setup func() + expectedStatus int + expectedBody []string + }{ + { + name: "TestResponseTimeMetric1", + setup: func() { + metrics.ResponseTime.Observe(1.23) + }, + expectedStatus: http.StatusOK, + expectedBody: []string{ + "cluster_manager_http_response_time_seconds_histogram", + "1.23", + }, + }, + { + name: "TestResponseTimeMetric2", + setup: func() { + metrics.ResponseTime.Observe(2.34) + }, + expectedStatus: http.StatusOK, + expectedBody: []string{ + "cluster_manager_http_response_time_seconds_histogram", + "3.57", + }, + }, + { + name: "TestHttpResponseCounterMetric1", + setup: func() { + metrics.HttpResponseCounter.WithLabelValues("GET", "/test1", "200").Inc() + }, + expectedStatus: http.StatusOK, + expectedBody: []string{ + "cluster_manager_http_response_codes_counter", + `code="200",method="GET",path="/test1"`, + }, + }, + { + name: "TestHttpResponseCounterMetric2", + setup: func() { + metrics.HttpResponseCounter.WithLabelValues("GET", "/test2", "404").Inc() + }, + expectedStatus: http.StatusOK, + expectedBody: []string{ + "cluster_manager_http_response_codes_counter", + `code="200",method="GET",path="/test1"`, + `code="404",method="GET",path="/test2"`, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + registry := metrics.GetRegistry() + + tc.setup() + + handler := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) + req := httptest.NewRequest(http.MethodGet, "/metrics", nil) + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + + assert.Equal(t, tc.expectedStatus, rec.Code) + + body := rec.Body.String() + for _, expected := range tc.expectedBody { + assert.Contains(t, body, expected) + } + }) + } +} + +func TestGetRegistry(t *testing.T) { + registry := metrics.GetRegistry() + assert.NotNil(t, registry, "Registry should not be nil") + + metricFamilies, err := registry.Gather() + assert.NoError(t, err, "Error gathering metrics") + assert.True(t, len(metricFamilies) > 0, "No metrics found in the registry") +} diff --git a/internal/metrics/metrics_test/statusresponsewriter_test.go b/internal/metrics/metrics_test/statusresponsewriter_test.go new file mode 100644 index 00000000..940ede6d --- /dev/null +++ b/internal/metrics/metrics_test/statusresponsewriter_test.go @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +package metrics_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/open-edge-platform/cluster-manager/v2/internal/metrics" + + "github.com/stretchr/testify/assert" +) + +func TestStatusResponseWriter(t *testing.T) { + cases := []struct { + name string + writeHeader int + writeBody string + expectedStatus string + expectedBody string + }{ + { + name: "WriteHeader and Write", + writeHeader: http.StatusOK, + writeBody: "Body and header", + expectedStatus: "200", + expectedBody: "Body and header", + }, + { + name: "WriteHeader only", + writeHeader: http.StatusNotFound, + expectedStatus: "404", + }, + { + name: "Write only", + writeHeader: 0, // No header written + writeBody: "Body only", + expectedStatus: "0", // Default status + expectedBody: "Body only", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + recorder := httptest.NewRecorder() + srw := metrics.NewStatusResponseWriter(recorder) + + if tc.writeHeader != 0 { + srw.WriteHeader(tc.writeHeader) + } + + if tc.writeBody != "" { + _, err := srw.Write([]byte(tc.writeBody)) + assert.NoError(t, err) + } + + assert.Equal(t, tc.expectedStatus, srw.Status()) + assert.Equal(t, tc.expectedBody, recorder.Body.String()) + }) + } +} diff --git a/internal/metrics/statusresponsewriter.go b/internal/metrics/statusresponsewriter.go new file mode 100644 index 00000000..23acd262 --- /dev/null +++ b/internal/metrics/statusresponsewriter.go @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +package metrics + +import ( + "net/http" + "strconv" +) + +type StatusResponseWriter struct { + wr http.ResponseWriter + status int +} + +func NewStatusResponseWriter(wr http.ResponseWriter) *StatusResponseWriter { + return &StatusResponseWriter{ + wr: wr, + } +} + +func (srw *StatusResponseWriter) WriteHeader(code int) { + srw.status = code + srw.wr.WriteHeader(code) +} + +func (srw *StatusResponseWriter) Write(b []byte) (int, error) { + return srw.wr.Write(b) +} + +func (srw *StatusResponseWriter) Header() http.Header { + return srw.wr.Header() +} + +func (srw *StatusResponseWriter) Status() string { + return strconv.Itoa(srw.status) +} diff --git a/internal/rest/getv2clusters.go b/internal/rest/getv2clusters.go index 2182d691..43130fe1 100644 --- a/internal/rest/getv2clusters.go +++ b/internal/rest/getv2clusters.go @@ -122,7 +122,6 @@ func (s *Server) convertClusters(ctx context.Context, namespace string, unstruct continue } - slog.Debug("Processing cluster", "name", capiCluster.Name, "labels", capiCluster.Labels) // get machines associated with the cluster machines := getClusterMachines(allMachines, capiCluster.Name) diff --git a/internal/rest/getv2clustersnamekubeconfig.go b/internal/rest/getv2clustersnamekubeconfig.go index b88c3be3..74f3d42c 100644 --- a/internal/rest/getv2clustersnamekubeconfig.go +++ b/internal/rest/getv2clustersnamekubeconfig.go @@ -35,20 +35,20 @@ func (s *Server) GetV2ClustersNameKubeconfigs(ctx context.Context, request api.G clusterKubeconfig, err := s.getClusterKubeconfig(ctx, namespace, request.Name) if err != nil { - slog.Error("error", "err", err) + slog.Error("failed to get kubeconfig", "error", err) return api.GetV2ClustersNameKubeconfigs404JSONResponse{ N404NotFoundJSONResponse: api.N404NotFoundJSONResponse{ - Message: ptr(err.Error()), + Message: ptr("404 Not Found: kubeconfig not found"), }, }, nil } clusterKubeconfigUpdated, err := updateKubeconfigWithTokenFunc(clusterKubeconfig, namespace, request.Name, request.Params.Authorization) if err != nil { - slog.Error("error", "err", err) + slog.Error("failed to update kubeconfig with token", "error", err) return api.GetV2ClustersNameKubeconfigs500JSONResponse{ N500InternalServerErrorJSONResponse: api.N500InternalServerErrorJSONResponse{ - Message: ptr(err.Error()), + Message: ptr("500 Internal Server Error: failed to process kubeconfig"), }, }, nil } @@ -60,38 +60,41 @@ func (s *Server) getClusterKubeconfig(ctx context.Context, namespace, clusterNam if s.config == nil { return kubeconfigParameters{}, fmt.Errorf("config is nil") } - secretName := clusterName + "-kubeconfig" - unstructuredClusterSecret, err := s.k8sclient.Resource(core.SecretResourceSchema).Namespace(namespace).Get(ctx, secretName, metav1.GetOptions{}) + unstructuredClusterSecret, err := s.k8sclient.Resource(core.SecretResourceSchema). + Namespace(namespace).Get(ctx, fmt.Sprintf("%s-kubeconfig", clusterName), metav1.GetOptions{}) if err != nil || unstructuredClusterSecret == nil { - msg := fmt.Sprintf("failed getting kubeconfig for cluster %s in namespace %s", clusterName, namespace) - return kubeconfigParameters{}, fmt.Errorf("%s", msg) + return kubeconfigParameters{}, fmt.Errorf("failed to get kubeconfig secret: %w", err) } dataValue, found, err := unstructured.NestedString(unstructuredClusterSecret.Object, "data", "value") - if err != nil || !found { - msg := fmt.Sprintf("failed to get kubeconfig from secret: namespace=%s, name=%s", namespace, clusterName) - return kubeconfigParameters{}, fmt.Errorf("%s", msg) + if err != nil { + return kubeconfigParameters{}, fmt.Errorf("failed to get raw kubeconfig data from secret: %w", err) + } + if !found { + return kubeconfigParameters{}, fmt.Errorf("kubeconfig data not found in secret") } kubeconfigBytes, err := base64.StdEncoding.DecodeString(dataValue) if err != nil { - msg := fmt.Sprintf("failed to decode kubeconfig: namespace=%s, name=%s", namespace, clusterName) - return kubeconfigParameters{}, fmt.Errorf("%s", msg) + return kubeconfigParameters{}, fmt.Errorf("failed to decode kubeconfig data: %w", err) } var caDataInSecretValue string apiServerCA, found, err := unstructured.NestedString(unstructuredClusterSecret.Object, "data", "apiServerCA") if err != nil || !found { - slog.Warn("failed to get apiServerCA from secret", "namespace", namespace, "name", clusterName) + slog.Warn("failed to get apiServerCA from secret", "namespace", namespace, "name", clusterName, "error", err) + caData, err := unmarshalKubeconfig(string(kubeconfigBytes)) if err != nil { return kubeconfigParameters{}, err } + caDataInSecretValue, err = getCertificateAuthorityData(caData) if err != nil { return kubeconfigParameters{}, err } + return kubeconfigParameters{serverCA: caDataInSecretValue, clusterDomain: s.config.ClusterDomain, userName: s.config.Username, kubeConfigDecode: string(kubeconfigBytes)}, nil } diff --git a/internal/rest/getv2clustersnamekubeconfig_test.go b/internal/rest/getv2clustersnamekubeconfig_test.go index c8535e82..8c2b2eb1 100644 --- a/internal/rest/getv2clustersnamekubeconfig_test.go +++ b/internal/rest/getv2clustersnamekubeconfig_test.go @@ -158,6 +158,7 @@ func TestGetV2ClustersNameKubeconfigs200(t *testing.T) { } func TestGetV2ClustersNameKubeconfigs404(t *testing.T) { + expected404Response := `{"message":"404 Not Found: kubeconfig not found"}` tests := []struct { name string clusterName string @@ -187,7 +188,7 @@ func TestGetV2ClustersNameKubeconfigs404(t *testing.T) { mockK8sClientSetup(resource, nsResource, mockedk8sclient, "example-cluster-kubeconfig", clusterSecret, errors.NewNotFound(core.SecretResourceSchema.GroupResource(), "example-cluster-kubeconfig")) }, expectedCode: http.StatusNotFound, - expectedResponse: `{"message":"failed getting kubeconfig for cluster example-cluster in namespace 655a6892-4280-4c37-97b1-31161ac0b99e"}`, + expectedResponse: expected404Response, }, { name: "no kubeconfig in secret", @@ -200,7 +201,7 @@ func TestGetV2ClustersNameKubeconfigs404(t *testing.T) { mockK8sClientSetup(resource, nsResource, mockedk8sclient, "example-cluster-kubeconfig", clusterSecret, nil) }, expectedCode: http.StatusNotFound, - expectedResponse: `{"message":"failed to get kubeconfig from secret: namespace=655a6892-4280-4c37-97b1-31161ac0b99e, name=example-cluster"}`, + expectedResponse: expected404Response, }, { name: "not able to decode kubeconfig", @@ -217,7 +218,7 @@ func TestGetV2ClustersNameKubeconfigs404(t *testing.T) { mockK8sClientSetup(resource, nsResource, mockedk8sclient, "example-cluster-kubeconfig", clusterSecret, nil) }, expectedCode: http.StatusNotFound, - expectedResponse: `{"message":"failed to decode kubeconfig: namespace=655a6892-4280-4c37-97b1-31161ac0b99e, name=example-cluster"}`, + expectedResponse: expected404Response, }, } serverConfig := config.Config{ClusterDomain: "kind.internal", Username: "admin"} @@ -348,7 +349,7 @@ func TestGetV2ClustersNameKubeconfigs500(t *testing.T) { mockK8sClientSetup(resource, nsResource, mockedk8sclient, "demo-example-cluster-kubeconfig", clusterSecret, nil) }, expectedCode: http.StatusInternalServerError, - expectedResponse: `{"message":"failed to update kubeconfig with token"}`, + expectedResponse: `{"message":"500 Internal Server Error: failed to process kubeconfig"}`, }, } serverConfig := config.Config{ClusterDomain: "kind.internal", Username: "admin"} diff --git a/internal/rest/getv2healthz.go b/internal/rest/getv2healthz.go index a14292d5..b1b5deea 100644 --- a/internal/rest/getv2healthz.go +++ b/internal/rest/getv2healthz.go @@ -8,7 +8,7 @@ import ( "github.com/open-edge-platform/cluster-manager/v2/pkg/api" ) -// (GET /v2/healthz) +// (GET /v2/healthz) TODO: remove healthz from oapi code generation and implement separately to the generated server handler func (s *Server) GetV2Healthz(ctx context.Context, request api.GetV2HealthzRequestObject) (api.GetV2HealthzResponseObject, error) { return api.GetV2Healthz200JSONResponse("cm rest server is healthy"), nil } diff --git a/internal/rest/middleware.go b/internal/rest/middleware.go index 7db3f583..e9320ee2 100644 --- a/internal/rest/middleware.go +++ b/internal/rest/middleware.go @@ -6,6 +6,17 @@ package rest import ( "log/slog" "net/http" + "slices" + "time" + + "github.com/open-edge-platform/cluster-manager/v2/internal/metrics" +) + +var ( + ignoredPaths = []string{ + "/v2/healthz", + "/metrics", + } ) // middleware is a function definition that wraps an http.Handler @@ -24,25 +35,61 @@ func appendMiddlewares(mw ...middleware) func(http.Handler) http.Handler { // logger logs the request and response func logger(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/v2/healthz" { // reduce log spam - slog.Debug("received request", "method", r.Method, "path", r.URL.Path) + if slices.Contains(ignoredPaths, r.URL.Path) { + next.ServeHTTP(w, r) + return } + + slog.Debug("received request", "method", r.Method, "path", r.URL.Path) next.ServeHTTP(w, r) }) } +// requestDurationMetrics measures the duration of the request and records it for Prometheus +func requestDurationMetrics(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if slices.Contains(ignoredPaths, r.URL.Path) { + next.ServeHTTP(w, r) + return + } + + start := time.Now() + next.ServeHTTP(w, r) + d := time.Since(start).Seconds() + metrics.ResponseTime.Observe(d) + }) +} + +// responseCounterMetrics counts the number of responses and records it for Prometheus +func responseCounterMetrics(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if slices.Contains(ignoredPaths, r.URL.Path) { + next.ServeHTTP(w, r) + return + } + + srw := metrics.NewStatusResponseWriter(w) + next.ServeHTTP(srw, r) + metrics.HttpResponseCounter.WithLabelValues(r.Method, r.URL.Path, srw.Status()).Inc() + }) +} + // projectIDValidator validates the project ID func projectIDValidator(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // ignore /v2/healthz endpoint as it doesn't require project ID - if r.URL.Path != "/v2/healthz" { - activeProjectId := r.Header.Get("Activeprojectid") - if activeProjectId == "" || activeProjectId == "00000000-0000-0000-0000-000000000000" { - w.Header().Set("Content-Type", "application/json") - http.Error(w, `{"message": "no active project id provided"}`, http.StatusBadRequest) - return - } + // skip endpoints that do not require a project id + if slices.Contains(ignoredPaths, r.URL.Path) { + next.ServeHTTP(w, r) + return } + + activeProjectId := r.Header.Get("Activeprojectid") + if activeProjectId == "" || activeProjectId == "00000000-0000-0000-0000-000000000000" { + w.Header().Set("Content-Type", "application/json") + http.Error(w, `{"message": "no active project id provided"}`, http.StatusBadRequest) + return + } + next.ServeHTTP(w, r) }) } diff --git a/internal/rest/postv2clusters.go b/internal/rest/postv2clusters.go index 546d4e29..a987d594 100644 --- a/internal/rest/postv2clusters.go +++ b/internal/rest/postv2clusters.go @@ -117,17 +117,17 @@ func (s *Server) PostV2Clusters(ctx context.Context, request api.PostV2ClustersR return api.PostV2Clusters201JSONResponse(fmt.Sprintf("successfully created cluster %s", createdClusterName)), nil } -func fetchTemplate(ctx context.Context, cli *k8s.Client, activeProjectID string, templateName *string) (ct.ClusterTemplate, error) { +func fetchTemplate(ctx context.Context, cli *k8s.Client, namespace string, templateName *string) (ct.ClusterTemplate, error) { // template name is optional, if not provided we use default var template ct.ClusterTemplate var err error if templateName == nil || *templateName == "" { slog.Info("template name not provided, using default template") - if template, err = cli.DefaultTemplate(ctx, activeProjectID); err != nil { + if template, err = cli.DefaultTemplate(ctx, namespace); err != nil { return ct.ClusterTemplate{}, err } } else { - if template, err = cli.Template(ctx, activeProjectID, *templateName); err != nil { + if template, err = cli.Template(ctx, namespace, *templateName); err != nil { return ct.ClusterTemplate{}, err } } diff --git a/internal/rest/server.go b/internal/rest/server.go index 1b45e914..be3a2a4d 100644 --- a/internal/rest/server.go +++ b/internal/rest/server.go @@ -13,11 +13,13 @@ import ( "github.com/getkin/kin-openapi/openapi3filter" httpmid "github.com/oapi-codegen/nethttp-middleware" + "github.com/prometheus/client_golang/prometheus/promhttp" "k8s.io/client-go/dynamic" "github.com/open-edge-platform/cluster-manager/v2/internal/auth" "github.com/open-edge-platform/cluster-manager/v2/internal/config" "github.com/open-edge-platform/cluster-manager/v2/internal/inventory" + "github.com/open-edge-platform/cluster-manager/v2/internal/metrics" "github.com/open-edge-platform/cluster-manager/v2/pkg/api" ) @@ -104,18 +106,41 @@ func (s *Server) Serve() error { // ConfigureHandler configures the server with necessary middleware and handlers func (s *Server) ConfigureHandler() (http.Handler, error) { // handler already implements request validation via oapi request validator - handler, err := s.getOapiHandler() + handler, err := s.getServerHandler() if err != nil { slog.Error("failed to get oapi handler", "error", err) return nil, err } // add middlewares (middleware1, middleware2, ...) - return appendMiddlewares(logger, projectIDValidator)(handler), nil + return appendMiddlewares(responseCounterMetrics, requestDurationMetrics, logger, projectIDValidator)(handler), nil } -// getOapiHandler returns the base http handler with strict validation against the OpenAPI spec -func (s *Server) getOapiHandler() (http.Handler, error) { +// getServerHandler returns the base http handler with strict validation against the OpenAPI spec +func (s *Server) getServerHandler() (http.Handler, error) { + // create the router for the metrics endpoint + router := http.NewServeMux() + + if !s.config.DisableMetrics { + router.Handle("/metrics", promhttp.HandlerFor(metrics.GetRegistry(), promhttp.HandlerOpts{})) + } + + // create the openapi handler with existing router + handler := api.HandlerWithOptions(api.NewStrictHandler(s, nil), api.StdHTTPServerOptions{ + BaseRouter: router, + ErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + slog.Error(err.Error(), "path", r.URL.Path, "method", r.Method) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + + if err := json.NewEncoder(w).Encode(api.N400BadRequest{Message: ptr(err.Error())}); err != nil { + http.Error(w, "Failed to encode response", http.StatusInternalServerError) + } + }, + }) + + // load the swagger spec swagger, err := api.GetSwagger() if err != nil { slog.Error("failed to get swagger spec", "error", err) @@ -123,6 +148,7 @@ func (s *Server) getOapiHandler() (http.Handler, error) { } swagger.Servers = nil + // set up the request validator with authentication and error handling validator := httpmid.OapiRequestValidatorWithOptions(swagger, &httpmid.Options{ Options: openapi3filter.Options{AuthenticationFunc: s.auth.Authenticate}, ErrorHandler: func(w http.ResponseWriter, message string, code int) { @@ -140,18 +166,6 @@ func (s *Server) getOapiHandler() (http.Handler, error) { } }, }) - handler := api.HandlerWithOptions(api.NewStrictHandler(s, nil), api.StdHTTPServerOptions{ - ErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { - slog.Error(err.Error(), "path", r.URL.Path, "method", r.Method) - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusBadRequest) - - if err := json.NewEncoder(w).Encode(api.N400BadRequest{Message: ptr(err.Error())}); err != nil { - http.Error(w, "Failed to encode response", http.StatusInternalServerError) - } - }, - }) return validator(handler), nil } diff --git a/internal/rest/utils.go b/internal/rest/utils.go index 29a318f0..45aa45e6 100644 --- a/internal/rest/utils.go +++ b/internal/rest/utils.go @@ -25,7 +25,7 @@ type ContextKey string const ( ActiveProjectIdHeaderKey = "Activeprojectid" ActiveProjectIdContextKey ContextKey = ActiveProjectIdHeaderKey - ClusterNameSelectorKey = "cluster.x-k8s.io/cluster-name" + ClusterNameSelectorKey = "cluster.x-k8s.io/cluster-name" ) func validateClusterDetail(clusterDetail api.ClusterDetailInfo) error { @@ -295,7 +295,7 @@ func getNodeHealth(cluster *capi.Cluster, machines []unstructured.Unstructured) } // getClusterMachines filters the machines by cluster name -func getClusterMachines(machines []unstructured.Unstructured, name string ) ([]unstructured.Unstructured) { +func getClusterMachines(machines []unstructured.Unstructured, name string) []unstructured.Unstructured { var filteredMachines []unstructured.Unstructured for _, machine := range machines { if machine.GetLabels()[ClusterNameSelectorKey] == name { diff --git a/pkg/api/spec.gen.go b/pkg/api/spec.gen.go index 37068fc0..8a442b66 100644 --- a/pkg/api/spec.gen.go +++ b/pkg/api/spec.gen.go @@ -18,84 +18,85 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "/H8AAAD//7asqJQBawAA", } // GetSwagger returns the content of the embedded swagger specification file