Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
584 changes: 292 additions & 292 deletions pipelines/graph/testdata/zz_fixture_TestForEntrypoint.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
120 changes: 60 additions & 60 deletions pipelines/graph/testdata/zz_fixture_TestForPipeline.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions pipelines/testdata/pipeline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions pipelines/types/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
41 changes: 41 additions & 0 deletions pipelines/types/pipeline.schema.v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down Expand Up @@ -1854,6 +1879,22 @@
"then": {
"$ref": "#/definitions/grafanaDashboardsStep"
}
},
{
"if": {
"type": "object",
"properties": {
"action": {
"const": "GrafanaDatasources"
}
},
"required": [
"action"
]
},
"then": {
"$ref": "#/definitions/grafanaDatasourcesStep"
}
}
],
"required": [
Expand Down
2 changes: 2 additions & 0 deletions pipelines/types/resourcegroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ func (s *Steps) UnmarshalJSON(data []byte) error {
step = &ProwJobStep{}
case StepActionGrafanaDashboards:
step = &GrafanaDashboardsStep{}
case StepActionGrafanaDatasources:
step = &GrafanaDatasourcesStep{}
default:
step = &GenericStep{}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
68 changes: 49 additions & 19 deletions tools/grafanactl/cmd/base/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -38,29 +43,32 @@ 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
}

// 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
Expand All @@ -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
}
4 changes: 0 additions & 4 deletions tools/grafanactl/cmd/clean/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -56,7 +55,6 @@ type CompletedCleanDatasourcesOptions struct {
func DefaultCleanDatasourcesOptions() *RawCleanDatasourcesOptions {
return &RawCleanDatasourcesOptions{
BaseOptions: base.DefaultBaseOptions(),
DryRun: false,
}
}

Expand All @@ -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
}

Expand Down
Loading
Loading