-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SUP-2506: Organization rule resource and data source implementation (#…
…562) * First organization rule resource/test implementation, GQL schema * OrganizationRule GraphQL, generated helper code * Update, Delete rules, generated code * Initial Read through node query - will need schema updated * Successful plan of Creates (matching upper in plan/resource attributes) * Required attributes only name/value - tightened Create logic * UUIDs to schema, fragment fields * Initial rule create test * Rule Datasource first implementation * Fine tuning source/target UUID fetching on rule creation * Tidied up source/target and value JSON read funcs * Added ARTIFACTS_READ value JSON reads, fixed TargetUUID state save for creates * Docs generation, slight MarkdownDescription alteration for resource * Datasource docs, example, touched up resource docs * Added additional organization rule resource tests (TRIGGER_BUILD, ARTIFACTS_READ creates/imports) * Added organization rule datasource tests (TRIGGER_BUILD, ARTIFACTS_READ) * Schema update, name->type conversion for rule resource/DSes, tests, docs * Rules resource docs example refresh for name->type refactor * A small capitalization amendment, rule creation state model func * updateOrganizatonRuleReadState reword for Rule resource tests * Docs alignment for the source|target_pipeline_uuid keys in a rule document * Double up characters, unknown action test in organizaton rule resource test * More corner-case, erroneous rule resource tests * Some wording and plan modifier corrections * Linting, switched to organizationRuleDatasourceModel * Linting around diagnostic error checks on Rule creation * Added dependencies in rule resource tests * Templated/rendered docs for organization rules, opt-in note * Some rewording for opting in/rules in development * And even better rewording * Spelling greatness, default erroring for obtaining source/target UUIDs on create * Changelog, some more obtainCreationUUIDs commentary * [skip ci] Changelog number * Added optional description attribute, rule resource tests extensibility, data source using correct schema * Extended rule data source tests with required/all attribute loading, some corrections * Fixed dupe `pipeline.trigger_build.pipeline` all attributes rule resource tests * Added uuid data source reads, ranged data source tests and UUIID read addition, docs refresh * I left in an empty space * Refactored range loop for data source tests to refer to a rule `action` * Ranged rule resource tests (creates, imports) * source|target_pipeline_uuid -> source|target_pipeline * UUID validation on rule creates, slight test renaming * Quick lints * Added rule document via GQL, conditions (un)+marshalling to rule resource/data source value in state * Conditions to rule create tests, data source load all tests * Storing document returned value from rule creates, conditions invalid error test * Added required attribute rule resource import * Realigned docs link * IDed + extra logging
- Loading branch information
Showing
21 changed files
with
4,440 additions
and
697 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,271 @@ | ||
package buildkite | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log" | ||
|
||
"github.com/MakeNowJust/heredoc" | ||
"github.com/hashicorp/terraform-plugin-framework/datasource" | ||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" | ||
) | ||
|
||
type organizationRuleDatasourceModel struct { | ||
ID types.String `tfsdk:"id"` | ||
UUID types.String `tfsdk:"uuid"` | ||
Description types.String `tfsdk:"description"` | ||
Type types.String `tfsdk:"type"` | ||
Value types.String `tfsdk:"value"` | ||
SourceType types.String `tfsdk:"source_type"` | ||
SourceUUID types.String `tfsdk:"source_uuid"` | ||
TargetType types.String `tfsdk:"target_type"` | ||
TargetUUID types.String `tfsdk:"target_uuid"` | ||
Effect types.String `tfsdk:"effect"` | ||
Action types.String `tfsdk:"action"` | ||
} | ||
|
||
type organizationRuleDatasource struct { | ||
client *Client | ||
} | ||
|
||
func newOrganizationRuleDatasource() datasource.DataSource { | ||
return &organizationRuleDatasource{} | ||
} | ||
|
||
func (organizationRuleDatasource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { | ||
resp.TypeName = req.ProviderTypeName + "_organization_rule" | ||
} | ||
|
||
func (or *organizationRuleDatasource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { | ||
if req.ProviderData == nil { | ||
return | ||
} | ||
|
||
or.client = req.ProviderData.(*Client) | ||
} | ||
|
||
func (organizationRuleDatasource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { | ||
resp.Schema = schema.Schema{ | ||
MarkdownDescription: heredoc.Doc(` | ||
Use this data source to retrieve an organization rule by its ID. | ||
More information on organization rules can be found in the [documentation](https://buildkite.com/docs/pipelines/rules). | ||
`), | ||
Attributes: map[string]schema.Attribute{ | ||
"id": schema.StringAttribute{ | ||
Optional: true, | ||
MarkdownDescription: "The GraphQL ID of the organization rule. ", | ||
}, | ||
"uuid": schema.StringAttribute{ | ||
Optional: true, | ||
MarkdownDescription: "The UUID of the organization rule. ", | ||
}, | ||
"description": schema.StringAttribute{ | ||
Computed: true, | ||
MarkdownDescription: "The description of the organization rule. ", | ||
}, | ||
"type": schema.StringAttribute{ | ||
Computed: true, | ||
MarkdownDescription: "The type of organization rule. ", | ||
}, | ||
"value": schema.StringAttribute{ | ||
Computed: true, | ||
MarkdownDescription: "The JSON document that this organization rule implements. ", | ||
}, | ||
"source_type": schema.StringAttribute{ | ||
Computed: true, | ||
MarkdownDescription: "The source resource type that this organization rule allows or denies to invoke its defined action. ", | ||
}, | ||
"source_uuid": schema.StringAttribute{ | ||
Computed: true, | ||
MarkdownDescription: "The UUID of the resource that this organization rule allows or denies invocating its defined action. ", | ||
}, | ||
"target_type": schema.StringAttribute{ | ||
Computed: true, | ||
MarkdownDescription: "The target resource type that this organization rule allows or denies the source to respective action. ", | ||
}, | ||
"target_uuid": schema.StringAttribute{ | ||
Computed: true, | ||
MarkdownDescription: "The UUID of the target resourcee that this organization rule allows or denies invocation its respective action. ", | ||
}, | ||
"effect": schema.StringAttribute{ | ||
Computed: true, | ||
MarkdownDescription: "Whether this organization rule allows or denys the action to take place between source and target resources. ", | ||
}, | ||
"action": schema.StringAttribute{ | ||
Computed: true, | ||
MarkdownDescription: "The action defined between source and target resources. ", | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func (or *organizationRuleDatasource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { | ||
var state organizationRuleDatasourceModel | ||
|
||
resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
timeouts, diags := or.client.timeouts.Read(ctx, DefaultTimeout) | ||
resp.Diagnostics.Append(diags...) | ||
|
||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
// If a UUID is entered through an organization rule data source | ||
if !state.UUID.IsNull() { | ||
matchFound := false | ||
err := retry.RetryContext(ctx, timeouts, func() *retry.RetryError { | ||
var cursor *string | ||
for { | ||
r, err := getOrganizationRules( | ||
ctx, | ||
or.client.genqlient, | ||
or.client.organization, | ||
cursor) | ||
if err != nil { | ||
if isRetryableError(err) { | ||
return retry.RetryableError(err) | ||
} | ||
resp.Diagnostics.AddError( | ||
"Unable to read organizatiion rules", | ||
fmt.Sprintf("Unable to read organizatiion rules: %s", err.Error()), | ||
) | ||
return retry.NonRetryableError(err) | ||
} | ||
|
||
for _, rule := range r.Organization.Rules.Edges { | ||
if rule.Node.Uuid == state.UUID.ValueString() { | ||
matchFound = true | ||
// Update data source state from the found rule | ||
value, err := obtainValueJSON(rule.Node.Document) | ||
if err != nil { | ||
resp.Diagnostics.AddError( | ||
"Unable to read organization rule", | ||
fmt.Sprintf("Unable to read organmization rule: %s", err.Error()), | ||
) | ||
} | ||
updateOrganizatonRuleDatasourceFromNode(&state, rule.Node, *value) | ||
break | ||
} | ||
} | ||
|
||
// If there is a match, or there is no next page, break | ||
if matchFound || !r.Organization.Rules.PageInfo.HasNextPage { | ||
break | ||
} | ||
|
||
// Move to the next cursor | ||
cursor = &r.Organization.Rules.PageInfo.EndCursor | ||
} | ||
return nil | ||
}) | ||
|
||
if err != nil { | ||
resp.Diagnostics.AddError("Unable to find organization rule", err.Error()) | ||
return | ||
} | ||
|
||
if !matchFound { | ||
resp.Diagnostics.AddError("Unable to find organization rule", | ||
fmt.Sprintf("Could not find an organization rule with UUID \"%s\"", state.UUID.ValueString())) | ||
return | ||
} | ||
// Otherwise if a ID is specified | ||
} else if !state.ID.IsNull() { | ||
var apiResponse *getNodeResponse | ||
err := retry.RetryContext(ctx, timeouts, func() *retry.RetryError { | ||
var err error | ||
|
||
log.Printf("Reading organization rule with ID %s ...", state.ID.ValueString()) | ||
apiResponse, err = getNode(ctx, | ||
or.client.genqlient, | ||
state.ID.ValueString(), | ||
) | ||
|
||
return retryContextError(err) | ||
}) | ||
|
||
if err != nil { | ||
resp.Diagnostics.AddError( | ||
"Unable to read organization rule", | ||
fmt.Sprintf("Unable to read organmization rule: %s", err.Error()), | ||
) | ||
return | ||
} | ||
|
||
// Convert fron Node to getNodeNodeRule type | ||
if organizationRule, ok := apiResponse.GetNode().(*getNodeNodeRule); ok { | ||
if organizationRule == nil { | ||
resp.Diagnostics.AddError( | ||
"Unable to get organization rule", | ||
"Error getting organization rule: nil response", | ||
) | ||
return | ||
} | ||
// Update data source state from the found rule | ||
value, err := obtainValueJSON(organizationRule.Document) | ||
if err != nil { | ||
resp.Diagnostics.AddError( | ||
"Unable to read organization rule", | ||
fmt.Sprintf("Unable to read organmization rule: %s", err.Error()), | ||
) | ||
} | ||
updateOrganizatonRuleDatasourceState(&state, *organizationRule, *value) | ||
} | ||
} | ||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) | ||
} | ||
|
||
func obtainDatasourceReadUUIDs(orn getOrganizationRulesOrganizationRulesRuleConnectionEdgesRuleEdgeNodeRule) (string, string) { | ||
var sourceUUID, targetUUID string | ||
|
||
switch orn.SourceType { | ||
case "PIPELINE": | ||
sourceUUID = orn.Source.(*OrganizationRuleFieldsSourcePipeline).Uuid | ||
} | ||
|
||
switch orn.TargetType { | ||
case "PIPELINE": | ||
targetUUID = orn.Target.(*OrganizationRuleFieldsTargetPipeline).Uuid | ||
} | ||
|
||
return sourceUUID, targetUUID | ||
} | ||
|
||
func updateOrganizatonRuleDatasourceState(or *organizationRuleDatasourceModel, orn getNodeNodeRule, value string) { | ||
sourceUUID, targetUUID := obtainReadUUIDs(orn) | ||
|
||
or.ID = types.StringValue(orn.Id) | ||
or.UUID = types.StringValue(orn.Uuid) | ||
or.Description = types.StringPointerValue(orn.Description) | ||
or.Type = types.StringValue(orn.Type) | ||
or.Value = types.StringValue(value) | ||
or.SourceType = types.StringValue(string(orn.SourceType)) | ||
or.SourceUUID = types.StringValue(sourceUUID) | ||
or.TargetType = types.StringValue(string(orn.TargetType)) | ||
or.TargetUUID = types.StringValue(targetUUID) | ||
or.Effect = types.StringValue(string(orn.Effect)) | ||
or.Action = types.StringValue(string(orn.Action)) | ||
} | ||
|
||
func updateOrganizatonRuleDatasourceFromNode(or *organizationRuleDatasourceModel, orn getOrganizationRulesOrganizationRulesRuleConnectionEdgesRuleEdgeNodeRule, value string) { | ||
sourceUUID, targetUUID := obtainDatasourceReadUUIDs(orn) | ||
|
||
or.ID = types.StringValue(orn.Id) | ||
or.UUID = types.StringValue(orn.Uuid) | ||
or.Description = types.StringPointerValue(orn.Description) | ||
or.Type = types.StringValue(orn.Type) | ||
or.Value = types.StringValue(value) | ||
or.SourceType = types.StringValue(string(orn.SourceType)) | ||
or.SourceUUID = types.StringValue(sourceUUID) | ||
or.TargetType = types.StringValue(string(orn.TargetType)) | ||
or.TargetUUID = types.StringValue(targetUUID) | ||
or.Effect = types.StringValue(string(orn.Effect)) | ||
or.Action = types.StringValue(string(orn.Action)) | ||
} |
Oops, something went wrong.