From 171c81374aa3b386d41476fbd868f27ef89d1ba3 Mon Sep 17 00:00:00 2001 From: Aditya Choudhari Date: Mon, 4 May 2026 11:56:19 -0400 Subject: [PATCH 1/2] feat: opa validations policy rule --- internal/api/client.gen.go | 495 ++++++++++++++++++++++++++- internal/provider/policy_resource.go | 117 +++++++ 2 files changed, 607 insertions(+), 5 deletions(-) diff --git a/internal/api/client.gen.go b/internal/api/client.gen.go index cdc506c..950fed8 100644 --- a/internal/api/client.gen.go +++ b/internal/api/client.gen.go @@ -248,8 +248,14 @@ type CreateDeploymentRequest struct { // CreateDeploymentVersionRequest defines model for CreateDeploymentVersionRequest. type CreateDeploymentVersionRequest struct { - Config *map[string]interface{} `json:"config,omitempty"` - CreatedAt *time.Time `json:"createdAt,omitempty"` + Config *map[string]interface{} `json:"config,omitempty"` + CreatedAt *time.Time `json:"createdAt,omitempty"` + + // Dependencies Map of dependency deployment ID to a CEL version selector evaluated against that deployment's current release on the same resource. Inserted atomically with the version so reconciliation cannot fire before edges are attached. + Dependencies *map[string]struct { + // VersionSelector CEL expression evaluated against the dependency deployment's current release version on the same resource. + VersionSelector string `json:"versionSelector"` + } `json:"dependencies,omitempty"` JobAgentConfig *map[string]interface{} `json:"jobAgentConfig,omitempty"` Metadata *map[string]string `json:"metadata,omitempty"` Name string `json:"name"` @@ -289,6 +295,7 @@ type CreatePolicyRule struct { DeploymentWindow *DeploymentWindowRule `json:"deploymentWindow,omitempty"` EnvironmentProgression *EnvironmentProgressionRule `json:"environmentProgression,omitempty"` GradualRollout *GradualRolloutRule `json:"gradualRollout,omitempty"` + PlanValidationOpa *PlanValidationOpaRule `json:"planValidationOpa,omitempty"` Retry *RetryRule `json:"retry,omitempty"` Verification *VerificationRule `json:"verification,omitempty"` VersionCooldown *VersionCooldownRule `json:"versionCooldown,omitempty"` @@ -534,6 +541,15 @@ type DeploymentVersion struct { Tag string `json:"tag"` } +// DeploymentVersionDependency defines model for DeploymentVersionDependency. +type DeploymentVersionDependency struct { + DependencyDeploymentId string `json:"dependencyDeploymentId"` + DeploymentVersionId string `json:"deploymentVersionId"` + + // VersionSelector CEL expression evaluated against the dependency deployment's current release version on the same resource. + VersionSelector string `json:"versionSelector"` +} + // DeploymentVersionStatus defines model for DeploymentVersionStatus. type DeploymentVersionStatus string @@ -778,6 +794,17 @@ type ObjectValue struct { Object map[string]interface{} `json:"object"` } +// PlanValidationOpaRule defines model for PlanValidationOpaRule. +type PlanValidationOpaRule struct { + Description *string `json:"description,omitempty"` + + // Name Human-readable rule name; used in check output to identify which rule produced a violation. + Name string `json:"name"` + + // Rego Rego v1 source code. Must define a `deny` rule set following the Conftest convention (deny contains msg if { ... }). + Rego string `json:"rego"` +} + // Policy defines model for Policy. type Policy struct { CreatedAt string `json:"createdAt"` @@ -805,6 +832,7 @@ type PolicyRule struct { EnvironmentProgression *EnvironmentProgressionRule `json:"environmentProgression,omitempty"` GradualRollout *GradualRolloutRule `json:"gradualRollout,omitempty"` Id string `json:"id"` + PlanValidationOpa *PlanValidationOpaRule `json:"planValidationOpa,omitempty"` PolicyId string `json:"policyId"` Retry *RetryRule `json:"retry,omitempty"` Verification *VerificationRule `json:"verification,omitempty"` @@ -1184,6 +1212,12 @@ type UpsertDeploymentVariableValueRequest struct { Value Value `json:"value"` } +// UpsertDeploymentVersionDependencyRequest defines model for UpsertDeploymentVersionDependencyRequest. +type UpsertDeploymentVersionDependencyRequest struct { + // VersionSelector CEL expression evaluated against the dependency deployment's current release version on the same resource. + VersionSelector string `json:"versionSelector"` +} + // UpsertEnvironmentRequest defines model for UpsertEnvironmentRequest. type UpsertEnvironmentRequest struct { Description *string `json:"description,omitempty"` @@ -1226,6 +1260,7 @@ type UpsertPolicyRule struct { EnvironmentProgression *EnvironmentProgressionRule `json:"environmentProgression,omitempty"` GradualRollout *GradualRolloutRule `json:"gradualRollout,omitempty"` Id *string `json:"id,omitempty"` + PlanValidationOpa *PlanValidationOpaRule `json:"planValidationOpa,omitempty"` PolicyId *string `json:"policyId,omitempty"` Retry *RetryRule `json:"retry,omitempty"` Verification *VerificationRule `json:"verification,omitempty"` @@ -1772,6 +1807,9 @@ type RequestDeploymentVariableValueUpsertJSONRequestBody = UpsertDeploymentVaria // RequestDeploymentVariableUpdateJSONRequestBody defines body for RequestDeploymentVariableUpdate for application/json ContentType. type RequestDeploymentVariableUpdateJSONRequestBody = UpsertDeploymentVariableRequest +// RequestDeploymentVersionDependencyUpsertJSONRequestBody defines body for RequestDeploymentVersionDependencyUpsert for application/json ContentType. +type RequestDeploymentVersionDependencyUpsertJSONRequestBody = UpsertDeploymentVersionDependencyRequest + // RequestUserApprovalRecordUpsertJSONRequestBody defines body for RequestUserApprovalRecordUpsert for application/json ContentType. type RequestUserApprovalRecordUpsertJSONRequestBody = UpsertUserApprovalRecordRequest @@ -2608,6 +2646,17 @@ type ClientInterface interface { RequestDeploymentVariableUpdate(ctx context.Context, workspaceId string, variableId string, body RequestDeploymentVariableUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListDeploymentVersionDependencies request + ListDeploymentVersionDependencies(ctx context.Context, workspaceId string, deploymentVersionId string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // RequestDeploymentVersionDependencyDeletion request + RequestDeploymentVersionDependencyDeletion(ctx context.Context, workspaceId string, deploymentVersionId string, dependencyDeploymentId string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // RequestDeploymentVersionDependencyUpsertWithBody request with any body + RequestDeploymentVersionDependencyUpsertWithBody(ctx context.Context, workspaceId string, deploymentVersionId string, dependencyDeploymentId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + RequestDeploymentVersionDependencyUpsert(ctx context.Context, workspaceId string, deploymentVersionId string, dependencyDeploymentId string, body RequestDeploymentVersionDependencyUpsertJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // RequestUserApprovalRecordUpsertWithBody request with any body RequestUserApprovalRecordUpsertWithBody(ctx context.Context, workspaceId string, deploymentVersionId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3094,6 +3143,54 @@ func (c *Client) RequestDeploymentVariableUpdate(ctx context.Context, workspaceI return c.Client.Do(req) } +func (c *Client) ListDeploymentVersionDependencies(ctx context.Context, workspaceId string, deploymentVersionId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListDeploymentVersionDependenciesRequest(c.Server, workspaceId, deploymentVersionId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) RequestDeploymentVersionDependencyDeletion(ctx context.Context, workspaceId string, deploymentVersionId string, dependencyDeploymentId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRequestDeploymentVersionDependencyDeletionRequest(c.Server, workspaceId, deploymentVersionId, dependencyDeploymentId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) RequestDeploymentVersionDependencyUpsertWithBody(ctx context.Context, workspaceId string, deploymentVersionId string, dependencyDeploymentId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRequestDeploymentVersionDependencyUpsertRequestWithBody(c.Server, workspaceId, deploymentVersionId, dependencyDeploymentId, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) RequestDeploymentVersionDependencyUpsert(ctx context.Context, workspaceId string, deploymentVersionId string, dependencyDeploymentId string, body RequestDeploymentVersionDependencyUpsertJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRequestDeploymentVersionDependencyUpsertRequest(c.Server, workspaceId, deploymentVersionId, dependencyDeploymentId, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) RequestUserApprovalRecordUpsertWithBody(ctx context.Context, workspaceId string, deploymentVersionId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewRequestUserApprovalRecordUpsertRequestWithBody(c.Server, workspaceId, deploymentVersionId, contentType, body) if err != nil { @@ -4866,6 +4963,156 @@ func NewRequestDeploymentVariableUpdateRequestWithBody(server string, workspaceI return req, nil } +// NewListDeploymentVersionDependenciesRequest generates requests for ListDeploymentVersionDependencies +func NewListDeploymentVersionDependenciesRequest(server string, workspaceId string, deploymentVersionId string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "workspaceId", runtime.ParamLocationPath, workspaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "deploymentVersionId", runtime.ParamLocationPath, deploymentVersionId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/workspaces/%s/deployment-versions/%s/dependencies", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewRequestDeploymentVersionDependencyDeletionRequest generates requests for RequestDeploymentVersionDependencyDeletion +func NewRequestDeploymentVersionDependencyDeletionRequest(server string, workspaceId string, deploymentVersionId string, dependencyDeploymentId string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "workspaceId", runtime.ParamLocationPath, workspaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "deploymentVersionId", runtime.ParamLocationPath, deploymentVersionId) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "dependencyDeploymentId", runtime.ParamLocationPath, dependencyDeploymentId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/workspaces/%s/deployment-versions/%s/dependencies/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewRequestDeploymentVersionDependencyUpsertRequest calls the generic RequestDeploymentVersionDependencyUpsert builder with application/json body +func NewRequestDeploymentVersionDependencyUpsertRequest(server string, workspaceId string, deploymentVersionId string, dependencyDeploymentId string, body RequestDeploymentVersionDependencyUpsertJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewRequestDeploymentVersionDependencyUpsertRequestWithBody(server, workspaceId, deploymentVersionId, dependencyDeploymentId, "application/json", bodyReader) +} + +// NewRequestDeploymentVersionDependencyUpsertRequestWithBody generates requests for RequestDeploymentVersionDependencyUpsert with any type of body +func NewRequestDeploymentVersionDependencyUpsertRequestWithBody(server string, workspaceId string, deploymentVersionId string, dependencyDeploymentId string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "workspaceId", runtime.ParamLocationPath, workspaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "deploymentVersionId", runtime.ParamLocationPath, deploymentVersionId) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "dependencyDeploymentId", runtime.ParamLocationPath, dependencyDeploymentId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/workspaces/%s/deployment-versions/%s/dependencies/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewRequestUserApprovalRecordUpsertRequest calls the generic RequestUserApprovalRecordUpsert builder with application/json body func NewRequestUserApprovalRecordUpsertRequest(server string, workspaceId string, deploymentVersionId string, body RequestUserApprovalRecordUpsertJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -9213,6 +9460,17 @@ type ClientWithResponsesInterface interface { RequestDeploymentVariableUpdateWithResponse(ctx context.Context, workspaceId string, variableId string, body RequestDeploymentVariableUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*RequestDeploymentVariableUpdateResponse, error) + // ListDeploymentVersionDependenciesWithResponse request + ListDeploymentVersionDependenciesWithResponse(ctx context.Context, workspaceId string, deploymentVersionId string, reqEditors ...RequestEditorFn) (*ListDeploymentVersionDependenciesResponse, error) + + // RequestDeploymentVersionDependencyDeletionWithResponse request + RequestDeploymentVersionDependencyDeletionWithResponse(ctx context.Context, workspaceId string, deploymentVersionId string, dependencyDeploymentId string, reqEditors ...RequestEditorFn) (*RequestDeploymentVersionDependencyDeletionResponse, error) + + // RequestDeploymentVersionDependencyUpsertWithBodyWithResponse request with any body + RequestDeploymentVersionDependencyUpsertWithBodyWithResponse(ctx context.Context, workspaceId string, deploymentVersionId string, dependencyDeploymentId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RequestDeploymentVersionDependencyUpsertResponse, error) + + RequestDeploymentVersionDependencyUpsertWithResponse(ctx context.Context, workspaceId string, deploymentVersionId string, dependencyDeploymentId string, body RequestDeploymentVersionDependencyUpsertJSONRequestBody, reqEditors ...RequestEditorFn) (*RequestDeploymentVersionDependencyUpsertResponse, error) + // RequestUserApprovalRecordUpsertWithBodyWithResponse request with any body RequestUserApprovalRecordUpsertWithBodyWithResponse(ctx context.Context, workspaceId string, deploymentVersionId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RequestUserApprovalRecordUpsertResponse, error) @@ -9802,6 +10060,77 @@ func (r RequestDeploymentVariableUpdateResponse) StatusCode() int { return 0 } +type ListDeploymentVersionDependenciesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]DeploymentVersionDependency + JSON404 *ErrorResponse +} + +// Status returns HTTPResponse.Status +func (r ListDeploymentVersionDependenciesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListDeploymentVersionDependenciesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type RequestDeploymentVersionDependencyDeletionResponse struct { + Body []byte + HTTPResponse *http.Response + JSON202 *DeploymentRequestAccepted + JSON400 *ErrorResponse + JSON404 *ErrorResponse +} + +// Status returns HTTPResponse.Status +func (r RequestDeploymentVersionDependencyDeletionResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r RequestDeploymentVersionDependencyDeletionResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type RequestDeploymentVersionDependencyUpsertResponse struct { + Body []byte + HTTPResponse *http.Response + JSON202 *DeploymentRequestAccepted + JSON400 *ErrorResponse + JSON404 *ErrorResponse +} + +// Status returns HTTPResponse.Status +func (r RequestDeploymentVersionDependencyUpsertResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r RequestDeploymentVersionDependencyUpsertResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type RequestUserApprovalRecordUpsertResponse struct { Body []byte HTTPResponse *http.Response @@ -10099,8 +10428,9 @@ func (r ListDeploymentVersionsResponse) StatusCode() int { type CreateDeploymentVersionResponse struct { Body []byte HTTPResponse *http.Response - JSON202 *DeploymentVersion + JSON200 *DeploymentVersion JSON400 *ErrorResponse + JSON404 *ErrorResponse } // Status returns HTTPResponse.Status @@ -12015,6 +12345,41 @@ func (c *ClientWithResponses) RequestDeploymentVariableUpdateWithResponse(ctx co return ParseRequestDeploymentVariableUpdateResponse(rsp) } +// ListDeploymentVersionDependenciesWithResponse request returning *ListDeploymentVersionDependenciesResponse +func (c *ClientWithResponses) ListDeploymentVersionDependenciesWithResponse(ctx context.Context, workspaceId string, deploymentVersionId string, reqEditors ...RequestEditorFn) (*ListDeploymentVersionDependenciesResponse, error) { + rsp, err := c.ListDeploymentVersionDependencies(ctx, workspaceId, deploymentVersionId, reqEditors...) + if err != nil { + return nil, err + } + return ParseListDeploymentVersionDependenciesResponse(rsp) +} + +// RequestDeploymentVersionDependencyDeletionWithResponse request returning *RequestDeploymentVersionDependencyDeletionResponse +func (c *ClientWithResponses) RequestDeploymentVersionDependencyDeletionWithResponse(ctx context.Context, workspaceId string, deploymentVersionId string, dependencyDeploymentId string, reqEditors ...RequestEditorFn) (*RequestDeploymentVersionDependencyDeletionResponse, error) { + rsp, err := c.RequestDeploymentVersionDependencyDeletion(ctx, workspaceId, deploymentVersionId, dependencyDeploymentId, reqEditors...) + if err != nil { + return nil, err + } + return ParseRequestDeploymentVersionDependencyDeletionResponse(rsp) +} + +// RequestDeploymentVersionDependencyUpsertWithBodyWithResponse request with arbitrary body returning *RequestDeploymentVersionDependencyUpsertResponse +func (c *ClientWithResponses) RequestDeploymentVersionDependencyUpsertWithBodyWithResponse(ctx context.Context, workspaceId string, deploymentVersionId string, dependencyDeploymentId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RequestDeploymentVersionDependencyUpsertResponse, error) { + rsp, err := c.RequestDeploymentVersionDependencyUpsertWithBody(ctx, workspaceId, deploymentVersionId, dependencyDeploymentId, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseRequestDeploymentVersionDependencyUpsertResponse(rsp) +} + +func (c *ClientWithResponses) RequestDeploymentVersionDependencyUpsertWithResponse(ctx context.Context, workspaceId string, deploymentVersionId string, dependencyDeploymentId string, body RequestDeploymentVersionDependencyUpsertJSONRequestBody, reqEditors ...RequestEditorFn) (*RequestDeploymentVersionDependencyUpsertResponse, error) { + rsp, err := c.RequestDeploymentVersionDependencyUpsert(ctx, workspaceId, deploymentVersionId, dependencyDeploymentId, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseRequestDeploymentVersionDependencyUpsertResponse(rsp) +} + // RequestUserApprovalRecordUpsertWithBodyWithResponse request with arbitrary body returning *RequestUserApprovalRecordUpsertResponse func (c *ClientWithResponses) RequestUserApprovalRecordUpsertWithBodyWithResponse(ctx context.Context, workspaceId string, deploymentVersionId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RequestUserApprovalRecordUpsertResponse, error) { rsp, err := c.RequestUserApprovalRecordUpsertWithBody(ctx, workspaceId, deploymentVersionId, contentType, body, reqEditors...) @@ -13479,6 +13844,119 @@ func ParseRequestDeploymentVariableUpdateResponse(rsp *http.Response) (*RequestD return response, nil } +// ParseListDeploymentVersionDependenciesResponse parses an HTTP response from a ListDeploymentVersionDependenciesWithResponse call +func ParseListDeploymentVersionDependenciesResponse(rsp *http.Response) (*ListDeploymentVersionDependenciesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListDeploymentVersionDependenciesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []DeploymentVersionDependency + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest ErrorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + +// ParseRequestDeploymentVersionDependencyDeletionResponse parses an HTTP response from a RequestDeploymentVersionDependencyDeletionWithResponse call +func ParseRequestDeploymentVersionDependencyDeletionResponse(rsp *http.Response) (*RequestDeploymentVersionDependencyDeletionResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &RequestDeploymentVersionDependencyDeletionResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: + var dest DeploymentRequestAccepted + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON202 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest ErrorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest ErrorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + +// ParseRequestDeploymentVersionDependencyUpsertResponse parses an HTTP response from a RequestDeploymentVersionDependencyUpsertWithResponse call +func ParseRequestDeploymentVersionDependencyUpsertResponse(rsp *http.Response) (*RequestDeploymentVersionDependencyUpsertResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &RequestDeploymentVersionDependencyUpsertResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: + var dest DeploymentRequestAccepted + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON202 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest ErrorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest ErrorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + // ParseRequestUserApprovalRecordUpsertResponse parses an HTTP response from a RequestUserApprovalRecordUpsertWithResponse call func ParseRequestUserApprovalRecordUpsertResponse(rsp *http.Response) (*RequestUserApprovalRecordUpsertResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -13945,12 +14423,12 @@ func ParseCreateDeploymentVersionResponse(rsp *http.Response) (*CreateDeployment } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: var dest DeploymentVersion if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON202 = &dest + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest ErrorResponse @@ -13959,6 +14437,13 @@ func ParseCreateDeploymentVersionResponse(rsp *http.Response) (*CreateDeployment } response.JSON400 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest ErrorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + } return response, nil diff --git a/internal/provider/policy_resource.go b/internal/provider/policy_resource.go index 8a695ca..1a13258 100644 --- a/internal/provider/policy_resource.go +++ b/internal/provider/policy_resource.go @@ -448,6 +448,39 @@ func (r *PolicyResource) Schema(ctx context.Context, req resource.SchemaRequest, }, }, }, + "plan_validation_opa": schema.ListNestedBlock{ + Description: "OPA-based plan validation rules. Each rule must define a `deny` rule set following the Conftest convention.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "created_at": schema.StringAttribute{ + Computed: true, + Description: "Rule creation timestamp", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "id": schema.StringAttribute{ + Computed: true, + Description: "Rule ID", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Human-readable rule name; used in check output to identify which rule produced a violation.", + }, + "description": schema.StringAttribute{ + Optional: true, + Description: "Optional human-readable explanation of the rule.", + }, + "rego": schema.StringAttribute{ + Required: true, + Description: "Rego source code. Follows Conftest conventions for emitting violations.", + }, + }, + }, + }, }, } } @@ -638,6 +671,7 @@ func (r *PolicyResource) Read(ctx context.Context, req resource.ReadRequest, res data.GradualRollout = rules.GradualRollout data.AnyApproval = rules.AnyApproval data.EnvironmentProgression = rules.EnvironmentProgression + data.PlanValidationOpa = rules.PlanValidationOpa resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -728,6 +762,7 @@ func (r *PolicyResource) Update(ctx context.Context, req resource.UpdateRequest, data.GradualRollout = readRules.GradualRollout data.AnyApproval = readRules.AnyApproval data.EnvironmentProgression = readRules.EnvironmentProgression + data.PlanValidationOpa = readRules.PlanValidationOpa resp.Diagnostics.Append(resp.State.Set(ctx, data)...) } @@ -773,6 +808,7 @@ type PolicyResourceModel struct { GradualRollout []PolicyGradualRollout `tfsdk:"gradual_rollout"` AnyApproval []PolicyAnyApproval `tfsdk:"any_approval"` EnvironmentProgression []PolicyEnvironmentProgression `tfsdk:"environment_progression"` + PlanValidationOpa []PolicyPlanValidationOpa `tfsdk:"plan_validation_opa"` } type PolicyVersionSelector struct { @@ -832,6 +868,14 @@ type PolicyVerificationRule struct { Metric []PolicyVerificationMetric `tfsdk:"metric"` } +type PolicyPlanValidationOpa struct { + CreatedAt types.String `tfsdk:"created_at"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Rego types.String `tfsdk:"rego"` +} + type PolicyVerificationMetric struct { Name types.String `tfsdk:"name"` Interval types.String `tfsdk:"interval"` @@ -870,6 +914,7 @@ type policyRulesModel struct { GradualRollout []PolicyGradualRollout AnyApproval []PolicyAnyApproval EnvironmentProgression []PolicyEnvironmentProgression + PlanValidationOpa []PolicyPlanValidationOpa } type policyRequestPayload struct { @@ -893,6 +938,7 @@ type policyRequestRule struct { GradualRollout *api.GradualRolloutRule `json:"gradualRollout,omitempty"` AnyApproval *api.AnyApprovalRule `json:"anyApproval,omitempty"` EnvironmentProgression *api.EnvironmentProgressionRule `json:"environmentProgression,omitempty"` + PlanValidationOpa *api.PlanValidationOpaRule `json:"planValidationOpa,omitempty"` PolicyId *string `json:"policyId,omitempty"` } @@ -1086,6 +1132,29 @@ func policyRulesFromModel(data PolicyResourceModel) ([]policyRequestRule, diag.D }) } + for _, opa := range data.PlanValidationOpa { + id := selectorIDValue(opa.ID) + name := opa.Name.ValueString() + rego := opa.Rego.ValueString() + if name == "" || rego == "" { + diags.AddError("Invalid plan validation rule", "name and rego must be set") + continue + } + rule := api.PlanValidationOpaRule{ + Name: name, + Rego: rego, + } + if selectorValueSet(opa.Description) { + desc := opa.Description.ValueString() + rule.Description = &desc + } + rules = append(rules, policyRequestRule{ + CreatedAt: createdAtValue(opa.CreatedAt), + Id: id, + PlanValidationOpa: &rule, + }) + } + return rules, diags } @@ -1340,6 +1409,19 @@ func policyRulesToModel(rules []api.PolicyRule) (policyRulesModel, diag.Diagnost } result.EnvironmentProgression = append(result.EnvironmentProgression, model) } + if rule.PlanValidationOpa != nil { + model := PolicyPlanValidationOpa{ + CreatedAt: types.StringValue(rule.CreatedAt), + ID: types.StringValue(rule.Id), + Name: types.StringValue(rule.PlanValidationOpa.Name), + Description: types.StringNull(), + Rego: types.StringValue(rule.PlanValidationOpa.Rego), + } + if rule.PlanValidationOpa.Description != nil { + model.Description = types.StringValue(*rule.PlanValidationOpa.Description) + } + result.PlanValidationOpa = append(result.PlanValidationOpa, model) + } } return result, diags @@ -1354,6 +1436,7 @@ func ensurePolicyIDs(plan *PolicyResourceModel, state *PolicyResourceModel) { mergeGradualRolloutIDs(plan.GradualRollout, gradualRolloutListFromState(state)) mergeAnyApprovalIDs(plan.AnyApproval, anyApprovalListFromState(state)) mergeEnvironmentProgressionIDs(plan.EnvironmentProgression, environmentProgressionListFromState(state)) + mergePlanValidationOpaIDs(plan.PlanValidationOpa, planValidationOpaListFromState(state)) } func ensurePolicyRuleCreatedAt(plan *PolicyResourceModel, state *PolicyResourceModel) { @@ -1365,6 +1448,7 @@ func ensurePolicyRuleCreatedAt(plan *PolicyResourceModel, state *PolicyResourceM mergeGradualRolloutCreatedAt(plan.GradualRollout, gradualRolloutListFromState(state)) mergeAnyApprovalCreatedAt(plan.AnyApproval, anyApprovalListFromState(state)) mergeEnvironmentProgressionCreatedAt(plan.EnvironmentProgression, environmentProgressionListFromState(state)) + mergePlanValidationOpaCreatedAt(plan.PlanValidationOpa, planValidationOpaListFromState(state)) } func setPolicyIDOnRules(request *policyRequestPayload, policyID string) { @@ -1644,6 +1728,39 @@ func mergeEnvironmentProgressionCreatedAt(plan []PolicyEnvironmentProgression, s } } +func planValidationOpaListFromState(state *PolicyResourceModel) []PolicyPlanValidationOpa { + if state == nil { + return nil + } + return state.PlanValidationOpa +} + +func mergePlanValidationOpaIDs(plan []PolicyPlanValidationOpa, state []PolicyPlanValidationOpa) { + for i := range plan { + if selectorValueSet(plan[i].ID) { + continue + } + if i < len(state) && selectorValueSet(state[i].ID) { + plan[i].ID = state[i].ID + continue + } + plan[i].ID = types.StringValue(uuid.NewString()) + } +} + +func mergePlanValidationOpaCreatedAt(plan []PolicyPlanValidationOpa, state []PolicyPlanValidationOpa) { + for i := range plan { + if selectorValueSet(plan[i].CreatedAt) { + continue + } + if i < len(state) && selectorValueSet(state[i].CreatedAt) { + plan[i].CreatedAt = state[i].CreatedAt + continue + } + plan[i].CreatedAt = types.StringValue(time.Now().UTC().Format(time.RFC3339)) + } +} + func policyVerificationRuleToModel(rule *api.VerificationRule) (PolicyVerificationRule, error) { model := PolicyVerificationRule{ TriggerOn: types.StringNull(), From f1c2fe8a587bd03af5e4398a848532ce162bc0b1 Mon Sep 17 00:00:00 2001 From: Aditya Choudhari Date: Mon, 4 May 2026 12:29:21 -0400 Subject: [PATCH 2/2] gen --- docs/resources/policy.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/resources/policy.md b/docs/resources/policy.md index 0ddcf26..dffc315 100644 --- a/docs/resources/policy.md +++ b/docs/resources/policy.md @@ -30,6 +30,7 @@ description: |- - `environment_progression` (Block List) Environment progression rules (see [below for nested schema](#nestedblock--environment_progression)) - `gradual_rollout` (Block List) Gradual rollout rules (see [below for nested schema](#nestedblock--gradual_rollout)) - `metadata` (Map of String) The metadata of the policy +- `plan_validation_opa` (Block List) OPA-based plan validation rules. Each rule must define a `deny` rule set following the Conftest convention. (see [below for nested schema](#nestedblock--plan_validation_opa)) - `priority` (Number) The priority of the policy (higher is evaluated first) - `verification` (Block List) Verification rules (see [below for nested schema](#nestedblock--verification)) - `version_cooldown` (Block List) Version cooldown rules (see [below for nested schema](#nestedblock--version_cooldown)) @@ -120,6 +121,24 @@ Read-Only: - `id` (String) Rule ID + +### Nested Schema for `plan_validation_opa` + +Required: + +- `name` (String) Human-readable rule name; used in check output to identify which rule produced a violation. +- `rego` (String) Rego source code. Follows Conftest conventions for emitting violations. + +Optional: + +- `description` (String) Optional human-readable explanation of the rule. + +Read-Only: + +- `created_at` (String) Rule creation timestamp +- `id` (String) Rule ID + + ### Nested Schema for `verification`