diff --git a/pkg/provider/apis/validation/validation.go b/pkg/provider/apis/validation/validation.go index a9ecbf2d..8106845f 100644 --- a/pkg/provider/apis/validation/validation.go +++ b/pkg/provider/apis/validation/validation.go @@ -10,6 +10,11 @@ import ( corev1 "k8s.io/api/core/v1" ) +const ( + StackitProjectIDSecretKey = "project-id" + StackitServiceAccountKey = "serviceaccount.json" +) + // uuidRegex is a regex pattern for validating UUID format var uuidRegex = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`) @@ -53,22 +58,22 @@ func ValidateProviderSpecNSecret(spec *api.ProviderSpec, secrets *corev1.Secret) return errors // Return early if secret is nil } - projectID, ok := secrets.Data["project-id"] + projectID, ok := secrets.Data[StackitProjectIDSecretKey] if !ok { - errors = append(errors, fmt.Errorf("secret field 'project-id' is required")) + errors = append(errors, fmt.Errorf("secret field '%s' is required", StackitProjectIDSecretKey)) } else if len(projectID) == 0 { - errors = append(errors, fmt.Errorf("secret field 'project-id' cannot be empty")) + errors = append(errors, fmt.Errorf("secret field '%s' cannot be empty", StackitProjectIDSecretKey)) } else if !isValidUUID(string(projectID)) { - errors = append(errors, fmt.Errorf("secret field 'project-id' must be a valid UUID")) + errors = append(errors, fmt.Errorf("secret field '%s' must be a valid UUID", StackitProjectIDSecretKey)) } // Validate serviceAccountKey (required for authentication) // ServiceAccount Key Flow: JSON string containing service account credentials and private key - serviceAccountKey, ok := secrets.Data["serviceaccount.json"] + serviceAccountKey, ok := secrets.Data[StackitServiceAccountKey] if !ok { - errors = append(errors, fmt.Errorf("secret field 'serviceaccount.json' is required")) + errors = append(errors, fmt.Errorf("secret field '%s' is required", StackitServiceAccountKey)) } else if len(serviceAccountKey) == 0 { - errors = append(errors, fmt.Errorf("secret field 'serviceaccount.json' cannot be empty")) + errors = append(errors, fmt.Errorf("secret field '%s' cannot be empty", StackitServiceAccountKey)) } else if !isValidJSON(string(serviceAccountKey)) { errors = append(errors, fmt.Errorf("secret field 'serviceAccountKey' must be valid JSON (service account credentials)")) } diff --git a/pkg/provider/create.go b/pkg/provider/create.go index ad6fdc01..da1b792f 100644 --- a/pkg/provider/create.go +++ b/pkg/provider/create.go @@ -54,8 +54,7 @@ func (p *Provider) CreateMachine(ctx context.Context, req *driver.CreateMachineR } // Extract credentials from Secret - projectID := string(req.Secret.Data["project-id"]) - serviceAccountKey := string(req.Secret.Data["serviceaccount.json"]) + projectID, serviceAccountKey := extractSecretCredentials(req.Secret.Data) // Initialize client on first use (lazy initialization) if err := p.ensureClient(serviceAccountKey); err != nil { diff --git a/pkg/provider/delete.go b/pkg/provider/delete.go index 1379fbdc..90f78cc5 100644 --- a/pkg/provider/delete.go +++ b/pkg/provider/delete.go @@ -27,7 +27,8 @@ func (p *Provider) DeleteMachine(ctx context.Context, req *driver.DeleteMachineR defer klog.V(2).Infof("Machine deletion request has been processed for %q", req.Machine.Name) // Extract credentials from Secret - serviceAccountKey := string(req.Secret.Data["serviceaccount.json"]) + projectIDFromSecret, serviceAccountKey := extractSecretCredentials(req.Secret.Data) + // Initialize client on first use (lazy initialization) if err := p.ensureClient(serviceAccountKey); err != nil { return nil, status.Error(codes.Internal, fmt.Sprintf("failed to initialize STACKIT client: %v", err)) @@ -48,7 +49,8 @@ func (p *Provider) DeleteMachine(ctx context.Context, req *driver.DeleteMachineR } if projectID == "" { - projectID = string(req.Secret.Data["project-id"]) + // use the secret as a fallback + projectID = projectIDFromSecret } providerSpec, err := decodeProviderSpec(req.MachineClass) diff --git a/pkg/provider/helpers.go b/pkg/provider/helpers.go index 14127ff4..37c31e4f 100644 --- a/pkg/provider/helpers.go +++ b/pkg/provider/helpers.go @@ -7,6 +7,7 @@ import ( "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1" api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis" + "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis/validation" ) // decodeProviderSpec decodes the ProviderSpec from a MachineClass @@ -31,10 +32,10 @@ func encodeProviderSpecForResponse(spec *api.ProviderSpec) ([]byte, error) { // parseProviderID parses a STACKIT ProviderID and extracts the projectID and serverID // Expected format: stackit:/// func parseProviderID(providerID string) (projectID, serverID string, err error) { - const prefix = "stackit://" + prefix := fmt.Sprintf("%s://", StackitProviderName) if !strings.HasPrefix(providerID, prefix) { - return "", "", fmt.Errorf("ProviderID must start with 'stackit://'") + return "", "", fmt.Errorf("ProviderID must start with '%s://'", StackitProviderName) } // Remove prefix and split by '/' @@ -42,7 +43,7 @@ func parseProviderID(providerID string) (projectID, serverID string, err error) parts := strings.Split(remainder, "/") if len(parts) != 2 { - return "", "", fmt.Errorf("ProviderID must have format 'stackit:///'") + return "", "", fmt.Errorf("ProviderID must have format '%s:///'", StackitProviderName) } if parts[0] == "" || parts[1] == "" { @@ -51,3 +52,9 @@ func parseProviderID(providerID string) (projectID, serverID string, err error) return parts[0], parts[1], nil } + +func extractSecretCredentials(secretData map[string][]byte) (projectID, serviceAccountKey string) { + projectID = string(secretData[validation.StackitProjectIDSecretKey]) + serviceAccountKey = string(secretData[validation.StackitServiceAccountKey]) + return projectID, serviceAccountKey +} diff --git a/pkg/provider/list.go b/pkg/provider/list.go index 6367b8b2..1d421ba0 100644 --- a/pkg/provider/list.go +++ b/pkg/provider/list.go @@ -27,8 +27,7 @@ func (p *Provider) ListMachines(ctx context.Context, req *driver.ListMachinesReq defer klog.V(2).Infof("List machines request has been processed for %q", req.MachineClass.Name) // Extract credentials from Secret - projectID := string(req.Secret.Data["project-id"]) - serviceAccountKey := string(req.Secret.Data["serviceaccount.json"]) + projectID, serviceAccountKey := extractSecretCredentials(req.Secret.Data) // Initialize client on first use (lazy initialization) if err := p.ensureClient(serviceAccountKey); err != nil { diff --git a/pkg/provider/status.go b/pkg/provider/status.go index 885ffe15..dce58a9b 100644 --- a/pkg/provider/status.go +++ b/pkg/provider/status.go @@ -39,7 +39,7 @@ func (p *Provider) GetMachineStatus(ctx context.Context, req *driver.GetMachineS } // Extract credentials from Secret - serviceAccountKey := string(req.Secret.Data["serviceaccount.json"]) + projectIDFromSecret, serviceAccountKey := extractSecretCredentials(req.Secret.Data) // Initialize client on first use (lazy initialization) if err := p.ensureClient(serviceAccountKey); err != nil { @@ -50,7 +50,7 @@ func (p *Provider) GetMachineStatus(ctx context.Context, req *driver.GetMachineS // Expected format: stackit:/// projectID, serverID, err := parseProviderID(req.Machine.Spec.ProviderID) if projectID == "" { - projectID = string(req.Secret.Data["project-id"]) + projectID = projectIDFromSecret } if err != nil { return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid ProviderID format: %v", err))