Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SUP-1084 Add Cluster resource #301

Merged
merged 41 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
2878e7c
Initial Cluster resource work
mcncl Jun 20, 2023
4b24e3b
No need for API model right now
mcncl Jun 20, 2023
7e6742f
CRUD complete for Cluster.
mcncl Jun 20, 2023
2215983
Merge remote-tracking branch 'origin/main' into bm/cluster_resource
mcncl Jun 20, 2023
ca27445
Saving for later development
mcncl Jun 21, 2023
7120fda
Merge in agent token changes
mcncl Jun 21, 2023
1115cc1
Cluster resource complete
mcncl Jun 21, 2023
50d26af
Improve GraphQL queries for more data.
mcncl Jun 22, 2023
1cb1929
CRUD for Clusters
mcncl Jun 22, 2023
19e1a57
Cluster resource tests
mcncl Jun 22, 2023
1ddf4f5
Use a planmodifier to maintain state for ID and UUID
mcncl Jun 23, 2023
1044ded
Bit of clean up.
mcncl Jun 28, 2023
a148d79
Merge remote-tracking branch 'origin/main' into bm/cluster_resource
mcncl Jun 28, 2023
0de1392
Fix some testing typos.
mcncl Jun 28, 2023
d1257eb
Add gotest
mcncl Jun 28, 2023
fa0d167
Destroy test refactor.
mcncl Jun 28, 2023
f5e9ccd
GraphQL fragments.
mcncl Jun 28, 2023
3b70603
Get for Cluster uses UUID
mcncl Jun 29, 2023
0cc2d7f
State doesn't embed cluster under org
mcncl Jun 30, 2023
f0208aa
Destroy shuold work now
mcncl Jun 30, 2023
06a7734
Add Import
mcncl Jul 3, 2023
4f0546c
Add remote check to see if it fails
mcncl Jul 3, 2023
2f0e4a6
Cluster tests passing. Doesn't check remote as returns null at moment.
mcncl Jul 3, 2023
4cb59ed
Add Cluster examples.
mcncl Jul 3, 2023
3d16e88
The story so far.
mcncl Jul 4, 2023
36e5025
Add an update func for Read to implement
mcncl Jul 4, 2023
9a649cb
Merge remote-tracking branch 'origin/main' into bm/cluster_resource
mcncl Jul 4, 2023
55378f8
Make Import sick
mcncl Jul 4, 2023
ad670f0
Re-gen schema.
mcncl Jul 4, 2023
49f7e7f
Change to pointer value
mcncl Jul 4, 2023
baaaeff
Fix the damn tests.
mcncl Jul 4, 2023
66e6fe6
Fix import test
jradtilbrook Jul 4, 2023
d848609
Omit new pipeline template input
jradtilbrook Jul 5, 2023
92f3ab5
Local changes so can fetch upstream changes
mcncl Jul 4, 2023
4eabb68
Make Update pass.
mcncl Jul 5, 2023
6a5915d
fmt
mcncl Jul 5, 2023
eec30c8
Use new() to create a non-empty
mcncl Jul 5, 2023
d5ec7ba
Empyting ID caused issue.
mcncl Jul 5, 2023
e174396
gofmt
mcncl Jul 5, 2023
80e1037
CHANGELOG and docs
mcncl Jul 5, 2023
f25ffad
%s/E/e
mcncl Jul 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
679 changes: 679 additions & 0 deletions buildkite/generated.go

Large diffs are not rendered by default.

70 changes: 70 additions & 0 deletions buildkite/graphql/cluster.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
fragment ClusterFields on Cluster {
id
uuid
name
description
emoji
color
}

query getCluster($orgSlug: ID!, $id: ID!) {
organization(slug: $orgSlug) {
cluster(id: $id) {
...ClusterFields
}
}
}

# @genqlient(for: "ClusterCreateInput.description", pointer: true)
# @genqlient(for: "ClusterCreateInput.emoji", pointer: true)
# @genqlient(for: "ClusterCreateInput.color", pointer: true)
jradtilbrook marked this conversation as resolved.
Show resolved Hide resolved
mutation createCluster(
$organizationId: ID!
$name: String!
$description: String
$emoji: String
$color: String
) {
clusterCreate(
input: { organizationId: $organizationId, name: $name, description: $description, emoji: $emoji, color: $color }
) {
clientMutationId
cluster {
...ClusterFields
}
}
}

# @genqlient(for: "ClusterUpdateInput.description", pointer: true)
# @genqlient(for: "ClusterUpdateInput.emoji", pointer: true)
# @genqlient(for: "ClusterUpdateInput.color", pointer: true)
mutation updateCluster(
$organizationId: ID!
$id: ID!
$name: String
$description: String
$emoji: String
$color: String
) {
clusterUpdate(
input: {
organizationId: $organizationId
id: $id
name: $name
description: $description
emoji: $emoji
color: $color
}
) {
clientMutationId
cluster {
...ClusterFields
}
}
}

