diff --git a/cmd/ctrlc/root/apply/cmd.go b/cmd/ctrlc/root/apply/cmd.go index 4c70f84..eddc469 100644 --- a/cmd/ctrlc/root/apply/cmd.go +++ b/cmd/ctrlc/root/apply/cmd.go @@ -45,6 +45,7 @@ func runApply(filePath string) error { processAllSystems(ctx, client, workspaceID, config.Systems) processResourceProvider(ctx, client, workspaceID.String(), config.Providers) processResourceRelationships(ctx, client, workspaceID.String(), config.Relationships) + processAllPolicies(ctx, client, workspaceID.String(), config.Policies) return nil } diff --git a/cmd/ctrlc/root/apply/policy.go b/cmd/ctrlc/root/apply/policy.go new file mode 100644 index 0000000..0fb34b5 --- /dev/null +++ b/cmd/ctrlc/root/apply/policy.go @@ -0,0 +1,151 @@ +package apply + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/charmbracelet/log" + "github.com/ctrlplanedev/cli/internal/api" +) + +func processAllPolicies( + ctx context.Context, + client *api.ClientWithResponses, + workspaceID string, + policies []Policy, +) { + if len(policies) == 0 { + return + } + + var wg sync.WaitGroup + for _, policy := range policies { + wg.Add(1) + policy.WorkspaceId = workspaceID + go processPolicy(ctx, client, policy, &wg) + } + wg.Wait() +} + +func processPolicy( + ctx context.Context, + client *api.ClientWithResponses, + policy Policy, + policyWg *sync.WaitGroup, +) { + defer policyWg.Done() + + if policy.Name == "" { + log.Error("Policy name is required", "policy", policy) + return + } + + body := createPolicyRequestBody(policy) + if _, err := upsertPolicy(ctx, client, body); err != nil { + log.Error("Failed to create policy", "name", policy.Name, "error", err) + return + } +} + +func createPolicyRequestBody(policy Policy) api.UpsertPolicyJSONRequestBody { + // Convert targets + targets := make([]api.PolicyTarget, len(policy.Targets)) + for i, target := range policy.Targets { + targets[i] = api.PolicyTarget{ + DeploymentSelector: target.DeploymentSelector, + EnvironmentSelector: target.EnvironmentSelector, + ResourceSelector: target.ResourceSelector, + } + } + + // Convert deny windows + denyWindows := make([]struct { + Dtend *time.Time `json:"dtend,omitempty"` + Rrule *map[string]interface{} `json:"rrule,omitempty"` + TimeZone string `json:"timeZone"` + }, len(policy.DenyWindows)) + for i, window := range policy.DenyWindows { + rrule := window.Rrule + denyWindows[i] = struct { + Dtend *time.Time `json:"dtend,omitempty"` + Rrule *map[string]interface{} `json:"rrule,omitempty"` + TimeZone string `json:"timeZone"` + }{ + Dtend: window.Dtend, + Rrule: &rrule, + TimeZone: window.TimeZone, + } + } + + // Convert version any approval + var versionAnyApprovals *api.VersionAnyApproval + if policy.VersionAnyApprovals != nil { + versionAnyApprovals = &api.VersionAnyApproval{ + RequiredApprovalsCount: policy.VersionAnyApprovals.RequiredApprovalsCount, + } + } + + versionUserApprovals := make([]api.VersionUserApproval, len(policy.VersionUserApprovals)) + for i, approval := range policy.VersionUserApprovals { + versionUserApprovals[i] = api.VersionUserApproval{ + UserId: approval.UserId, + } + } + + versionRoleApprovals := make([]api.VersionRoleApproval, len(policy.VersionRoleApprovals)) + for i, approval := range policy.VersionRoleApprovals { + count := approval.RequiredApprovalsCount + versionRoleApprovals[i] = api.VersionRoleApproval{ + RequiredApprovalsCount: count, + RoleId: approval.RoleId, + } + } + + // Create deployment version selector if present + var deploymentVersionSelector *api.DeploymentVersionSelector + if policy.DeploymentVersionSelector != nil { + deploymentVersionSelector = &api.DeploymentVersionSelector{ + Name: policy.DeploymentVersionSelector.Name, + DeploymentVersionSelector: policy.DeploymentVersionSelector.DeploymentVersionSelector, + Description: policy.DeploymentVersionSelector.Description, + } + } + + return api.UpsertPolicyJSONRequestBody{ + Name: policy.Name, + Description: policy.Description, + Priority: policy.Priority, + Enabled: policy.Enabled, + WorkspaceId: policy.WorkspaceId, + Targets: targets, + DenyWindows: &denyWindows, + DeploymentVersionSelector: deploymentVersionSelector, + VersionAnyApprovals: versionAnyApprovals, + VersionUserApprovals: &versionUserApprovals, + VersionRoleApprovals: &versionRoleApprovals, + } +} + +func upsertPolicy( + ctx context.Context, + client *api.ClientWithResponses, + policy api.UpsertPolicyJSONRequestBody, +) (string, error) { + resp, err := client.UpsertPolicyWithResponse(ctx, policy) + + if err != nil { + return "", fmt.Errorf("API request failed: %w", err) + } + + if resp.StatusCode() >= 400 { + return "", fmt.Errorf("API returned error status: %d", resp.StatusCode()) + } + + if resp.JSON200 != nil { + return resp.JSON200.Id.String(), nil + } + + return "", fmt.Errorf("unexpected response format") +} diff --git a/cmd/ctrlc/root/apply/types.go b/cmd/ctrlc/root/apply/types.go index ec1d3ea..3608206 100644 --- a/cmd/ctrlc/root/apply/types.go +++ b/cmd/ctrlc/root/apply/types.go @@ -1,10 +1,13 @@ package apply +import "time" + // Config represents the structure of the YAML file type Config struct { Systems []System `yaml:"systems"` Providers ResourceProvider `yaml:"resourceProvider"` Relationships []ResourceRelationship `yaml:"relationshipRules"` + Policies []Policy `yaml:"policies,omitempty"` } type System struct { @@ -99,3 +102,49 @@ type ResourceRelationship struct { MetadataKeysMatch []string `yaml:"metadataKeysMatch"` DependencyType string `yaml:"dependencyType"` } + +// Policy structs +type Policy struct { + Name string `yaml:"name"` + Description *string `yaml:"description,omitempty"` + Priority *float32 `yaml:"priority,omitempty"` + Enabled *bool `yaml:"enabled,omitempty"` + WorkspaceId string `yaml:"workspaceId"` + Targets []PolicyTarget `yaml:"targets"` + DenyWindows []DenyWindow `yaml:"denyWindows,omitempty"` + DeploymentVersionSelector *DeploymentVersionSelector `yaml:"deploymentVersionSelector,omitempty"` + VersionAnyApprovals *VersionAnyApproval `yaml:"versionAnyApprovals,omitempty"` + VersionUserApprovals []VersionUserApproval `yaml:"versionUserApprovals,omitempty"` + VersionRoleApprovals []VersionRoleApproval `yaml:"versionRoleApprovals,omitempty"` +} + +type PolicyTarget struct { + DeploymentSelector *map[string]any `yaml:"deploymentSelector,omitempty"` + EnvironmentSelector *map[string]any `yaml:"environmentSelector,omitempty"` + ResourceSelector *map[string]any `yaml:"resourceSelector,omitempty"` +} + +type DenyWindow struct { + TimeZone string `yaml:"timeZone"` + Rrule map[string]any `yaml:"rrule"` + Dtend *time.Time `yaml:"dtend,omitempty"` +} + +type DeploymentVersionSelector struct { + Name string `yaml:"name"` + DeploymentVersionSelector map[string]any `yaml:"deploymentVersionSelector"` + Description *string `yaml:"description,omitempty"` +} + +type VersionAnyApproval struct { + RequiredApprovalsCount float32 `yaml:"requiredApprovalsCount"` +} + +type VersionUserApproval struct { + UserId string `yaml:"userId"` +} + +type VersionRoleApproval struct { + RoleId string `yaml:"roleId"` + RequiredApprovalsCount float32 `yaml:"requiredApprovalsCount"` +} diff --git a/internal/api/client.gen.go b/internal/api/client.gen.go index f6ace76..2988ca0 100644 --- a/internal/api/client.gen.go +++ b/internal/api/client.gen.go @@ -480,7 +480,7 @@ type Policy1 struct { Name string `json:"name"` Priority float32 `json:"priority"` Targets []PolicyTarget `json:"targets"` - VersionAnyApprovals *[]VersionAnyApproval `json:"versionAnyApprovals,omitempty"` + VersionAnyApprovals *VersionAnyApproval `json:"versionAnyApprovals,omitempty"` VersionRoleApprovals []VersionRoleApproval `json:"versionRoleApprovals"` VersionUserApprovals []VersionUserApproval `json:"versionUserApprovals"` WorkspaceId openapi_types.UUID `json:"workspaceId"` @@ -873,15 +873,10 @@ type UpsertPolicyJSONBody struct { Name string `json:"name"` Priority *float32 `json:"priority,omitempty"` Targets []PolicyTarget `json:"targets"` - VersionAnyApprovals *[]struct { - RequiredApprovalsCount *float32 `json:"requiredApprovalsCount,omitempty"` - } `json:"versionAnyApprovals,omitempty"` - VersionRoleApprovals *[]struct { - RequiredApprovalsCount *float32 `json:"requiredApprovalsCount,omitempty"` - RoleId string `json:"roleId"` - } `json:"versionRoleApprovals,omitempty"` - VersionUserApprovals *[]VersionUserApproval `json:"versionUserApprovals,omitempty"` - WorkspaceId string `json:"workspaceId"` + VersionAnyApprovals *VersionAnyApproval `json:"versionAnyApprovals,omitempty"` + VersionRoleApprovals *[]VersionRoleApproval `json:"versionRoleApprovals,omitempty"` + VersionUserApprovals *[]VersionUserApproval `json:"versionUserApprovals,omitempty"` + WorkspaceId string `json:"workspaceId"` } // UpdatePolicyJSONBody defines parameters for UpdatePolicy.