diff --git a/Dockerfile b/Dockerfile index 8155a25c1e..1e0243b3a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,8 +27,15 @@ WORKDIR / ENTRYPOINT [ "/usr/local/bin/dlv" , "--listen=:40000", "--headless=true", "--api-version=2", "--accept-multiclient", "exec", "/usr/local/bin/fleet-manager", "serve"] FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 as standard -COPY --from=build-standard /src/fleet-manager /src/fleetshard-sync /usr/local/bin/ -COPY --from=build /rds_ca /usr/local/share/ca-certificates + +RUN microdnf install shadow-utils + +RUN useradd -u 1001 unprivilegeduser +# Switch to non-root user +USER unprivilegeduser + +COPY --chown=unprivilegeduser --from=build-standard /src/fleet-manager /src/fleetshard-sync /usr/local/bin/ +COPY --chown=unprivilegeduser --from=build /rds_ca /usr/local/share/ca-certificates EXPOSE 8000 WORKDIR / ENTRYPOINT ["/usr/local/bin/fleet-manager", "serve"] diff --git a/dp-terraform/helm/rhacs-terraform/charts/secured-cluster/templates/_helpers.tpl b/dp-terraform/helm/rhacs-terraform/charts/secured-cluster/templates/_helpers.tpl new file mode 100644 index 0000000000..19316e170a --- /dev/null +++ b/dp-terraform/helm/rhacs-terraform/charts/secured-cluster/templates/_helpers.tpl @@ -0,0 +1,3 @@ +{{- define "secured-cluster.namespace" }} +{{- printf "%s-%s" .Release.Namespace "secured-cluster" }} +{{- end }} diff --git a/dp-terraform/helm/rhacs-terraform/charts/secured-cluster/templates/secured-cluster-cr.yaml b/dp-terraform/helm/rhacs-terraform/charts/secured-cluster/templates/secured-cluster-cr.yaml index 26c1232d37..cdb0a2f705 100644 --- a/dp-terraform/helm/rhacs-terraform/charts/secured-cluster/templates/secured-cluster-cr.yaml +++ b/dp-terraform/helm/rhacs-terraform/charts/secured-cluster/templates/secured-cluster-cr.yaml @@ -2,6 +2,7 @@ apiVersion: platform.stackrox.io/v1alpha1 kind: SecuredCluster metadata: name: stackrox-secured-cluster-services + namespace: {{ include "secured-cluster.namespace" . }} spec: {{- if .Values.pullSecret }} imagePullSecrets: diff --git a/dp-terraform/helm/rhacs-terraform/charts/secured-cluster/templates/secured-cluster-namespace.yaml b/dp-terraform/helm/rhacs-terraform/charts/secured-cluster/templates/secured-cluster-namespace.yaml new file mode 100644 index 0000000000..b1c91748b0 --- /dev/null +++ b/dp-terraform/helm/rhacs-terraform/charts/secured-cluster/templates/secured-cluster-namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: {{ include "secured-cluster.namespace" . }} diff --git a/dp-terraform/helm/rhacs-terraform/charts/secured-cluster/templates/secured-cluster-secrets.yaml b/dp-terraform/helm/rhacs-terraform/charts/secured-cluster/templates/secured-cluster-secrets.yaml index 09a0fde85f..1a93d5bbfc 100644 --- a/dp-terraform/helm/rhacs-terraform/charts/secured-cluster/templates/secured-cluster-secrets.yaml +++ b/dp-terraform/helm/rhacs-terraform/charts/secured-cluster/templates/secured-cluster-secrets.yaml @@ -6,6 +6,7 @@ metadata: init-bundle.stackrox.io/name: {{ .Values.clusterName }} creationTimestamp: null name: admission-control-tls + namespace: {{ include "secured-cluster.namespace" . }} stringData: ca.pem: | {{ .Values.ca.cert | indent 4 }} @@ -21,6 +22,7 @@ metadata: init-bundle.stackrox.io/name: {{ .Values.clusterName }} creationTimestamp: null name: collector-tls + namespace: {{ include "secured-cluster.namespace" . }} stringData: ca.pem: | {{ .Values.ca.cert | indent 4 }} @@ -36,6 +38,7 @@ metadata: init-bundle.stackrox.io/name: {{ .Values.clusterName }} creationTimestamp: null name: sensor-tls + namespace: {{ include "secured-cluster.namespace" . }} stringData: ca.pem: | {{ .Values.ca.cert | indent 4 }} diff --git a/dp-terraform/helm/rhacs-terraform/templates/_helpers.tpl b/dp-terraform/helm/rhacs-terraform/templates/_helpers.tpl new file mode 100644 index 0000000000..4994c5d716 --- /dev/null +++ b/dp-terraform/helm/rhacs-terraform/templates/_helpers.tpl @@ -0,0 +1,5 @@ +{{- define "imagePullSecret" }} +{{- with .Values.fleetshardSync.imageCredentials }} +{{- printf "{\"auths\":{\"%s\":{\"username\":\"%s\",\"password\":\"%s\",\"email\":\"%s\",\"auth\":\"%s\"}}}" .registry .username .password .email (printf "%s:%s" .username .password | b64enc) | b64enc }} +{{- end }} +{{- end }} diff --git a/dp-terraform/helm/rhacs-terraform/templates/fleetshard-sync-secret.yaml b/dp-terraform/helm/rhacs-terraform/templates/fleetshard-sync-secret.yaml index 88b76b390e..9a3bfbab65 100644 --- a/dp-terraform/helm/rhacs-terraform/templates/fleetshard-sync-secret.yaml +++ b/dp-terraform/helm/rhacs-terraform/templates/fleetshard-sync-secret.yaml @@ -8,6 +8,7 @@ metadata: stringData: rhsso-service-account-client-id: {{ .Values.fleetshardSync.redHatSSO.clientId | quote }} rhsso-service-account-client-secret: {{ .Values.fleetshardSync.redHatSSO.clientSecret | quote }} + image-pull.dockerconfigjson: {{ template "imagePullSecret" . }} {{- if eq .Values.fleetshardSync.aws.enableTokenAuth false }} aws-access-key-id: {{ required "fleetshardSync.aws.accessKeyId is required when fleetshardSync.aws.enableTokenAuth = false" .Values.fleetshardSync.aws.accessKeyId | quote }} aws-secret-access-key: {{ required "fleetshardSync.aws.secretAccessKey is required when fleetshardSync.aws.enableTokenAuth = false" .Values.fleetshardSync.aws.secretAccessKey | quote }} diff --git a/dp-terraform/helm/rhacs-terraform/terraform_cluster.sh b/dp-terraform/helm/rhacs-terraform/terraform_cluster.sh index 5ff62c7412..3a0afba728 100755 --- a/dp-terraform/helm/rhacs-terraform/terraform_cluster.sh +++ b/dp-terraform/helm/rhacs-terraform/terraform_cluster.sh @@ -27,6 +27,7 @@ load_external_config cloudwatch-exporter CLOUDWATCH_EXPORTER_ load_external_config logging LOGGING_ load_external_config observability OBSERVABILITY_ load_external_config secured-cluster SECURED_CLUSTER_ +load_external_config quay/rhacs-eng QUAY_ case $ENVIRONMENT in dev) @@ -105,7 +106,6 @@ fi OPERATOR_SOURCE="redhat-operators" OPERATOR_USE_UPSTREAM="${OPERATOR_USE_UPSTREAM:-false}" if [[ "${OPERATOR_USE_UPSTREAM}" == "true" ]]; then - load_external_config quay/rhacs-eng QUAY_ quay_basic_auth="${QUAY_READ_ONLY_USERNAME}:${QUAY_READ_ONLY_PASSWORD}" pull_secret_json="$(mktemp)" trap 'rm -f "${pull_secret_json}"' EXIT @@ -149,6 +149,9 @@ invoke_helm "${SCRIPT_DIR}" rhacs-terraform \ --set fleetshardSync.resources.requests.memory="${FLEETSHARD_SYNC_MEMORY_REQUEST}" \ --set fleetshardSync.resources.limits.cpu="${FLEETSHARD_SYNC_CPU_LIMIT}" \ --set fleetshardSync.resources.limits.memory="${FLEETSHARD_SYNC_MEMORY_LIMIT}" \ + --set fleetshardSync.imageCredentials.registry="quay.io" \ + --set fleetshardSync.imageCredentials.username="${QUAY_READ_ONLY_USERNAME}" \ + --set fleetshardSync.imageCredentials.password="${QUAY_READ_ONLY_PASSWORD}" \ --set cloudwatch.aws.accessKeyId="${CLOUDWATCH_EXPORTER_AWS_ACCESS_KEY_ID:-}" \ --set cloudwatch.aws.secretAccessKey="${CLOUDWATCH_EXPORTER_AWS_SECRET_ACCESS_KEY:-}" \ --set cloudwatch.clusterName="${CLUSTER_NAME}" \ diff --git a/dp-terraform/helm/rhacs-terraform/values.yaml b/dp-terraform/helm/rhacs-terraform/values.yaml index 33d3c5e9b1..b75974e041 100644 --- a/dp-terraform/helm/rhacs-terraform/values.yaml +++ b/dp-terraform/helm/rhacs-terraform/values.yaml @@ -50,6 +50,11 @@ fleetshardSync: limits: cpu: "500m" memory: "512Mi" + imageCredentials: + registry: quay.io + username: "" + password: "" + email: "quayuser@example.com" acsOperator: enabled: false diff --git a/go.mod b/go.mod index 432740ea9a..b02ff79ae1 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Nerzal/gocloak/v11 v11.2.0 github.com/antihax/optional v1.0.0 github.com/auth0/go-jwt-middleware/v2 v2.1.0 - github.com/aws/aws-sdk-go v1.44.289 + github.com/aws/aws-sdk-go v1.44.300 github.com/bxcodec/faker/v3 v3.8.1 github.com/caarlos0/env/v6 v6.10.1 github.com/coreos/go-oidc/v3 v3.6.0 @@ -37,7 +37,7 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.0 - github.com/prometheus/client_golang v1.15.1 + github.com/prometheus/client_golang v1.16.0 github.com/prometheus/client_model v0.4.0 github.com/prometheus/common v0.44.0 github.com/redhat-developer/app-services-sdk-core/app-services-sdk-go/serviceaccountmgmt v0.0.0-20230323122535-49460b57cc45 @@ -151,7 +151,7 @@ require ( github.com/openshift/client-go v0.0.0-20200623090625-83993cebb5ae // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/prometheus/procfs v0.9.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/rivo/uniseg v0.4.3 // indirect github.com/segmentio/analytics-go/v3 v3.2.1 // indirect github.com/segmentio/backo-go v1.0.1 // indirect diff --git a/go.sum b/go.sum index c99e341700..b2860ad99a 100644 --- a/go.sum +++ b/go.sum @@ -95,8 +95,8 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/auth0/go-jwt-middleware/v2 v2.1.0 h1:VU4LsC3aFPoqXVyEp8EixU6FNM+ZNIjECszRTvtGQI8= github.com/auth0/go-jwt-middleware/v2 v2.1.0/go.mod h1:CpzcJoleayAACpv+vt0AP8/aYn5TDngsqzLapV1nM4c= -github.com/aws/aws-sdk-go v1.44.289 h1:5CVEjiHFvdiVlKPBzv0rjG4zH/21W/onT18R5AH/qx0= -github.com/aws/aws-sdk-go v1.44.289/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.300 h1:Zn+3lqgYahIf9yfrwZ+g+hq/c3KzUBaQ8wqY/ZXiAbY= +github.com/aws/aws-sdk-go v1.44.300/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -630,8 +630,8 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= -github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -649,8 +649,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= +github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/redhat-developer/app-services-sdk-core/app-services-sdk-go v0.1.0/go.mod h1:JPNDOitDoHoHk5ZPRjfOxHQhE4Br0WtiyV8m43E0rso= github.com/redhat-developer/app-services-sdk-core/app-services-sdk-go/serviceaccountmgmt v0.0.0-20230323122535-49460b57cc45 h1:zB7YuR81lby8jPK9CKIvzKQIrbpooR7R2lr5l3aL5KE= github.com/redhat-developer/app-services-sdk-core/app-services-sdk-go/serviceaccountmgmt v0.0.0-20230323122535-49460b57cc45/go.mod h1:9UjE86bWDvSfAwSAqweZPRNEAjAgI0ZvKYMIoz06qd0= diff --git a/internal/dinosaur/pkg/handlers/admin_dinosaur.go b/internal/dinosaur/pkg/handlers/admin_dinosaur.go index 54affc7974..c329b7277a 100644 --- a/internal/dinosaur/pkg/handlers/admin_dinosaur.go +++ b/internal/dinosaur/pkg/handlers/admin_dinosaur.go @@ -2,13 +2,11 @@ package handlers import ( + "encoding/json" "fmt" + "io" "net/http" - "github.com/stackrox/acs-fleet-manager/pkg/services/account" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - "github.com/gorilla/mux" "github.com/stackrox/acs-fleet-manager/internal/dinosaur/pkg/api/admin/private" "github.com/stackrox/acs-fleet-manager/internal/dinosaur/pkg/api/dbapi" @@ -21,6 +19,9 @@ import ( "github.com/stackrox/acs-fleet-manager/pkg/errors" "github.com/stackrox/acs-fleet-manager/pkg/handlers" coreServices "github.com/stackrox/acs-fleet-manager/pkg/services" + "github.com/stackrox/acs-fleet-manager/pkg/services/account" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/strategicpatch" ) type adminCentralHandler struct { @@ -192,37 +193,27 @@ func (h adminCentralHandler) DbDelete(w http.ResponseWriter, r *http.Request) { handlers.HandleDelete(w, r, cfg, http.StatusOK) } -func updateResourcesList(to *corev1.ResourceList, from map[string]string) error { - newResourceList := to.DeepCopy() - for name, qty := range from { - if qty == "" { - continue - } - resourceName, isSupported := ValidateResourceName(name) +func validateResourcesList(rl *corev1.ResourceList) error { + if rl == nil { + return nil + } + for name := range *rl { + _, isSupported := validateResourceName(name) if !isSupported { return fmt.Errorf("resource type %q is not supported", name) } - resourceQty, err := resource.ParseQuantity(qty) - if err != nil { - return fmt.Errorf("parsing %s quantity %q: %w", resourceName, qty, err) - } - if newResourceList == nil { - newResourceList = corev1.ResourceList(make(map[corev1.ResourceName]resource.Quantity)) - } - newResourceList[resourceName] = resourceQty } - *to = newResourceList return nil } -func updateCoreV1Resources(to *corev1.ResourceRequirements, from private.ResourceRequirements) error { +func validateCoreV1Resources(to *corev1.ResourceRequirements) error { newResources := to.DeepCopy() - err := updateResourcesList(&newResources.Limits, from.Limits) + err := validateResourcesList(&newResources.Limits) if err != nil { return err } - err = updateResourcesList(&newResources.Requests, from.Requests) + err = validateResourcesList(&newResources.Requests) if err != nil { return err } @@ -231,106 +222,111 @@ func updateCoreV1Resources(to *corev1.ResourceRequirements, from private.Resourc return nil } -// updateCentralFromPrivateAPI updates the CentralSpec using the non-zero fields from the API's CentralSpec. -func updateCentralFromPrivateAPI(c *dbapi.CentralSpec, apiCentralSpec *private.CentralSpec) error { - err := updateCoreV1Resources(&c.Resources, apiCentralSpec.Resources) +// validateCentralSpec validates the CentralSpec using the non-zero fields from the API's CentralSpec. +func validateCentralSpec(c *dbapi.CentralSpec) error { + err := validateCoreV1Resources(&c.Resources) if err != nil { return fmt.Errorf("updating resources within CentralSpec: %w", err) } return nil } -// updateScannerFromPrivateAPI updates the ScannerSpec using the non-zero fields from the API's ScannerSpec. -func updateScannerFromPrivateAPI(s *dbapi.ScannerSpec, apiSpec *private.ScannerSpec) error { +// validateScannerSpec validates the ScannerSpec using the non-zero fields from the API's ScannerSpec. +func validateScannerSpec(s *dbapi.ScannerSpec) error { var err error - new := *s - - err = updateCoreV1Resources(&new.Analyzer.Resources, apiSpec.Analyzer.Resources) + err = validateCoreV1Resources(&s.Analyzer.Resources) if err != nil { return fmt.Errorf("updating resources within ScannerSpec Analyzer: %w", err) } - err = updateScannerAnalyzerScaling(&new.Analyzer.Scaling, apiSpec.Analyzer.Scaling) - if err != nil { - return fmt.Errorf("updating scaling configuration within ScannerSpec Analyzer: %w", err) - } - err = updateCoreV1Resources(&new.Db.Resources, apiSpec.Db.Resources) + err = validateCoreV1Resources(&s.Db.Resources) if err != nil { return fmt.Errorf("updating resources within ScannerSpec DB: %w", err) } - *s = new return nil } -func updateScannerAnalyzerScaling(s *dbapi.ScannerAnalyzerScaling, apiScaling private.ScannerSpecAnalyzerScaling) error { - if apiScaling.AutoScaling != "" { - s.AutoScaling = apiScaling.AutoScaling +func updateCentralRequest(request *dbapi.CentralRequest, strategicPatch []byte) error { + + var patchMap map[string]interface{} + err := json.Unmarshal(strategicPatch, &patchMap) + if err != nil { + return fmt.Errorf("unmarshalling strategic merge patch: %w", err) + } + // only keep central and scanner keys + for k := range patchMap { + if k != "central" && k != "scanner" { + delete(patchMap, k) + } } - if apiScaling.MaxReplicas > 0 { - s.MaxReplicas = apiScaling.MaxReplicas + patchBytes, err := json.Marshal(patchMap) + if err != nil { + return fmt.Errorf("marshalling strategic merge patch: %w", err) } - if apiScaling.MinReplicas > 0 { - s.MinReplicas = apiScaling.MinReplicas + + var centralBytes = "{}" + if len(request.Central) > 0 { + centralBytes = string(request.Central) } - if apiScaling.Replicas > 0 { - s.Replicas = apiScaling.Replicas + var scannerBytes = "{}" + if len(request.Scanner) > 0 { + scannerBytes = string(request.Scanner) } - return nil -} -func updateCentralRequest(request *dbapi.CentralRequest, updateRequest *private.CentralUpdateRequest) error { - if updateRequest == nil { - return nil + var originalBytes = fmt.Sprintf("{\"central\":%s,\"scanner\":%s,\"forceReconcile\":\"%s\"}", centralBytes, scannerBytes, request.ForceReconcile) + + type Original struct { + Central *dbapi.CentralSpec `json:"central,omitempty"` + Scanner *dbapi.ScannerSpec `json:"scanner,omitempty"` + ForceReconcile string `json:"forceReconcile,omitempty"` } - centralSpec, err := request.GetCentralSpec() + // apply the patch + mergedBytes, err := strategicpatch.StrategicMergePatch([]byte(originalBytes), patchBytes, Original{}) if err != nil { - return fmt.Errorf("retrieving CentralSpec from CentralRequest: %w", err) + return fmt.Errorf("applying strategic merge patch: %w", err) } - scannerSpec, err := request.GetScannerSpec() - if err != nil { - return fmt.Errorf("retrieving ScannerSpec from CentralRequest: %w", err) + var merged Original + if err := json.Unmarshal(mergedBytes, &merged); err != nil { + return fmt.Errorf("unmarshalling merged CentralRequest: %w", err) + } + + if merged.Central == nil { + merged.Central = &dbapi.CentralSpec{} } - err = updateCentralFromPrivateAPI(centralSpec, &updateRequest.Central) + if merged.Scanner == nil { + merged.Scanner = &dbapi.ScannerSpec{} + } + + err = validateCentralSpec(merged.Central) if err != nil { return fmt.Errorf("updating CentralSpec from CentralUpdateRequest: %w", err) } - err = updateScannerFromPrivateAPI(scannerSpec, &updateRequest.Scanner) + err = validateScannerSpec(merged.Scanner) if err != nil { return fmt.Errorf("updating ScannerSpec from CentralUpdateRequest: %w", err) } - err = ValidateScannerAnalyzerScaling(&scannerSpec.Analyzer.Scaling) - if err != nil { - return err - } - - // TODO: We should also validate the resource configuration here. If the configuration is invalid - // the operator will not be able to create the Central instance and we could fail early here. - - new := *request - - err = new.SetCentralSpec(centralSpec) + newCentralBytes, err := json.Marshal(merged.Central) if err != nil { - return fmt.Errorf("updating CentralSpec within CentralRequest: %w", err) + return fmt.Errorf("marshalling CentralSpec: %w", err) } - - err = new.SetScannerSpec(scannerSpec) + newScannerBytes, err := json.Marshal(merged.Scanner) if err != nil { - return fmt.Errorf("updating ScannerSpec within CentralRequest: %w", err) + return fmt.Errorf("marshalling ScannerSpec: %w", err) } - new.ForceReconcile = updateRequest.ForceReconcile + request.Central = newCentralBytes + request.Scanner = newScannerBytes + request.ForceReconcile = merged.ForceReconcile - *request = new return nil + } // Update a Central instance. func (h adminCentralHandler) Update(w http.ResponseWriter, r *http.Request) { - var centralUpdateReq private.CentralUpdateRequest cfg := &handlers.HandlerConfig{ - MarshalInto: ¢ralUpdateReq, Action: func() (i interface{}, serviceError *errors.ServiceError) { id := mux.Vars(r)["id"] ctx := r.Context() @@ -339,7 +335,17 @@ func (h adminCentralHandler) Update(w http.ResponseWriter, r *http.Request) { return nil, svcErr } - err := updateCentralRequest(centralRequest, ¢ralUpdateReq) + updateBytes, err := io.ReadAll(r.Body) + if err != nil { + return nil, errors.NewWithCause(errors.ErrorBadRequest, err, "Reading request body: %s", err.Error()) + } + + // unmarshal the update into a private.CentralUpdateRequest to ensure that it is well-formed + if err := json.Unmarshal(updateBytes, &private.CentralUpdateRequest{}); err != nil { + return nil, errors.NewWithCause(errors.ErrorBadRequest, err, "Unmarshalling request body: %s", err.Error()) + } + + err = updateCentralRequest(centralRequest, updateBytes) if err != nil { return nil, errors.NewWithCause(errors.ErrorBadRequest, err, "Updating CentralRequest: %s", err.Error()) } diff --git a/internal/dinosaur/pkg/handlers/admin_dinosaur_test.go b/internal/dinosaur/pkg/handlers/admin_dinosaur_test.go new file mode 100644 index 0000000000..ca2425e615 --- /dev/null +++ b/internal/dinosaur/pkg/handlers/admin_dinosaur_test.go @@ -0,0 +1,223 @@ +package handlers + +import ( + "encoding/json" + "testing" + + "github.com/stackrox/acs-fleet-manager/internal/dinosaur/pkg/api/dbapi" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_updateCentralRequest(t *testing.T) { + tests := []struct { + name string + state string + patch string + wantCentral string + wantScanner string + wantForceReconcile string + wantErr func(t *testing.T, err error) + }{ + { + name: "empty update on empty central should have no effect", + state: `{}`, + patch: `{}`, + wantCentral: `{"resources":{}}`, + }, { + name: "empty update on defined central should have no effect", + state: `{"central":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}`, + patch: `{}`, + wantCentral: `{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}`, + }, { + name: "replacing central limits", + state: `{"central":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}`, + patch: `{"central":{"resources":{"limits":{"cpu":"2","memory":"2"}}}}`, + wantCentral: `{"resources":{"limits":{"cpu":"2","memory":"2"},"requests":{"cpu":"1","memory":"1"}}}`, + }, { + name: "replacing central limits when requests are not set", + state: `{"central":{"resources":{"limits":{"cpu":"1","memory":"1"}}}}`, + patch: `{"central":{"resources":{"limits":{"cpu":"2","memory":"2"}}}}`, + wantCentral: `{"resources":{"limits":{"cpu":"2","memory":"2"}}}`, + }, { + name: "replacing central CPU limits", + state: `{"central":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}`, + patch: `{"central":{"resources":{"limits":{"cpu":"2"}}}}`, + wantCentral: `{"resources":{"limits":{"cpu":"2","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}`, + }, { + name: "unsetting central CPU limits", + state: `{"central":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}`, + patch: `{"central":{"resources":{"limits":{"cpu":null}}}}`, + wantCentral: `{"resources":{"limits":{"memory":"1"},"requests":{"cpu":"1","memory":"1"}}}`, + }, { + name: "unsetting central limits", + state: `{"central":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}`, + patch: `{"central":{"resources":{"limits":null}}}`, + wantCentral: `{"resources":{"requests":{"cpu":"1","memory":"1"}}}`, + }, { + name: "unsetting central resources", + state: `{"central":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}`, + patch: `{"central":{"resources":null}}`, + wantCentral: `{"resources":{}}`, + }, { + name: "unsetting central altogether", + state: `{"central":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}`, + patch: `{"central":null}`, + wantCentral: `{"resources":{}}`, + }, { + name: "replacing central CPU limits when memory is not set", + state: `{"central":{"resources":{"limits":{"cpu":"1"}}}}`, + patch: `{"central":{"resources":{"limits":{"cpu":"2"}}}}`, + wantCentral: `{"resources":{"limits":{"cpu":"2"}}}`, + }, { + name: "replacing central CPU limits when CPU is not set", + state: `{"central":{"resources":{"limits":{"memory":"1"}}}}`, + patch: `{"central":{"resources":{"limits":{"cpu":"2"}}}}`, + wantCentral: `{"resources":{"limits":{"cpu":"2","memory":"1"}}}`, + }, { + name: "update with existing central", + state: `{"central":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}`, + patch: `{"central":{"resources":{"requests":{"cpu":"2","memory":"2"}}}}`, + wantCentral: `{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"2","memory":"2"}}}`, + }, { + name: "should ignore unknown fields", + state: `{"central":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}`, + patch: `{"central":{"resources":{"requests":{"cpu":"2","memory":"2"}},"unknown":{"foo":"bar"}}}`, + wantCentral: `{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"2","memory":"2"}}}`, + }, { + name: "empty update on empty scanner should have no effect", + state: `{"scanner":{}}`, + patch: `{}`, + wantScanner: `{"analyzer":{"resources":{},"scaling":{}},"db":{"resources":{}}}`, + }, { + name: "empty update on defined scanner should have no effect", + state: `{"scanner":{"analyzer":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}},"scaling":{"autoScaling":"Enabled","replicas":1,"minReplicas":1,"maxReplicas":1}},"db":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}}`, + patch: `{}`, + wantScanner: `{"analyzer":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}},"scaling":{"autoScaling":"Enabled","replicas":1,"minReplicas":1,"maxReplicas":1}},"db":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}`, + }, { + name: "replacing scanner analyzer resources", + state: `{"scanner":{"analyzer":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}}`, + patch: `{"scanner":{"analyzer":{"resources":{"limits":{"cpu":"2","memory":"2"}}}}}`, + wantScanner: `{"analyzer":{"resources":{"limits":{"cpu":"2","memory":"2"},"requests":{"cpu":"1","memory":"1"}},"scaling":{}},"db":{"resources":{}}}`, + }, { + name: "replacing scanner analyzer CPU resources", + state: `{"scanner":{"analyzer":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}}`, + patch: `{"scanner":{"analyzer":{"resources":{"limits":{"cpu":"2"}}}}}`, + wantScanner: `{"analyzer":{"resources":{"limits":{"cpu":"2","memory":"1"},"requests":{"cpu":"1","memory":"1"}},"scaling":{}},"db":{"resources":{}}}`, + }, { + name: "replacing scanner analyzer memory resources", + state: `{"scanner":{"analyzer":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}}`, + patch: `{"scanner":{"analyzer":{"resources":{"limits":{"memory":"2"}}}}}`, + wantScanner: `{"analyzer":{"resources":{"limits":{"cpu":"1","memory":"2"},"requests":{"cpu":"1","memory":"1"}},"scaling":{}},"db":{"resources":{}}}`, + }, { + name: "replacing scanner analyzer requests", + state: `{"scanner":{"analyzer":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}}`, + patch: `{"scanner":{"analyzer":{"resources":{"requests":{"cpu":"2","memory":"2"}}}}}`, + wantScanner: `{"analyzer":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"2","memory":"2"}},"scaling":{}},"db":{"resources":{}}}`, + }, { + name: "replacing scanner analyzer CPU requests", + state: `{"scanner":{"analyzer":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}}`, + patch: `{"scanner":{"analyzer":{"resources":{"requests":{"cpu":"2"}}}}}`, + wantScanner: `{"analyzer":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"2","memory":"1"}},"scaling":{}},"db":{"resources":{}}}`, + }, { + name: "replacing scanner analyzer scaling", + state: `{"scanner":{"analyzer":{"scaling":{"autoScaling":"Enabled","replicas":1,"minReplicas":1,"maxReplicas":1}}}}`, + patch: `{"scanner":{"analyzer":{"scaling":{"autoScaling":"Disabled","replicas":2,"minReplicas":2,"maxReplicas":2}}}}`, + wantScanner: `{"analyzer":{"resources":{},"scaling":{"autoScaling":"Disabled","replicas":2,"minReplicas":2,"maxReplicas":2}},"db":{"resources":{}}}`, + }, { + name: "replacing scanner analyzer scaling replicas", + state: `{"scanner":{"analyzer":{"scaling":{"autoScaling":"Enabled","replicas":1,"minReplicas":1,"maxReplicas":1}}}}`, + patch: `{"scanner":{"analyzer":{"scaling":{"replicas":2}}}}`, + wantScanner: `{"analyzer":{"resources":{},"scaling":{"autoScaling":"Enabled","replicas":2,"minReplicas":1,"maxReplicas":1}},"db":{"resources":{}}}`, + }, { + name: "replacing scanner db", + state: `{"scanner":{"db":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}}`, + patch: `{"scanner":{"db":{"resources":{"limits":{"cpu":"2","memory":"2"},"requests":{"cpu":"2","memory":"2"}}}}}`, + wantScanner: `{"analyzer":{"resources":{},"scaling":{}},"db":{"resources":{"limits":{"cpu":"2","memory":"2"},"requests":{"cpu":"2","memory":"2"}}}}`, + }, { + name: "replacing scanner db CPU request", + state: `{"scanner":{"db":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}}`, + patch: `{"scanner":{"db":{"resources":{"limits":{"cpu":"2"}}}}}`, + wantScanner: `{"analyzer":{"resources":{},"scaling":{}},"db":{"resources":{"limits":{"cpu":"2","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}`, + }, { + name: "replacing scanner db requests", + state: `{"scanner":{"db":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}}`, + patch: `{"scanner":{"db":{"resources":{"requests":{"cpu":"2","memory":"2"}}}}}`, + wantScanner: `{"analyzer":{"resources":{},"scaling":{}},"db":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"2","memory":"2"}}}}`, + }, { + name: "unset scanner analyzer resources", + state: `{"scanner":{"analyzer":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}}`, + patch: `{"scanner":{"analyzer":{"resources":null}}}`, + wantScanner: `{"analyzer":{"resources":{},"scaling":{}},"db":{"resources":{}}}`, + }, { + name: "unset scanner analyzer scaling", + state: `{"scanner":{"analyzer":{"scaling":{"autoScaling":"Enabled","replicas":1,"minReplicas":1,"maxReplicas":1}}}}`, + patch: `{"scanner":{"analyzer":{"scaling":null}}}`, + wantScanner: `{"analyzer":{"resources":{},"scaling":{}},"db":{"resources":{}}}`, + }, { + name: "unset scanner db resources", + state: `{"scanner":{"db":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}}`, + patch: `{"scanner":{"db":{"resources":null}}}`, + wantScanner: `{"analyzer":{"resources":{},"scaling":{}},"db":{"resources":{}}}`, + }, { + name: "unset scanner analyzer resources limits", + state: `{"scanner":{"analyzer":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}}`, + patch: `{"scanner":{"analyzer":{"resources":{"limits":null}}}}`, + wantScanner: `{"analyzer":{"resources":{"requests":{"cpu":"1","memory":"1"}},"scaling":{}},"db":{"resources":{}}}`, + }, { + name: "replacing forceReconcile", + state: `{"forceReconcile":"foo"}`, + patch: `{"forceReconcile":"bar"}`, + wantForceReconcile: "bar", + }, { + name: "unsetting forceReconcile", + state: `{"forceReconcile":"foo"}`, + patch: `{"forceReconcile":null}`, + wantForceReconcile: "", + }, { + name: "setting forceReconcile to empty string", + state: `{"forceReconcile":"foo"}`, + patch: `{"forceReconcile":""}`, + wantForceReconcile: "", + }, { + name: "should fail if the patch is invalid json", + state: `{"scanner":{"analyzer":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}}`, + patch: `foo`, + wantErr: func(t *testing.T, err error) { + assert.Error(t, err) + }, + }, { + name: "should fail if the resource name is not cpu or memory", + state: `{"scanner":{"analyzer":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}}`, + patch: `{"scanner":{"analyzer":{"resources":{"limits":{"foo":"1"}}}}}`, + wantErr: func(t *testing.T, err error) { + assert.Error(t, err) + }, + }, { + name: "should fail if the resource value is invalid", + state: `{"scanner":{"analyzer":{"resources":{"limits":{"cpu":"1","memory":"1"},"requests":{"cpu":"1","memory":"1"}}}}}`, + patch: `{"scanner":{"analyzer":{"resources":{"limits":{"cpu":foo}}}}}`, + wantErr: func(t *testing.T, err error) { + assert.Error(t, err) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var request *dbapi.CentralRequest + require.NoError(t, json.Unmarshal([]byte(tt.state), &request)) + err := updateCentralRequest(request, []byte(tt.patch)) + if tt.wantErr != nil { + tt.wantErr(t, err) + } else { + require.NoError(t, err) + if len(tt.wantScanner) > 0 { + assert.Equal(t, string(tt.wantScanner), string(request.Scanner)) + } + if len(tt.wantCentral) > 0 { + assert.Equal(t, string(tt.wantCentral), string(request.Central)) + } + } + }) + } +} diff --git a/internal/dinosaur/pkg/handlers/validation.go b/internal/dinosaur/pkg/handlers/validation.go index 3a723bec9f..cf068d6332 100644 --- a/internal/dinosaur/pkg/handlers/validation.go +++ b/internal/dinosaur/pkg/handlers/validation.go @@ -246,15 +246,15 @@ func ValidateScannerAnalyzerScaling(scaling *dbapi.ScannerAnalyzerScaling) error return nil } -// ValidateResourceName checks if the given name denotes a supported resource. -func ValidateResourceName(name string) (corev1.ResourceName, bool) { - resourceName := corev1.ResourceName(name) +// validateResourceName checks if the given name denotes a supported resource. +func validateResourceName(name corev1.ResourceName) (corev1.ResourceName, bool) { + resourceName := name for _, supportedResource := range supportedResources { if supportedResource == resourceName { return resourceName, true } } - return corev1.ResourceName(""), false + return "", false } // ValidateCentralDefaultVersion validates the given version diff --git a/probe/Dockerfile b/probe/Dockerfile index beb71a949b..5029076739 100644 --- a/probe/Dockerfile +++ b/probe/Dockerfile @@ -23,6 +23,13 @@ ENTRYPOINT [ "/stackrox/dlv" , "--listen=:40000", "--headless=true", "--api-vers CMD ["start"] FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8 as standard + +RUN microdnf install shadow-utils + +RUN useradd -u 1001 unprivilegeduser +# Switch to non-root user +USER unprivilegeduser + COPY --from=build-standard /src/probe/bin /stackrox/ EXPOSE 7070 ENTRYPOINT ["/stackrox/probe"] diff --git a/probe/cmd/probe/main.go b/probe/cmd/probe/main.go index 75e68e195a..c2ccb99b97 100644 --- a/probe/cmd/probe/main.go +++ b/probe/cmd/probe/main.go @@ -27,7 +27,7 @@ func main() { glog.Fatal(err) } - if metricsServer := metrics.NewMetricsServer(config.MetricsAddress); metricsServer != nil { + if metricsServer := metrics.NewMetricsServer(config.MetricsAddress, config.DataPlaneRegion); metricsServer != nil { defer metrics.CloseMetricsServer(metricsServer) go metrics.ListenAndServe(metricsServer) } else { diff --git a/probe/pkg/metrics/metrics.go b/probe/pkg/metrics/metrics.go index 33ea9201ca..f6093c6f10 100644 --- a/probe/pkg/metrics/metrics.go +++ b/probe/pkg/metrics/metrics.go @@ -42,6 +42,13 @@ func (m *Metrics) Register(r prometheus.Registerer) { r.MustRegister(m.lastFailureTimestamp) } +// Init sets initial values for the gauge metrics. +func (m *Metrics) Init(region string) { + m.lastFailureTimestamp.With(prometheus.Labels{regionLabelName: region}).Set(0) + m.lastStartedTimestamp.With(prometheus.Labels{regionLabelName: region}).Set(0) + m.lastSuccessTimestamp.With(prometheus.Labels{regionLabelName: region}).Set(0) +} + // IncStartedRuns increments the metric counter for started probe runs. func (m *Metrics) IncStartedRuns(region string) { m.startedRuns.With(prometheus.Labels{regionLabelName: region}).Inc() diff --git a/probe/pkg/metrics/metrics_test.go b/probe/pkg/metrics/metrics_test.go index 4461234752..07f50ffb35 100644 --- a/probe/pkg/metrics/metrics_test.go +++ b/probe/pkg/metrics/metrics_test.go @@ -6,12 +6,21 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" + io_prometheus_client "github.com/prometheus/client_model/go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var regionValue = "us-east-1" +func getMetricSeries(t *testing.T, registry *prometheus.Registry, name string) *io_prometheus_client.Metric { + metrics := serveMetrics(t, registry) + require.Contains(t, metrics, name) + targetMetric := metrics[name] + require.NotEmpty(t, targetMetric.Metric) + return targetMetric.Metric[0] +} + func TestCounterIncrements(t *testing.T) { const expectedIncrement = 1.0 @@ -43,15 +52,12 @@ func TestCounterIncrements(t *testing.T) { tc := tc t.Run(tc.metricName, func(t *testing.T) { m := newMetrics() + registry := initPrometheus(m, regionValue) tc.callIncrementFunc(m) - metrics := serveMetrics(t, m) - require.Contains(t, metrics, tc.metricName) - targetMetric := metrics[tc.metricName] + targetSeries := getMetricSeries(t, registry, tc.metricName) // Test that the metrics value is 1 after calling the incrementFunc. - require.NotEmpty(t, targetMetric.Metric) - targetSeries := targetMetric.Metric[0] value := targetSeries.GetCounter().GetValue() assert.Equalf(t, expectedIncrement, value, "metric %s has unexpected value", tc.metricName) label := targetSeries.GetLabel()[0] @@ -90,16 +96,17 @@ func TestTimestampGauges(t *testing.T) { tc := tc t.Run(tc.metricName, func(t *testing.T) { m := newMetrics() + registry := initPrometheus(m, regionValue) lowerBound := time.Now().Unix() - tc.callSetTimestampFunc(m) - - metrics := serveMetrics(t, m) - require.Contains(t, metrics, tc.metricName) - targetMetric := metrics[tc.metricName] - require.NotEmpty(t, targetMetric.Metric) - targetSeries := targetMetric.Metric[0] + targetSeries := getMetricSeries(t, registry, tc.metricName) value := int64(targetSeries.GetGauge().GetValue()) + assert.Zero(t, value) + + tc.callSetTimestampFunc(m) + + targetSeries = getMetricSeries(t, registry, tc.metricName) + value = int64(targetSeries.GetGauge().GetValue()) assert.GreaterOrEqualf(t, value, lowerBound, "metric %s has unexpected value", tc.metricName) label := targetSeries.GetLabel()[0] assert.Containsf(t, label.GetName(), regionLabelName, "metric %s has unexpected label", tc.metricName) @@ -126,16 +133,13 @@ func TestHistograms(t *testing.T) { tc := tc t.Run(tc.metricName, func(t *testing.T) { m := newMetrics() + registry := initPrometheus(m, regionValue) expectedCount := uint64(2) expectedSum := 480.0 tc.callObserveFunc(m) - metrics := serveMetrics(t, m) - require.Contains(t, metrics, tc.metricName) - targetMetric := metrics[tc.metricName] + targetSeries := getMetricSeries(t, registry, tc.metricName) - require.NotEmpty(t, targetMetric.Metric) - targetSeries := targetMetric.Metric[0] count := targetSeries.GetHistogram().GetSampleCount() sum := targetSeries.GetHistogram().GetSampleSum() assert.Equalf(t, expectedCount, count, "expected metric: %s to have a count of %v", tc.metricName, expectedCount) diff --git a/probe/pkg/metrics/server.go b/probe/pkg/metrics/server.go index 4ce78458b6..fbaeedab28 100644 --- a/probe/pkg/metrics/server.go +++ b/probe/pkg/metrics/server.go @@ -10,8 +10,9 @@ import ( ) // NewMetricsServer returns the metrics server. -func NewMetricsServer(address string) *http.Server { - return newMetricsServer(address, MetricsInstance()) +func NewMetricsServer(address string, region string) *http.Server { + registry := initPrometheus(MetricsInstance(), region) + return newMetricsServer(address, registry) } // ListenAndServe listens for incoming requests and serves the metrics. @@ -28,14 +29,18 @@ func CloseMetricsServer(server *http.Server) { } } -func newMetricsServer(address string, customMetrics *Metrics) *http.Server { +func initPrometheus(customMetrics *Metrics, region string) *prometheus.Registry { registry := prometheus.NewRegistry() // Register default metrics to use a dedicated registry instead of prometheus.DefaultRegistry. // This makes it easier to isolate metric state when unit testing this package. registry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{})) registry.MustRegister(prometheus.NewGoCollector()) customMetrics.Register(registry) + customMetrics.Init(region) + return registry +} +func newMetricsServer(address string, registry *prometheus.Registry) *http.Server { mux := http.NewServeMux() mux.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) diff --git a/probe/pkg/metrics/server_test.go b/probe/pkg/metrics/server_test.go index 61b89505bb..70e6a9cce0 100644 --- a/probe/pkg/metrics/server_test.go +++ b/probe/pkg/metrics/server_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/prometheus/client_golang/prometheus" io_prometheus_client "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" "github.com/stretchr/testify/assert" @@ -15,13 +16,14 @@ import ( type metricResponse map[string]*io_prometheus_client.MetricFamily func TestMetricsServerCorrectAddress(t *testing.T) { - server := NewMetricsServer(":8081") + server := NewMetricsServer(":8081", regionValue) defer server.Close() assert.Equal(t, ":8081", server.Addr) } func TestMetricsServerServesDefaultMetrics(t *testing.T) { - metrics := serveMetrics(t, newMetrics()) + registry := initPrometheus(newMetrics(), regionValue) + metrics := serveMetrics(t, registry) assert.Contains(t, metrics, "go_memstats_alloc_bytes", "expected metrics to contain go default metrics but it did not") } @@ -35,7 +37,8 @@ func TestMetricsServerServesCustomMetrics(t *testing.T) { customMetrics.SetLastSuccessTimestamp(regionValue) customMetrics.SetLastFailureTimestamp(regionValue) customMetrics.ObserveTotalDuration(time.Minute, regionValue) - metrics := serveMetrics(t, customMetrics) + registry := initPrometheus(customMetrics, regionValue) + metrics := serveMetrics(t, registry) expectedKeys := []string{ "acs_probe_runs_started_total", @@ -52,12 +55,12 @@ func TestMetricsServerServesCustomMetrics(t *testing.T) { } } -func serveMetrics(t *testing.T, customMetrics *Metrics) metricResponse { +func serveMetrics(t *testing.T, registry *prometheus.Registry) metricResponse { rec := httptest.NewRecorder() req, err := http.NewRequest(http.MethodGet, "/metrics", nil) require.NoError(t, err, "failed creating metrics requests") - server := newMetricsServer(":8081", customMetrics) + server := newMetricsServer(":8081", registry) defer server.Close() server.Handler.ServeHTTP(rec, req) require.Equal(t, http.StatusOK, rec.Code, "status code should be OK") diff --git a/scripts/lib/external_config.sh b/scripts/lib/external_config.sh index 9f786d72a6..30d56a64ff 100644 --- a/scripts/lib/external_config.sh +++ b/scripts/lib/external_config.sh @@ -67,8 +67,10 @@ load_external_config() { local prefix="${2:-}" local parameter_store_output local secrets_manager_output - parameter_store_output=$(chamber env "$service") - secrets_manager_output=$(chamber env "$service" -b secretsmanager) + parameter_store_output=$(chamber env "$service" --backend ssm) + # chamber fails for secretsmanager backend, but not for ssm (parameter store). + # We suppress pipefail error for secretsmanager backend to get similar behaviour. + secrets_manager_output=$(chamber env "$service" --backend secretsmanager) || true [[ -z "$parameter_store_output" && -z "$secrets_manager_output" ]] && echo "WARNING: no parameters found under '/$service' of this environment" eval "$(printf '%s\n%s' "$parameter_store_output" "$secrets_manager_output" | sed -E "s/(^export +)(.*)/readonly ${prefix}\2/")" } diff --git a/tools/go.mod b/tools/go.mod index 657518fe99..bc59177cda 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -6,13 +6,13 @@ require ( github.com/go-bindata/go-bindata/v3 v3.1.4-0.20210427095211-26949cc13d95 github.com/matryer/moq v0.3.2 github.com/onsi/ginkgo/v2 v2.11.0 - github.com/segmentio/chamber/v2 v2.13.1 - gotest.tools/gotestsum v1.10.0 + github.com/segmentio/chamber/v2 v2.13.2 + gotest.tools/gotestsum v1.10.1 ) require ( github.com/alessio/shellescape v1.4.1 // indirect - github.com/aws/aws-sdk-go v1.44.280 // indirect + github.com/aws/aws-sdk-go v1.44.298 // indirect github.com/dnephin/pflag v1.0.7 // indirect github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect diff --git a/tools/go.sum b/tools/go.sum index f11974c8d4..c9c1c5d92b 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -1,7 +1,7 @@ github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= -github.com/aws/aws-sdk-go v1.44.280 h1:UYl/yxhDxP8naok6ftWyQ9/9ZzNwjC9dvEs/j8BkGhw= -github.com/aws/aws-sdk-go v1.44.280/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go v1.44.298 h1:5qTxdubgV7PptZJmp/2qDwD2JL187ePL7VOxsSh1i3g= +github.com/aws/aws-sdk-go v1.44.298/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -71,8 +71,8 @@ github.com/segmentio/analytics-go/v3 v3.2.1 h1:G+f90zxtc1p9G+WigVyTR0xNfOghOGs/P github.com/segmentio/analytics-go/v3 v3.2.1/go.mod h1:p8owAF8X+5o27jmvUognuXxdtqvSGtD0ZrfY2kcS9bE= github.com/segmentio/backo-go v1.0.1 h1:68RQccglxZeyURy93ASB/2kc9QudzgIDexJ927N++y4= github.com/segmentio/backo-go v1.0.1/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= -github.com/segmentio/chamber/v2 v2.13.1 h1:640irCm9XMRnPkxIhxajFtp/xMumtGZJnBs1UuWd4zI= -github.com/segmentio/chamber/v2 v2.13.1/go.mod h1:oxMki7rGQkWdniKPRD4UptABY4bLBzJr1EmZVM35fY0= +github.com/segmentio/chamber/v2 v2.13.2 h1:rDy7M5qyP2qDyLla4BiUI7hIt/szSF1mYMgodNt/Rc0= +github.com/segmentio/chamber/v2 v2.13.2/go.mod h1:qdWcyFZI7mQlNZH1lWyS7Ot5drkCcounFMhXoClgm8w= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -163,7 +163,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/gotestsum v1.10.0 h1:lVO4uQJoxdsJb7jgmr1fg8QW7zGQ/tuqvsq5fHKyoHQ= -gotest.tools/gotestsum v1.10.0/go.mod h1:6JHCiN6TEjA7Kaz23q1bH0e2Dc3YJjDUZ0DmctFZf+w= +gotest.tools/gotestsum v1.10.1 h1:TOV5xZVd5HDscBLSrPXpc4/MQm6QQr/YSI9iDC62d7E= +gotest.tools/gotestsum v1.10.1/go.mod h1:6JHCiN6TEjA7Kaz23q1bH0e2Dc3YJjDUZ0DmctFZf+w= gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=