package tfe import ( "bytes" "context" "errors" "fmt" "io" "net/url" "time" ) // Compile-time proof of interface implementation. var _ PolicyChecks = (*policyChecks)(nil) // PolicyChecks describes all the policy check related methods that the // Terraform Enterprise API supports. // // TFE API docs: // https://www.terraform.io/docs/enterprise/api/policy-checks.html type PolicyChecks interface { // List all policy checks of the given run. List(ctx context.Context, runID string, options PolicyCheckListOptions) (*PolicyCheckList, error) // Read a policy check by its ID. Read(ctx context.Context, policyCheckID string) (*PolicyCheck, error) // Override a soft-mandatory or warning policy. Override(ctx context.Context, policyCheckID string) (*PolicyCheck, error) // Logs retrieves the logs of a policy check. Logs(ctx context.Context, policyCheckID string) (io.Reader, error) } // policyChecks implements PolicyChecks. type policyChecks struct { client *Client } // PolicyScope represents a policy scope. type PolicyScope string // List all available policy scopes. const ( PolicyScopeOrganization PolicyScope = "organization" PolicyScopeWorkspace PolicyScope = "workspace" ) // PolicyStatus represents a policy check state. type PolicyStatus string //List all available policy check statuses. const ( PolicyCanceled PolicyStatus = "canceled" PolicyErrored PolicyStatus = "errored" PolicyHardFailed PolicyStatus = "hard_failed" PolicyOverridden PolicyStatus = "overridden" PolicyPasses PolicyStatus = "passed" PolicyPending PolicyStatus = "pending" PolicyQueued PolicyStatus = "queued" PolicySoftFailed PolicyStatus = "soft_failed" PolicyUnreachable PolicyStatus = "unreachable" ) // PolicyCheckList represents a list of policy checks. type PolicyCheckList struct { *Pagination Items []*PolicyCheck } // PolicyCheck represents a Terraform Enterprise policy check.. type PolicyCheck struct { ID string `jsonapi:"primary,policy-checks"` Actions *PolicyActions `jsonapi:"attr,actions"` Permissions *PolicyPermissions `jsonapi:"attr,permissions"` Result *PolicyResult `jsonapi:"attr,result"` Scope PolicyScope `jsonapi:"attr,scope"` Status PolicyStatus `jsonapi:"attr,status"` StatusTimestamps *PolicyStatusTimestamps `jsonapi:"attr,status-timestamps"` } // PolicyActions represents the policy check actions. type PolicyActions struct { IsOverridable bool `json:"is-overridable"` } // PolicyPermissions represents the policy check permissions. type PolicyPermissions struct { CanOverride bool `json:"can-override"` } // PolicyResult represents the complete policy check result, type PolicyResult struct { AdvisoryFailed int `json:"advisory-failed"` Duration int `json:"duration"` HardFailed int `json:"hard-failed"` Passed int `json:"passed"` Result bool `json:"result"` // Sentinel *sentinel.EvalResult `json:"sentinel"` SoftFailed int `json:"soft-failed"` TotalFailed int `json:"total-failed"` } // PolicyStatusTimestamps holds the timestamps for individual policy check // statuses. type PolicyStatusTimestamps struct { ErroredAt time.Time `json:"errored-at"` HardFailedAt time.Time `json:"hard-failed-at"` PassedAt time.Time `json:"passed-at"` QueuedAt time.Time `json:"queued-at"` SoftFailedAt time.Time `json:"soft-failed-at"` } // PolicyCheckListOptions represents the options for listing policy checks. type PolicyCheckListOptions struct { ListOptions } // List all policy checks of the given run. func (s *policyChecks) List(ctx context.Context, runID string, options PolicyCheckListOptions) (*PolicyCheckList, error) { if !validStringID(&runID) { return nil, errors.New("invalid value for run ID") } u := fmt.Sprintf("runs/%s/policy-checks", url.QueryEscape(runID)) req, err := s.client.newRequest("GET", u, &options) if err != nil { return nil, err } pcl := &PolicyCheckList{} err = s.client.do(ctx, req, pcl) if err != nil { return nil, err } return pcl, nil } // Read a policy check by its ID. func (s *policyChecks) Read(ctx context.Context, policyCheckID string) (*PolicyCheck, error) { if !validStringID(&policyCheckID) { return nil, errors.New("invalid value for policy check ID") } u := fmt.Sprintf("policy-checks/%s", url.QueryEscape(policyCheckID)) req, err := s.client.newRequest("GET", u, nil) if err != nil { return nil, err } pc := &PolicyCheck{} err = s.client.do(ctx, req, pc) if err != nil { return nil, err } return pc, nil } // Override a soft-mandatory or warning policy. func (s *policyChecks) Override(ctx context.Context, policyCheckID string) (*PolicyCheck, error) { if !validStringID(&policyCheckID) { return nil, errors.New("invalid value for policy check ID") } u := fmt.Sprintf("policy-checks/%s/actions/override", url.QueryEscape(policyCheckID)) req, err := s.client.newRequest("POST", u, nil) if err != nil { return nil, err } pc := &PolicyCheck{} err = s.client.do(ctx, req, pc) if err != nil { return nil, err } return pc, nil } // Logs retrieves the logs of a policy check. func (s *policyChecks) Logs(ctx context.Context, policyCheckID string) (io.Reader, error) { if !validStringID(&policyCheckID) { return nil, errors.New("invalid value for policy check ID") } // Loop until the context is canceled or the policy check is finished // running. The policy check logs are not streamed and so only available // once the check is finished. for { pc, err := s.Read(ctx, policyCheckID) if err != nil { return nil, err } switch pc.Status { case PolicyPending, PolicyQueued: select { case <-ctx.Done(): return nil, ctx.Err() case <-time.After(500 * time.Millisecond): continue } } u := fmt.Sprintf("policy-checks/%s/output", url.QueryEscape(policyCheckID)) req, err := s.client.newRequest("GET", u, nil) if err != nil { return nil, err } logs := bytes.NewBuffer(nil) err = s.client.do(ctx, req, logs) if err != nil { return nil, err } return logs, nil } }