diff --git a/pipelines/graph/testdata/zz_fixture_TestForEntrypoint.svg b/pipelines/graph/testdata/zz_fixture_TestForEntrypoint.svg index 772dc06..a1baf4b 100644 --- a/pipelines/graph/testdata/zz_fixture_TestForEntrypoint.svg +++ b/pipelines/graph/testdata/zz_fixture_TestForEntrypoint.svg @@ -1,877 +1,877 @@ - - + regexp - + Grandparent_global_acrs - -Grandparent/global/acrs + +Grandparent/global/acrs Grandparent_global_mirror-oc-mirror-image - -Grandparent/global/mirror-oc-mirror-image + +Grandparent/global/mirror-oc-mirror-image Grandparent_global_acrs->Grandparent_global_mirror-oc-mirror-image - - + + Grandparent_global_imagemirror - -Grandparent/global/imagemirror + +Grandparent/global/imagemirror Grandparent_global_mirror-oc-mirror-image->Grandparent_global_imagemirror - - + + Grandparent_global_certificates - -Grandparent/global/certificates + +Grandparent/global/certificates Parent_global_output - -Parent/global/output + +Parent/global/output Grandparent_global_certificates->Parent_global_output - - + + Uncle_global_output - -Uncle/global/output + +Uncle/global/output Grandparent_global_certificates->Uncle_global_output - - + + Parent_global_add-hcp-grafana-datasource - -Parent/global/add-hcp-grafana-datasource + +Parent/global/add-hcp-grafana-datasource Parent_global_output->Parent_global_add-hcp-grafana-datasource - - + + Parent_global_add-svc-grafana-datasource - -Parent/global/add-svc-grafana-datasource + +Parent/global/add-svc-grafana-datasource Parent_global_output->Parent_global_add-svc-grafana-datasource - - + + Parent_global_ocp-acr-replication - -Parent/global/ocp-acr-replication + +Parent/global/ocp-acr-replication Parent_global_output->Parent_global_ocp-acr-replication - - + + Parent_regional_rpRegistration - -Parent/regional/rpRegistration + +Parent/regional/rpRegistration Parent_global_output->Parent_regional_rpRegistration - - + + Parent_global_svc-acr-replication - -Parent/global/svc-acr-replication + +Parent/global/svc-acr-replication Parent_global_output->Parent_global_svc-acr-replication - - + + Parent_regional_region - -Parent/regional/region + +Parent/regional/region Parent_global_output->Parent_regional_region - - + + Sibling_management_mgmt-nsp - -Sibling/management/mgmt-nsp + +Sibling/management/mgmt-nsp Uncle_global_output->Sibling_management_mgmt-nsp - - + + Uncle_global_rg-ownership - -Uncle/global/rg-ownership + +Uncle/global/rg-ownership Uncle_global_output->Uncle_global_rg-ownership - - + + Uncle_regional_delete-region - -Uncle/regional/delete-region + +Uncle/regional/delete-region Uncle_global_output->Uncle_regional_delete-region - - + + Uncle_regional_rg-ownership - -Uncle/regional/rg-ownership + +Uncle/regional/rg-ownership Uncle_global_output->Uncle_regional_rg-ownership - - + + Grandparent_global_cxChildZone - -Grandparent/global/cxChildZone + +Grandparent/global/cxChildZone Grandparent_global_cxChildZone->Parent_global_output - - + + Grandparent_global_cxChildZone->Uncle_global_output - - + + Grandparent_global_decrypt-and-ingest-secrets - -Grandparent/global/decrypt-and-ingest-secrets + +Grandparent/global/decrypt-and-ingest-secrets Grandparent_global_decrypt-and-ingest-secrets->Grandparent_global_mirror-oc-mirror-image - - + + Grandparent_global_grafana-dashboards - -Grandparent/global/grafana-dashboards + +Grandparent/global/grafana-dashboards Grandparent_global_housekeeping - -Grandparent/global/housekeeping + +Grandparent/global/housekeeping Grandparent_global_housekeeping->Parent_global_output - - + + Grandparent_global_housekeeping->Uncle_global_output - - + + Grandparent_global_housekeeping->Grandparent_global_grafana-dashboards - - + + Grandparent_global_imagemirror->Parent_global_output - - + + Grandparent_global_imagemirror->Uncle_global_output - - + + Grandparent_global_infra - -Grandparent/global/infra + +Grandparent/global/infra Grandparent_global_infra->Grandparent_global_acrs - - + + Grandparent_global_infra->Grandparent_global_cxChildZone - - + + Grandparent_global_infra->Grandparent_global_decrypt-and-ingest-secrets - - + + Grandparent_global_infra->Grandparent_global_grafana-dashboards - - + + Grandparent_global_onecert-private-kv-issuer - -Grandparent/global/onecert-private-kv-issuer + +Grandparent/global/onecert-private-kv-issuer Grandparent_global_infra->Grandparent_global_onecert-private-kv-issuer - - + + Grandparent_global_output - -Grandparent/global/output + +Grandparent/global/output Grandparent_global_infra->Grandparent_global_output - - + + Grandparent_global_svcChildZone - -Grandparent/global/svcChildZone + +Grandparent/global/svcChildZone Grandparent_global_infra->Grandparent_global_svcChildZone - - + + Grandparent_global_onecert-private-kv-issuer->Grandparent_global_certificates - - + + Grandparent_global_output->Grandparent_global_mirror-oc-mirror-image - - + + Grandparent_global_output->Grandparent_global_decrypt-and-ingest-secrets - - + + Grandparent_global_output->Grandparent_global_grafana-dashboards - - + + Grandparent_global_output->Grandparent_global_housekeeping - - + + Grandparent_global_svcChildZone->Parent_global_output - - + + Grandparent_global_svcChildZone->Uncle_global_output - - + + Child_global_output - -Child/global/output + +Child/global/output Parent_global_add-hcp-grafana-datasource->Child_global_output - - + + Child_regional_output - -Child/regional/output + +Child/regional/output Parent_global_add-hcp-grafana-datasource->Child_regional_output - - + + Sibling_global_output - -Sibling/global/output + +Sibling/global/output Parent_global_add-hcp-grafana-datasource->Sibling_global_output - - + + Sibling_regional_output - -Sibling/regional/output + +Sibling/regional/output Parent_global_add-hcp-grafana-datasource->Sibling_regional_output - - + + Sibling_service_output - -Sibling/service/output + +Sibling/service/output Parent_global_add-hcp-grafana-datasource->Sibling_service_output - - + + Child_service_acrpull - -Child/service/acrpull + +Child/service/acrpull Child_global_output->Child_service_acrpull - - + + Child_service_arobit - -Child/service/arobit + +Child/service/arobit Child_global_output->Child_service_arobit - - + + Child_service_infra - -Child/service/infra + +Child/service/infra Child_global_output->Child_service_infra - - + + Child_service_istio-config - -Child/service/istio-config + +Child/service/istio-config Child_global_output->Child_service_istio-config - - + + Child_service_istio-upgrade - -Child/service/istio-upgrade + +Child/service/istio-upgrade Child_global_output->Child_service_istio-upgrade - - + + Child_service_prometheus - -Child/service/prometheus + +Child/service/prometheus Child_global_output->Child_service_prometheus - - + + Child_service_svc - -Child/service/svc + +Child/service/svc Child_global_output->Child_service_svc - - + + Child_regional_output->Child_service_infra - - + + Child_regional_output->Child_service_svc - - + + Sibling_management_acrpull - -Sibling/management/acrpull + +Sibling/management/acrpull Sibling_global_output->Sibling_management_acrpull - - + + Sibling_management_arobit - -Sibling/management/arobit + +Sibling/management/arobit Sibling_global_output->Sibling_management_arobit - - + + Sibling_management_mgmt-cluster - -Sibling/management/mgmt-cluster + +Sibling/management/mgmt-cluster Sibling_global_output->Sibling_management_mgmt-cluster - - + + Sibling_management_mgmt-fixes - -Sibling/management/mgmt-fixes + +Sibling/management/mgmt-fixes Sibling_global_output->Sibling_management_mgmt-fixes - - + + Sibling_management_mgmt-infra - -Sibling/management/mgmt-infra + +Sibling/management/mgmt-infra Sibling_global_output->Sibling_management_mgmt-infra - - + + Sibling_management_prometheus - -Sibling/management/prometheus + +Sibling/management/prometheus Sibling_global_output->Sibling_management_prometheus - - + + Sibling_management_rpRegistration - -Sibling/management/rpRegistration + +Sibling/management/rpRegistration Sibling_global_output->Sibling_management_rpRegistration - - + + Sibling_regional_output->Sibling_management_mgmt-cluster - - + + Sibling_regional_output->Sibling_management_mgmt-infra - - + + Sibling_service_output->Sibling_management_mgmt-infra - - + + Sibling_service_output->Sibling_management_mgmt-nsp - - + + Parent_global_add-svc-grafana-datasource->Parent_global_add-hcp-grafana-datasource - - + + Parent_global_ocp-acr-replication->Parent_regional_rpRegistration - - + + Parent_regional_rpRegistration->Parent_regional_region - - + + Parent_global_svc-acr-replication->Parent_regional_rpRegistration - - + + Parent_regional_output - -Parent/regional/output + +Parent/regional/output Parent_regional_region->Parent_regional_output - - + + Parent_regional_output->Parent_global_add-hcp-grafana-datasource - - + + Parent_regional_output->Parent_global_add-svc-grafana-datasource - - + + Child_service_svc-oncert-private-kv-issuer - -Child/service/svc-oncert-private-kv-issuer + +Child/service/svc-oncert-private-kv-issuer Child_service_infra->Child_service_svc-oncert-private-kv-issuer - - + + Child_service_svc-oncert-public-kv-issuer - -Child/service/svc-oncert-public-kv-issuer + +Child/service/svc-oncert-public-kv-issuer Child_service_infra->Child_service_svc-oncert-public-kv-issuer - - + + Child_service_istio-config->Child_service_istio-upgrade - - + + Child_service_prometheus->Child_service_acrpull - - + + Child_service_prometheus->Child_service_istio-config - - + + Child_service_svc->Child_service_arobit - - + + Child_service_svc->Child_service_prometheus - - + + Child_service_svc-oncert-private-kv-issuer->Child_service_svc - - + + Child_service_svc-oncert-public-kv-issuer->Child_service_svc - - + + Sibling_management_mgmt-cluster->Sibling_management_arobit - - + + Sibling_management_mgmt-cluster->Sibling_management_mgmt-fixes - - + + Sibling_management_mgmt-cluster->Sibling_management_prometheus - - + + Sibling_management_mgmt-cluster->Sibling_management_mgmt-nsp - - + + Sibling_management_cx-oncert-public-kv-issuer - -Sibling/management/cx-oncert-public-kv-issuer + +Sibling/management/cx-oncert-public-kv-issuer Sibling_management_mgmt-infra->Sibling_management_cx-oncert-public-kv-issuer - - + + Sibling_management_mgmt-infra->Sibling_management_mgmt-nsp - - + + Sibling_management_mgmt-oncert-private-kv-issuer - -Sibling/management/mgmt-oncert-private-kv-issuer + +Sibling/management/mgmt-oncert-private-kv-issuer Sibling_management_mgmt-infra->Sibling_management_mgmt-oncert-private-kv-issuer - - + + Sibling_management_mgmt-oncert-public-kv-issuer - -Sibling/management/mgmt-oncert-public-kv-issuer + +Sibling/management/mgmt-oncert-public-kv-issuer Sibling_management_mgmt-infra->Sibling_management_mgmt-oncert-public-kv-issuer - - + + Sibling_management_prometheus->Sibling_management_acrpull - - + + Sibling_management_rpRegistration->Sibling_management_mgmt-infra - - + + Sibling_management_cx-oncert-public-kv-issuer->Sibling_management_mgmt-cluster - - + + Sibling_management_mgmt-oncert-private-kv-issuer->Sibling_management_mgmt-cluster - - + + Sibling_management_mgmt-oncert-public-kv-issuer->Sibling_management_mgmt-cluster - - + + Uncle_global_rg-ownership->Uncle_regional_delete-region - - + + Uncle_regional_rg-ownership->Uncle_regional_delete-region - - + + serviceValidation - -serviceValidation + +serviceValidation Child_service_validation - -Child_service_validation + +Child_service_validation serviceValidation->Child_service_validation - - + + diff --git a/pipelines/graph/testdata/zz_fixture_TestForPipeline.svg b/pipelines/graph/testdata/zz_fixture_TestForPipeline.svg index 72d46cf..9da1342 100644 --- a/pipelines/graph/testdata/zz_fixture_TestForPipeline.svg +++ b/pipelines/graph/testdata/zz_fixture_TestForPipeline.svg @@ -1,181 +1,181 @@ - - + regexp - + Grandparent_global_acrs - -Grandparent/global/acrs + +Grandparent/global/acrs Grandparent_global_mirror-oc-mirror-image - -Grandparent/global/mirror-oc-mirror-image + +Grandparent/global/mirror-oc-mirror-image Grandparent_global_acrs->Grandparent_global_mirror-oc-mirror-image - - + + Grandparent_global_imagemirror - -Grandparent/global/imagemirror + +Grandparent/global/imagemirror Grandparent_global_mirror-oc-mirror-image->Grandparent_global_imagemirror - - + + Grandparent_global_certificates - -Grandparent/global/certificates + +Grandparent/global/certificates Grandparent_global_cxChildZone - -Grandparent/global/cxChildZone + +Grandparent/global/cxChildZone Grandparent_global_decrypt-and-ingest-secrets - -Grandparent/global/decrypt-and-ingest-secrets + +Grandparent/global/decrypt-and-ingest-secrets Grandparent_global_decrypt-and-ingest-secrets->Grandparent_global_mirror-oc-mirror-image - - + + Grandparent_global_grafana-dashboards - -Grandparent/global/grafana-dashboards + +Grandparent/global/grafana-dashboards Grandparent_global_housekeeping - -Grandparent/global/housekeeping + +Grandparent/global/housekeeping Grandparent_global_housekeeping->Grandparent_global_grafana-dashboards - - + + Grandparent_global_infra - -Grandparent/global/infra + +Grandparent/global/infra Grandparent_global_infra->Grandparent_global_acrs - - + + Grandparent_global_infra->Grandparent_global_cxChildZone - - + + Grandparent_global_infra->Grandparent_global_decrypt-and-ingest-secrets - - + + Grandparent_global_infra->Grandparent_global_grafana-dashboards - - + + Grandparent_global_onecert-private-kv-issuer - -Grandparent/global/onecert-private-kv-issuer + +Grandparent/global/onecert-private-kv-issuer Grandparent_global_infra->Grandparent_global_onecert-private-kv-issuer - - + + Grandparent_global_output - -Grandparent/global/output + +Grandparent/global/output Grandparent_global_infra->Grandparent_global_output - - + + Grandparent_global_svcChildZone - -Grandparent/global/svcChildZone + +Grandparent/global/svcChildZone Grandparent_global_infra->Grandparent_global_svcChildZone - - + + Grandparent_global_onecert-private-kv-issuer->Grandparent_global_certificates - - + + Grandparent_global_output->Grandparent_global_mirror-oc-mirror-image - - + + Grandparent_global_output->Grandparent_global_decrypt-and-ingest-secrets - - + + Grandparent_global_output->Grandparent_global_grafana-dashboards - - + + Grandparent_global_output->Grandparent_global_housekeeping - - + + diff --git a/pipelines/testdata/pipeline.yaml b/pipelines/testdata/pipeline.yaml index 57f7d16..d9628ae 100644 --- a/pipelines/testdata/pipeline.yaml +++ b/pipelines/testdata/pipeline.yaml @@ -496,6 +496,13 @@ resourceGroups: resourceGroup: regional step: deploy name: whatever + - name: datasources + action: GrafanaDatasources + grafanaName: "{{ .global.keyVault.name }}" + identityFrom: + resourceGroup: regional + step: deploy + name: whatever validationSteps: - name: e2e action: ProwJob diff --git a/pipelines/types/common.go b/pipelines/types/common.go index 9a42722..b7c1968 100644 --- a/pipelines/types/common.go +++ b/pipelines/types/common.go @@ -716,3 +716,33 @@ func (s *GrafanaDashboardsStep) RequiredInputs() []StepDependency { func (s *GrafanaDashboardsStep) IsWellFormedOverInputs() bool { return true } + +const StepActionGrafanaDatasources = "GrafanaDatasources" + +type GrafanaDatasourcesStep struct { + StepMeta `json:",inline"` + + GrafanaName string `json:"grafanaName"` + + // IdentityFrom specifies the managed identity with which this deployment will run in Ev2. + IdentityFrom Input `json:"identityFrom,omitempty"` +} + +func (s *GrafanaDatasourcesStep) Description() string { + return fmt.Sprintf("Step %s\n Kind: %s\n", s.Name, s.Action) +} + +func (s *GrafanaDatasourcesStep) RequiredInputs() []StepDependency { + var deps []StepDependency + for _, val := range []Input{s.IdentityFrom} { + deps = append(deps, val.StepDependency) + } + + slices.SortFunc(deps, SortDependencies) + deps = slices.Compact(deps) + return deps +} + +func (s *GrafanaDatasourcesStep) IsWellFormedOverInputs() bool { + return true +} diff --git a/pipelines/types/pipeline.schema.v1.json b/pipelines/types/pipeline.schema.v1.json index 010440e..c774b8b 100644 --- a/pipelines/types/pipeline.schema.v1.json +++ b/pipelines/types/pipeline.schema.v1.json @@ -673,6 +673,31 @@ } ] }, + "grafanaDatasourcesStep": { + "unevaluatedProperties": false, + "allOf": [ + { + "$ref": "#/definitions/stepMeta" + }, + { + "properties": { + "action": { + "const": "GrafanaDatasources" + }, + "grafanaName": { + "type": "string" + }, + "identityFrom": { + "$ref": "#/definitions/input" + } + }, + "required": [ + "grafanaName", + "identityFrom" + ] + } + ] + }, "helmStep": { "unevaluatedProperties": false, "allOf": [ @@ -1854,6 +1879,22 @@ "then": { "$ref": "#/definitions/grafanaDashboardsStep" } + }, + { + "if": { + "type": "object", + "properties": { + "action": { + "const": "GrafanaDatasources" + } + }, + "required": [ + "action" + ] + }, + "then": { + "$ref": "#/definitions/grafanaDatasourcesStep" + } } ], "required": [ diff --git a/pipelines/types/resourcegroup.go b/pipelines/types/resourcegroup.go index 919b4dd..0aee978 100644 --- a/pipelines/types/resourcegroup.go +++ b/pipelines/types/resourcegroup.go @@ -139,6 +139,8 @@ func (s *Steps) UnmarshalJSON(data []byte) error { step = &ProwJobStep{} case StepActionGrafanaDashboards: step = &GrafanaDashboardsStep{} + case StepActionGrafanaDatasources: + step = &GrafanaDatasourcesStep{} default: step = &GenericStep{} } diff --git a/pipelines/types/testdata/zz_fixture_TestNewPipelineFromFile.yaml b/pipelines/types/testdata/zz_fixture_TestNewPipelineFromFile.yaml index 58e35a7..6fe1cd8 100644 --- a/pipelines/types/testdata/zz_fixture_TestNewPipelineFromFile.yaml +++ b/pipelines/types/testdata/zz_fixture_TestNewPipelineFromFile.yaml @@ -510,6 +510,13 @@ resourceGroups: step: deploy name: dashboards observabilityConfig: ./observability.yaml + - action: GrafanaDatasources + grafanaName: arohcpint-global + identityFrom: + name: whatever + resourceGroup: regional + step: deploy + name: datasources subscription: hcp-uksouth subscriptionProvisioning: displayName: diff --git a/tools/grafanactl/cmd/base/options.go b/tools/grafanactl/cmd/base/options.go index 95ea360..0983d02 100644 --- a/tools/grafanactl/cmd/base/options.go +++ b/tools/grafanactl/cmd/base/options.go @@ -16,16 +16,21 @@ package base import ( "fmt" + "strings" "github.com/spf13/cobra" + + azcorearm "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" ) // BaseOptions represents common options used across multiple commands. type BaseOptions struct { - SubscriptionID string - ResourceGroup string - GrafanaName string - OutputFormat string + SubscriptionID string + ResourceGroup string + GrafanaName string + GrafanaResourceID string + OutputFormat string + DryRun bool } // DefaultBaseOptions returns a new BaseOptions with default values @@ -38,14 +43,12 @@ func DefaultBaseOptions() *BaseOptions { // BindBaseOptions binds common command-line flags to the base options func BindBaseOptions(opts *BaseOptions, cmd *cobra.Command) error { flags := cmd.Flags() - flags.StringVar(&opts.SubscriptionID, "subscription", "", "Azure subscription ID (required)") - flags.StringVar(&opts.ResourceGroup, "resource-group", "", "Azure resource group name (required)") - flags.StringVar(&opts.GrafanaName, "grafana-name", "", "Azure Managed Grafana instance name (required)") + flags.StringVar(&opts.SubscriptionID, "subscription", opts.SubscriptionID, "Azure subscription ID ") + flags.StringVar(&opts.ResourceGroup, "resource-group", opts.ResourceGroup, "Azure resource group name ") + flags.StringVar(&opts.GrafanaName, "grafana-name", opts.GrafanaName, "Azure Managed Grafana instance name ") + flags.StringVar(&opts.GrafanaResourceID, "grafana-resource-id", opts.GrafanaResourceID, "Azure Managed Grafana instance resource ID") flags.StringVar(&opts.OutputFormat, "output", opts.OutputFormat, "Output format: table or json") - - _ = cmd.MarkFlagRequired("subscription") - _ = cmd.MarkFlagRequired("resource-group") - _ = cmd.MarkFlagRequired("grafana-name") + flags.BoolVar(&opts.DryRun, "dry-run", opts.DryRun, "Print actions without executing them") return nil } @@ -53,14 +56,19 @@ func BindBaseOptions(opts *BaseOptions, cmd *cobra.Command) error { // ValidateBaseOptions performs validation on the base options func ValidateBaseOptions(opts *BaseOptions) error { // Validate required fields - if opts.SubscriptionID == "" { - return fmt.Errorf("subscription ID is required") - } - if opts.ResourceGroup == "" { - return fmt.Errorf("resource group is required") - } - if opts.GrafanaName == "" { - return fmt.Errorf("grafana name is required") + + if opts.GrafanaResourceID == "" { + if opts.SubscriptionID == "" || opts.ResourceGroup == "" || opts.GrafanaName == "" { + return fmt.Errorf("subscription ID, resource group, and grafana name are required if grafana resource ID is not provided") + } + } else { + resourceID, err := ValidateAzureResourceID(opts.GrafanaResourceID, "Microsoft.Dashboard/grafana") + if err != nil { + return fmt.Errorf("failed to validate grafana resource ID: %w", err) + } + opts.SubscriptionID = resourceID.SubscriptionID + opts.ResourceGroup = resourceID.ResourceGroupName + opts.GrafanaName = resourceID.Name } // Validate output format @@ -70,3 +78,25 @@ func ValidateBaseOptions(opts *BaseOptions) error { return nil } + +// ValidateAzureResourceID validates an Azure resource ID and ensures it's an Azure Managed Grafana resource +func ValidateAzureResourceID(resourceID string, expectedFullType string) (*azcorearm.ResourceID, error) { + if resourceID == "" { + return nil, fmt.Errorf("resourceID cannot be empty") + } + + parsedID, err := azcorearm.ParseResourceID(resourceID) + if err != nil { + return nil, fmt.Errorf("invalid Azure resource ID format: %w", err) + } + + if !strings.EqualFold(parsedID.ResourceType.String(), expectedFullType) { + return nil, fmt.Errorf("invalid Azure resource type: expected '%s', got '%s'", expectedFullType, parsedID.ResourceType.String()) + } + + if parsedID.Name == "" { + return nil, fmt.Errorf("resource name cannot be empty in resource ID") + } + + return parsedID, nil +} diff --git a/tools/grafanactl/cmd/clean/options.go b/tools/grafanactl/cmd/clean/options.go index 109f4bb..e14c7f7 100644 --- a/tools/grafanactl/cmd/clean/options.go +++ b/tools/grafanactl/cmd/clean/options.go @@ -29,7 +29,6 @@ import ( // RawCleanOptions represents the initial, unvalidated configuration for clean operations. type RawCleanDatasourcesOptions struct { *base.BaseOptions - DryRun bool } // validatedCleanOptions is a private struct that enforces the options validation pattern. @@ -56,7 +55,6 @@ type CompletedCleanDatasourcesOptions struct { func DefaultCleanDatasourcesOptions() *RawCleanDatasourcesOptions { return &RawCleanDatasourcesOptions{ BaseOptions: base.DefaultBaseOptions(), - DryRun: false, } } @@ -66,8 +64,6 @@ func BindCleanDatasourcesOptions(opts *RawCleanDatasourcesOptions, cmd *cobra.Co return err } - flags := cmd.Flags() - flags.BoolVar(&opts.DryRun, "dry-run", false, "Perform a dry run without making changes") return nil } diff --git a/tools/grafanactl/cmd/modify/cmd.go b/tools/grafanactl/cmd/modify/cmd.go new file mode 100644 index 0000000..3583bd3 --- /dev/null +++ b/tools/grafanactl/cmd/modify/cmd.go @@ -0,0 +1,156 @@ +// Copyright 2025 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modify + +import ( + "context" + "fmt" + "strings" + + "github.com/go-logr/logr" + "github.com/spf13/cobra" + + "k8s.io/utils/set" + + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/monitor/armmonitor" +) + +const datasourceGroupID = "datasource" + +func NewModifyCommand(group string) (*cobra.Command, error) { + opts := DefaultAddDatasourceOptions() + + modifyCmd := &cobra.Command{ + Use: "modify", + Short: "Modify Grafana resources", + Long: "Modify Grafana dashboards, data sources, or other resources.", + GroupID: group, + } + + modifyCmd.AddGroup(&cobra.Group{ + ID: datasourceGroupID, + Title: "Datasource Commands:", + }) + + datasourceCmd := &cobra.Command{ + Use: "datasource", + Short: "Manage Grafana datasources", + Long: "Add, update, or manage Grafana datasources.", + GroupID: datasourceGroupID, + } + + addDatasourceCmd := &cobra.Command{ + Use: "reconcile", + Short: "Reconcile Azure Monitor Workspace datasources in Grafana", + Long: "Reconcile Azure Monitor Workspace datasources in the Azure Managed Grafana instance. This integrates the workspaces with Grafana and creates the necessary datasource configuration.", + RunE: func(cmd *cobra.Command, args []string) error { + return opts.Run(cmd.Context()) + }, + } + + if err := BindAddDatasourceOptions(opts, addDatasourceCmd); err != nil { + return nil, err + } + + datasourceCmd.AddCommand(addDatasourceCmd) + modifyCmd.AddCommand(datasourceCmd) + + return modifyCmd, nil +} + +func (opts *RawAddDatasourceOptions) Run(ctx context.Context) error { + validated, err := opts.Validate(ctx) + if err != nil { + return fmt.Errorf("validation failed: %w", err) + } + + completed, err := validated.Complete(ctx) + if err != nil { + return fmt.Errorf("completion failed: %w", err) + } + + return completed.Run(ctx) +} + +func (o *CompletedAddDatasourceOptions) getMatchingWorkspaceIDs(ctx context.Context, logger logr.Logger) (set.Set[string], error) { + validWorkspaceIDs := set.New[string]() + + monitorWorkspaces, err := o.MonitorWorkspaceClient.GetAllMonitorWorkspaces(ctx) + if err != nil { + return nil, fmt.Errorf("failed to list Azure Monitor Workspaces: %w", err) + } + + for _, workspace := range monitorWorkspaces { + if workspace.Properties == nil || workspace.Properties.ProvisioningState == nil || workspace.ID == nil { + continue + } + if *workspace.Properties.ProvisioningState == armmonitor.ProvisioningStateSucceeded { + logger.Info("Found", "workspace-id", *workspace.ID, "provisioning-state", *workspace.Properties.ProvisioningState) + validWorkspaceIDs.Insert(strings.ToLower(*workspace.ID)) + } + } + + return validWorkspaceIDs, nil +} + +func (o *CompletedAddDatasourceOptions) Run(ctx context.Context) error { + logger := logr.FromContextOrDiscard(ctx).WithValues("resource-group", o.ResourceGroup, "grafana-name", o.GrafanaName) + + logger.Info("add datasource command executed") + + grafana, err := o.ManagedGrafanaClient.GetGrafanaInstance(ctx, o.ResourceGroup, o.GrafanaName) + if err != nil { + return fmt.Errorf("failed to get Grafana instance: %w", err) + } + + validWorkspaceIDs, err := o.getMatchingWorkspaceIDs(ctx, logger) + if err != nil { + return fmt.Errorf("failed to get valid workspace IDs: %w", err) + } + + integrationList := set.New[string]() + for _, integration := range grafana.Properties.GrafanaIntegrations.AzureMonitorWorkspaceIntegrations { + if integration.AzureMonitorWorkspaceResourceID == nil { + return fmt.Errorf("got nil resource ID for integration, this looks like a bug") + } + integrationID := strings.ToLower(*integration.AzureMonitorWorkspaceResourceID) + if validWorkspaceIDs.Has(integrationID) { + integrationList.Insert(integrationID) + } else { + logger.Info("Removing", "workspace-id", integrationID) + } + } + + for _, workspaceID := range validWorkspaceIDs.UnsortedList() { + if !integrationList.Has(workspaceID) { + logger.Info("Adding", "workspace-id", workspaceID) + integrationList.Insert(workspaceID) + } + } + + if o.DryRun { + logger.Info("Dry run - would reconcile Azure Monitor Workspace integrations", "total-integrations", integrationList.Len()) + return nil + } + + logger.Info("Reconciling Azure Monitor Workspace integrations", "total-integrations", integrationList.Len()) + + err = o.ManagedGrafanaClient.UpdateGrafanaIntegrations(ctx, o.ResourceGroup, o.GrafanaName, integrationList.UnsortedList()) + if err != nil { + return fmt.Errorf("failed to update Grafana integrations: %w", err) + } + + return nil +} diff --git a/tools/grafanactl/cmd/modify/options.go b/tools/grafanactl/cmd/modify/options.go new file mode 100644 index 0000000..79a987b --- /dev/null +++ b/tools/grafanactl/cmd/modify/options.go @@ -0,0 +1,115 @@ +// Copyright 2025 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modify + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "github.com/Azure/ARO-Tools/tools/cmdutils" + "github.com/Azure/ARO-Tools/tools/grafanactl/cmd/base" + "github.com/Azure/ARO-Tools/tools/grafanactl/internal/azure" +) + +// RawAddDatasourceOptions represents the initial, unvalidated configuration for add datasource operations. +type RawAddDatasourceOptions struct { + *base.BaseOptions + TagKey string + TagValue string +} + +// validatedAddDatasourceOptions is a private struct that enforces the options validation pattern. +type validatedAddDatasourceOptions struct { + *RawAddDatasourceOptions +} + +// ValidatedAddDatasourceOptions represents add datasource configuration that has passed validation. +type ValidatedAddDatasourceOptions struct { + // Embed a private pointer that cannot be instantiated outside of this package + *validatedAddDatasourceOptions +} + +// CompletedAddDatasourceOptions represents the final, fully validated and initialized configuration +// for add datasource operations. +type CompletedAddDatasourceOptions struct { + *validatedAddDatasourceOptions + MonitorWorkspaceClient *azure.MonitorWorkspaceClient + ManagedGrafanaClient *azure.ManagedGrafanaClient +} + +// DefaultAddDatasourceOptions returns a new RawAddDatasourceOptions with default values +func DefaultAddDatasourceOptions() *RawAddDatasourceOptions { + return &RawAddDatasourceOptions{ + BaseOptions: base.DefaultBaseOptions(), + TagKey: "grafanactl-discovery", + TagValue: "true", + } +} + +// BindAddDatasourceOptions binds command-line flags to the options +func BindAddDatasourceOptions(opts *RawAddDatasourceOptions, cmd *cobra.Command) error { + if err := base.BindBaseOptions(opts.BaseOptions, cmd); err != nil { + return err + } + + flags := cmd.Flags() + flags.StringVar(&opts.TagKey, "tag-key", opts.TagKey, "Azure Monitor Workspace tag key to filter by") + flags.StringVar(&opts.TagValue, "tag-value", opts.TagValue, "Azure Monitor Workspace tag value to filter by") + + return nil +} + +// Validate performs validation on the raw options +func (o *RawAddDatasourceOptions) Validate(ctx context.Context) (*ValidatedAddDatasourceOptions, error) { + if err := base.ValidateBaseOptions(o.BaseOptions); err != nil { + return nil, err + } + + return &ValidatedAddDatasourceOptions{ + validatedAddDatasourceOptions: &validatedAddDatasourceOptions{ + RawAddDatasourceOptions: &RawAddDatasourceOptions{ + BaseOptions: o.BaseOptions, + TagKey: o.TagKey, + TagValue: o.TagValue, + }, + }, + }, nil +} + +// Complete performs final initialization to create fully usable add datasource options. +func (o *ValidatedAddDatasourceOptions) Complete(ctx context.Context) (*CompletedAddDatasourceOptions, error) { + cred, err := cmdutils.GetAzureTokenCredentials() + if err != nil { + return nil, fmt.Errorf("failed to obtain Azure credentials: %w", err) + } + + managedGrafanaClient, err := azure.NewManagedGrafanaClient(o.SubscriptionID, cred) + if err != nil { + return nil, fmt.Errorf("failed to create managed Grafana client: %w", err) + } + + monitorWorkspaceClient, err := azure.NewMonitorWorkspaceClient(o.SubscriptionID, cred) + if err != nil { + return nil, fmt.Errorf("failed to create monitor workspace client: %w", err) + } + + return &CompletedAddDatasourceOptions{ + validatedAddDatasourceOptions: o.validatedAddDatasourceOptions, + MonitorWorkspaceClient: monitorWorkspaceClient, + ManagedGrafanaClient: managedGrafanaClient, + }, nil +} diff --git a/tools/grafanactl/cmd/sync/options.go b/tools/grafanactl/cmd/sync/options.go index a5b0d3c..2570d85 100644 --- a/tools/grafanactl/cmd/sync/options.go +++ b/tools/grafanactl/cmd/sync/options.go @@ -32,7 +32,6 @@ import ( // RawSyncDashboardsOptions represents the initial, unvalidated configuration for sync operations. type RawSyncDashboardsOptions struct { *base.BaseOptions - DryRun bool ConfigFilePath string } @@ -59,7 +58,6 @@ type CompletedSyncDashboardsOptions struct { func DefaultSyncDashboardsOptions() *RawSyncDashboardsOptions { return &RawSyncDashboardsOptions{ BaseOptions: base.DefaultBaseOptions(), - DryRun: false, } } @@ -70,7 +68,6 @@ func BindSyncDashboardsOptions(opts *RawSyncDashboardsOptions, cmd *cobra.Comman } flags := cmd.Flags() - flags.BoolVar(&opts.DryRun, "dry-run", false, "Perform a dry run without making changes") flags.StringVar(&opts.ConfigFilePath, "config-file", "", "Path to config file with Grafana dashboard references (absolute or relative path, required)") _ = cmd.MarkFlagRequired("config-file") diff --git a/tools/grafanactl/go.mod b/tools/grafanactl/go.mod index f85a2f6..c4ae8a4 100644 --- a/tools/grafanactl/go.mod +++ b/tools/grafanactl/go.mod @@ -5,12 +5,13 @@ go 1.25.0 require ( github.com/Azure/ARO-Tools/tools/cmdutils v0.0.0-20260227032723-11f678744bf9 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dashboard/armdashboard v1.2.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dashboard/armdashboard/v2 v2.0.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/monitor/armmonitor v0.11.0 github.com/go-logr/logr v1.4.3 github.com/grafana-tools/sdk v0.0.0-20220919052116-6562121319fc github.com/hashicorp/go-retryablehttp v0.7.8 github.com/spf13/cobra v1.10.2 + k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 sigs.k8s.io/yaml v1.6.0 ) diff --git a/tools/grafanactl/go.sum b/tools/grafanactl/go.sum index 229f5de..fc49a11 100644 --- a/tools/grafanactl/go.sum +++ b/tools/grafanactl/go.sum @@ -8,8 +8,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+ github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dashboard/armdashboard v1.2.0 h1:MRPU8Bge2f9tkfG3PCr4vEnqXl8XOSjlhuK3l+8Hvkc= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dashboard/armdashboard v1.2.0/go.mod h1:xYrOYxajQvXMlp6M1E3amlaqPDXspyJxmjqTsGo6Jmw= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dashboard/armdashboard/v2 v2.0.0 h1:+bTCDDKBNS8wkViYYns4zWXFpBH5FImwnGlKUnwBhMQ= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dashboard/armdashboard/v2 v2.0.0/go.mod h1:KBhCuB5dXyYVRkEqmTqO3l5IcEHPjKcnV2dU7+hzumk= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0/go.mod h1:LRr2FzBTQlONPPa5HREE5+RjSCTXl7BwOvYOaWTqCaI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/monitor/armmonitor v0.11.0 h1:Ds0KRF8ggpEGg4Vo42oX1cIt/IfOhHWJBikksZbVxeg= @@ -124,5 +124,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/tools/grafanactl/internal/azure/grafana.go b/tools/grafanactl/internal/azure/grafana.go index e52b5e2..e4af349 100644 --- a/tools/grafanactl/internal/azure/grafana.go +++ b/tools/grafanactl/internal/azure/grafana.go @@ -18,9 +18,12 @@ import ( "context" "fmt" "strings" + "time" + + "github.com/go-logr/logr" "github.com/Azure/azure-sdk-for-go/sdk/azcore" - "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dashboard/armdashboard" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dashboard/armdashboard/v2" ) // ManagedGrafanaClient provides operations for Managed Grafana Resources @@ -31,13 +34,11 @@ type ManagedGrafanaClient struct { // NewManagedGrafanaClient creates a new ManagedGrafanaClient with the provided credentials func NewManagedGrafanaClient(subscriptionID string, cred azcore.TokenCredential) (*ManagedGrafanaClient, error) { - client, err := armdashboard.NewClientFactory(subscriptionID, cred, nil) + grafanaClient, err := armdashboard.NewGrafanaClient(subscriptionID, cred, nil) if err != nil { return nil, fmt.Errorf("failed to create Azure Monitor Workspaces client: %w", err) } - grafanaClient := client.NewGrafanaClient() - return &ManagedGrafanaClient{ client: grafanaClient, }, nil @@ -53,8 +54,13 @@ func (p *ManagedGrafanaClient) GetGrafanaInstance(ctx context.Context, resourceG } -// ListPrometheusInstances returns all managed Prometheus instances in the subscription +// UpdateGrafanaIntegrations updates the Azure Monitor Workspace integrations for the Grafana instance func (p *ManagedGrafanaClient) UpdateGrafanaIntegrations(ctx context.Context, resourceGroup, grafanaName string, integrations []string) error { + err := p.waitForReadyGrafana(ctx, resourceGroup, grafanaName) + if err != nil { + return fmt.Errorf("failed to wait for Grafana to be ready: %w", err) + } + azureMonitorWorkspaceIntegrations := make([]*armdashboard.AzureMonitorWorkspaceIntegration, 0) for _, integration := range integrations { azureMonitorWorkspaceIntegrations = append(azureMonitorWorkspaceIntegrations, &armdashboard.AzureMonitorWorkspaceIntegration{ @@ -62,7 +68,7 @@ func (p *ManagedGrafanaClient) UpdateGrafanaIntegrations(ctx context.Context, re }) } - _, err := p.client.Update(ctx, resourceGroup, grafanaName, armdashboard.ManagedGrafanaUpdateParameters{ + poller, err := p.client.BeginUpdate(ctx, resourceGroup, grafanaName, armdashboard.ManagedGrafanaUpdateParameters{ Properties: &armdashboard.ManagedGrafanaPropertiesUpdateParameters{ GrafanaIntegrations: &armdashboard.GrafanaIntegrations{ AzureMonitorWorkspaceIntegrations: azureMonitorWorkspaceIntegrations, @@ -73,6 +79,11 @@ func (p *ManagedGrafanaClient) UpdateGrafanaIntegrations(ctx context.Context, re return fmt.Errorf("failed to update Grafana instance: %w", err) } + _, err = poller.PollUntilDone(ctx, nil) + if err != nil { + return fmt.Errorf("failed to update Grafana instance: %w", err) + } + return nil } @@ -95,3 +106,31 @@ func (c *ManagedGrafanaClient) GetGrafanaEndpoint(ctx context.Context, subscript return endpoint, nil } + +func (c *ManagedGrafanaClient) waitForReadyGrafana(ctx context.Context, resourceGroup, grafanaName string) error { + logger := logr.FromContextOrDiscard(ctx) + for { + grafana, err := c.GetGrafanaInstance(ctx, resourceGroup, grafanaName) + if err != nil { + return fmt.Errorf("failed to get Grafana instance: %w", err) + } + if grafana.Properties == nil || grafana.Properties.ProvisioningState == nil { + logger.Info("Grafana properties or provisioning state not available yet, retrying...") + } else { + state := *grafana.Properties.ProvisioningState + switch state { + case armdashboard.ProvisioningStateSucceeded: + return nil + case armdashboard.ProvisioningStateFailed, armdashboard.ProvisioningStateCanceled, armdashboard.ProvisioningStateDeleted: + return fmt.Errorf("grafana instance provisioning ended in non-ready state: %s", state) + default: + logger.Info("Waiting for Grafana to be ready", "provisioningState", string(state)) + } + } + select { + case <-ctx.Done(): + return fmt.Errorf("context cancelled while waiting for Grafana to be ready: %w", ctx.Err()) + case <-time.After(10 * time.Second): + } + } +} diff --git a/tools/helm/go.mod b/tools/helm/go.mod index fe987ff..549150b 100644 --- a/tools/helm/go.mod +++ b/tools/helm/go.mod @@ -13,7 +13,7 @@ require ( k8s.io/apimachinery v0.34.3 k8s.io/cli-runtime v0.34.3 k8s.io/client-go v0.34.3 - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 + k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 sigs.k8s.io/yaml v1.6.0 ) diff --git a/tools/helm/go.sum b/tools/helm/go.sum index f8b1bb2..f7cd13f 100644 --- a/tools/helm/go.sum +++ b/tools/helm/go.sum @@ -454,8 +454,8 @@ k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOP k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= k8s.io/kubectl v0.34.1 h1:1qP1oqT5Xc93K+H8J7ecpBjaz511gan89KO9Vbsh/OI= k8s.io/kubectl v0.34.1/go.mod h1:JRYlhJpGPyk3dEmJ+BuBiOB9/dAvnrALJEiY/C5qa6A= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= diff --git a/tools/prow-job-executor/go.mod b/tools/prow-job-executor/go.mod index 6c8424a..8720087 100644 --- a/tools/prow-job-executor/go.mod +++ b/tools/prow-job-executor/go.mod @@ -184,7 +184,7 @@ require ( k8s.io/client-go v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect - k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 // indirect knative.dev/pkg v0.0.0-20250415155312-ed3e2158b883 // indirect sigs.k8s.io/controller-runtime v0.22.3 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect diff --git a/tools/prow-job-executor/go.sum b/tools/prow-job-executor/go.sum index 47be45e..9188d56 100644 --- a/tools/prow-job-executor/go.sum +++ b/tools/prow-job-executor/go.sum @@ -1073,8 +1073,8 @@ k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= -k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= knative.dev/pkg v0.0.0-20250415155312-ed3e2158b883 h1:UeOY7009M0EHwdyW3P35Fc1U6FJHzBrj6Gf370do8zY= knative.dev/pkg v0.0.0-20250415155312-ed3e2158b883/go.mod h1:ptwLYr04MAyeoRvhnhhz0FFkVZTdYJV2QWnw9sZyFSM= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=