mutation deleteCluster($organizationId: ID!, $id: ID!) {
clusterDelete(input: { organizationId: $organizationId, id: $id }) {
clientMutationId
}
}
1 change: 1 addition & 0 deletions buildkite/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ func (tf *terraformProvider) Metadata(ctx context.Context, req provider.Metadata
func (*terraformProvider) Resources(context.Context) []func() resource.Resource {
return []func() resource.Resource{
NewAgentTokenResource,
newClusterResource,
}
}

Expand Down
216 changes: 216 additions & 0 deletions buildkite/resource_cluster.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package buildkite

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
resource_schema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)

type clusterResource struct {
client *Client
}

type clusterResourceModel struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
Emoji types.String `tfsdk:"emoji"`
Color types.String `tfsdk:"color"`
UUID types.String `tfsdk:"uuid"`
}

func newClusterResource() resource.Resource {
return &clusterResource{}
}

func (c *clusterResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_cluster"
}

func (c *clusterResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

c.client = req.ProviderData.(*Client)
}

func (c *clusterResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = resource_schema.Schema{
MarkdownDescription: "A Cluster is a group of Agents that can be used to run your builds. " +
"Clusters are useful for grouping Agents by their capabilities, such as operating system, hardware, or location. ",
Attributes: map[string]resource_schema.Attribute{
"id": resource_schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"uuid": resource_schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"name": resource_schema.StringAttribute{
MarkdownDescription: "The name of the Cluster. Can only contain numbers and letters, no spaces or special characters.",
Required: true,
},
"description": resource_schema.StringAttribute{
Optional: true,
MarkdownDescription: "A description for the Cluster. Consider something short but clear on the Cluster's function.",
},
"emoji": resource_schema.StringAttribute{
Optional: true,
MarkdownDescription: "An emoji to represent the Cluster. Accepts the format :buildkite:.",
},
"color": resource_schema.StringAttribute{
Optional: true,
MarkdownDescription: "A color representation of the Cluster. Accepts hex codes, eg #BADA55.",
},
},
}
}

func (c *clusterResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var state *clusterResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...)

if resp.Diagnostics.HasError() {
return
}

r, err := createCluster(
c.client.genqlient,
c.client.organizationId,
state.Name.ValueString(),
state.Description.ValueString(),
state.Emoji.ValueString(),
state.Color.ValueString(),
)

if err != nil {
resp.Diagnostics.AddError(
"Unable to create Cluster",
fmt.Sprintf("Unable to create Cluster: %s", err.Error()),
)
return
}

state.ID = types.StringValue(r.ClusterCreate.Cluster.Id)
state.UUID = types.StringValue(r.ClusterCreate.Cluster.Uuid)

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (c *clusterResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state clusterResourceModel

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)

if resp.Diagnostics.HasError() {
return
}

_, err := getCluster(c.client.genqlient, c.client.organization, state.UUID.ValueString())

if err != nil {
resp.Diagnostics.AddError(
"Unable to read Cluster",
fmt.Sprintf("Unable to read Cluster: %s", err.Error()),
)
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
jradtilbrook marked this conversation as resolved.
Show resolved Hide resolved
}

func (c *clusterResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var state, plan clusterResourceModel

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)

if resp.Diagnostics.HasError() {
return
}

id := state.ID.ValueString()

_, err := updateCluster(
c.client.genqlient,
c.client.organizationId,
id,
plan.Name.ValueString(),
plan.Description.ValueString(),
plan.Emoji.ValueString(),
plan.Color.ValueString(),
)

if err != nil {
resp.Diagnostics.AddError(
"Unable to update Cluster",
fmt.Sprintf("Unable to update Cluster: %s", err.Error()),
)
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}

func (c *clusterResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state clusterResourceModel

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)

if resp.Diagnostics.HasError() {
return
}

_, err := deleteCluster(c.client.genqlient, c.client.organizationId, state.ID.ValueString())

if err != nil {
resp.Diagnostics.AddError(
"Unable to delete Cluster",
fmt.Sprintf("Unable to delete Cluster: %s", err.Error()),
)
return
}
}

func (c *clusterResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
uuid := req.ID

if uuid == "" {
resp.Diagnostics.AddError(
"Unable to import Cluster",
"Unable to import Cluster, no UUID was provided",
)
return
}

cluster, err := getCluster(c.client.genqlient, c.client.organization, uuid)

if err != nil {
resp.Diagnostics.AddError(
"Unable to import Cluster",
fmt.Sprintf("Unable to import Cluster: %s", err.Error()),
)
return
}

id := cluster.Organization.Cluster.Id

resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), types.StringValue(id))...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("uuid"), uuid)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), types.StringValue(cluster.Organization.Cluster.Name))...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("description"), types.StringValue(cluster.Organization.Cluster.Description))...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("emoji"), types.StringValue(cluster.Organization.Cluster.Emoji))...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("color"), types.StringValue(cluster.Organization.Cluster.Color))...)
jradtilbrook marked this conversation as resolved.
Show resolved Hide resolved
}
Loading