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

Migrate agent token to framework #289

Merged
merged 3 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.

* SUP-202 Add graphql example queries for finding import IDs [[PR #280](https://github.com/buildkite/terraform-provider-buildkite/pull/280)] @james2791 @jradtilbrook
* SUP-1072 Create new provider using framework plugin [[PR #286](https://github.com/buildkite/terraform-provider-buildkite/pull/286)] @jradtilbrook
* SUP-1066 Migrate agent token to framework [[PR #289](https://github.com/buildkite/terraform-provider-buildkite/pull/289)] @jradtilbrook

## [v0.19.0](https://github.com/buildkite/terraform-provider-buildkite/compare/v0.18.0...v0.19.0)

Expand Down
20 changes: 10 additions & 10 deletions buildkite/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions buildkite/graphql/agent_token.graphql
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
query getAgentToken($slug: ID!) {
agentToken(slug: $slug) {
id
# @genqlient(pointer: true)
description
uuid
}
}

mutation createAgentToken(
$organizationId: ID!
# @genqlient(pointer: true)
$description: String
) {
agentTokenCreate(
Expand All @@ -20,6 +22,7 @@ mutation createAgentToken(
agentTokenEdge {
node {
id
# @genqlient(pointer: true)
description
uuid
}
Expand All @@ -31,6 +34,7 @@ mutation revokeAgentToken($id: ID!, $reason: String!) {
agentTokenRevoke(input: {id: $id, reason: $reason}){
agentToken {
id
# @genqlient(pointer: true)
description
uuid
}
Expand Down
9 changes: 5 additions & 4 deletions buildkite/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ func (tf *terraformProvider) Configure(ctx context.Context, req provider.Configu
resp.Diagnostics.AddError(err.Error(), fmt.Sprintf("... details ... %s", err))
}

resp.ResourceData = &client
resp.DataSourceData = &client
resp.ResourceData = client
resp.DataSourceData = client
}

func (*terraformProvider) DataSources(context.Context) []func() datasource.DataSource {
Expand All @@ -90,7 +90,9 @@ func (tf *terraformProvider) Metadata(ctx context.Context, req provider.Metadata
}

func (*terraformProvider) Resources(context.Context) []func() resource.Resource {
return []func() resource.Resource{}
return []func() resource.Resource{
NewAgentTokenResource,
}
}

func (*terraformProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
Expand Down Expand Up @@ -127,7 +129,6 @@ func New(version string) provider.Provider {
func Provider(version string) *schema.Provider {
provider := &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"buildkite_agent_token": resourceAgentToken(),
"buildkite_pipeline": resourcePipeline(),
"buildkite_pipeline_schedule": resourcePipelineSchedule(),
"buildkite_team": resourceTeam(),
Expand Down
153 changes: 91 additions & 62 deletions buildkite/resource_agent_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package buildkite

import (
"context"
"errors"
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"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"
"github.com/shurcooL/graphql"
)

Expand All @@ -19,88 +21,115 @@ type AgentTokenNode struct {
RevokedAt graphql.String
}

func resourceAgentToken() *schema.Resource {
return &schema.Resource{
CreateContext: CreateToken,
ReadContext: ReadToken,
// NB: there is no updating a token, changes force a new one to be creaated
DeleteContext: DeleteToken,
Importer: nil,
Schema: map[string]*schema.Schema{
"description": &schema.Schema{
ForceNew: true,
Optional: true,
Type: schema.TypeString,
},
"token": &schema.Schema{
Computed: true,
Type: schema.TypeString,
},
"uuid": &schema.Schema{
Computed: true,
Type: schema.TypeString,
},
},
type AgentTokenStateModel struct {
Description types.String `tfsdk:"description"`
Id types.String `tfsdk:"id"`
Token types.String `tfsdk:"token"`
Uuid types.String `tfsdk:"uuid"`
}

type AgentTokenResource struct {
client *Client
}

func NewAgentTokenResource() resource.Resource {
return &AgentTokenResource{}
}

func (at *AgentTokenResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
at.client = req.ProviderData.(*Client)
}

// CreateToken creates a Buildkite agent token
func CreateToken(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics
client := m.(*Client)
func (at *AgentTokenResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan, state AgentTokenStateModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)

apiResponse, err := createAgentToken(
client.genqlient,
client.organizationId,
d.Get("description").(string),
at.client.genqlient,
at.client.organizationId,
plan.Description.ValueStringPointer(),
)

if err != nil {
return diag.FromErr(err)
resp.Diagnostics.AddError(err.Error(), err.Error())
}

d.SetId(apiResponse.AgentTokenCreate.AgentTokenEdge.Node.Id)
d.Set("uuid", apiResponse.AgentTokenCreate.AgentTokenEdge.Node.Uuid)
d.Set("description", apiResponse.AgentTokenCreate.AgentTokenEdge.Node.Description)
d.Set("token", apiResponse.AgentTokenCreate.TokenValue)
state.Description = types.StringPointerValue(apiResponse.AgentTokenCreate.AgentTokenEdge.Node.Description)
state.Id = types.StringValue(apiResponse.AgentTokenCreate.AgentTokenEdge.Node.Id)
state.Token = types.StringValue(apiResponse.AgentTokenCreate.TokenValue)
state.Uuid = types.StringValue(apiResponse.AgentTokenCreate.AgentTokenEdge.Node.Uuid)

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

// ReadToken retrieves a Buildkite agent token
func ReadToken(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics
client := m.(*Client)
func (at *AgentTokenResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var plan AgentTokenStateModel
resp.Diagnostics.Append(req.State.Get(ctx, &plan)...)

agentToken, err := getAgentToken(client.genqlient, fmt.Sprintf("%s/%s", client.organization, d.Get("uuid").(string)))
_, err := revokeAgentToken(at.client.genqlient, plan.Id.ValueString(), "Revoked by Terraform")

if err != nil {
return diag.FromErr(err)
}

if agentToken.AgentToken.Id == "" {
return diag.FromErr(errors.New("Agent Token not found"))
resp.Diagnostics.AddError(err.Error(), err.Error())
}
}

d.SetId(agentToken.AgentToken.Id)
d.Set("uuid", agentToken.AgentToken.Uuid)
d.Set("description", agentToken.AgentToken.Description)
// NB: we never set the token in read context because its not available in the API after creation

return diags
func (AgentTokenResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = "buildkite_agent_token"
}

// DeleteToken revokes a Buildkite agent token - they cannot be completely deleted (will have a revoke)
func DeleteToken(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics
client := m.(*Client)
var err error
func (at *AgentTokenResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var plan, state AgentTokenStateModel
resp.Diagnostics.Append(req.State.Get(ctx, &plan)...)

_, err = revokeAgentToken(client.genqlient, d.Id(), "Revoked by Terraform")
agentToken, err := getAgentToken(at.client.genqlient, fmt.Sprintf("%s/%s", at.client.organization, plan.Uuid.ValueString()))

if err != nil {
return diag.FromErr(err)
resp.Diagnostics.AddError(err.Error(), err.Error())
}
if agentToken == nil {
resp.Diagnostics.AddError("Agent token not found", "Removing from state")
resp.State.RemoveResource(ctx)
return
}

state.Description = types.StringPointerValue(agentToken.AgentToken.Description)
state.Id = types.StringValue(agentToken.AgentToken.Id)
state.Token = plan.Token // token is never returned after creation so use the existing value in state
state.Uuid = types.StringValue(agentToken.AgentToken.Uuid)

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

func (AgentTokenResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = resource_schema.Schema{
Attributes: map[string]resource_schema.Attribute{
"description": resource_schema.StringAttribute{
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"id": resource_schema.StringAttribute{
Computed: true,
},
"token": resource_schema.StringAttribute{
Computed: true,
Sensitive: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"uuid": resource_schema.StringAttribute{
Computed: true,
},
},
}
}

return diags
func (AgentTokenResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
resp.Diagnostics.AddError("Cannot update an agent token", "A new agent token must be created")
panic("cannot update an agent token")
}
15 changes: 8 additions & 7 deletions buildkite/resource_agent_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package buildkite

import (
"context"
"errors"
"fmt"
"strings"
"testing"
Expand All @@ -13,6 +12,7 @@ import (

// Confirm that we can create a new agent token, and then delete it without error
func TestAccAgentToken_add_remove(t *testing.T) {
t.Parallel()
var resourceToken AgentTokenNode

resource.Test(t, resource.TestCase{
Expand All @@ -29,19 +29,19 @@ func TestAccAgentToken_add_remove(t *testing.T) {
testAccCheckAgentTokenRemoteValues(&resourceToken, "Acceptance Test foo"),
// Confirm the token has the correct values in terraform state
resource.TestCheckResourceAttr("buildkite_agent_token.foobar", "description", "Acceptance Test foo"),
resource.TestCheckResourceAttrSet("buildkite_agent_token.foobar", "id"),
resource.TestCheckResourceAttrSet("buildkite_agent_token.foobar", "token"),
resource.TestCheckResourceAttrSet("buildkite_agent_token.foobar", "uuid"),
),
},
{
RefreshState: true,
PlanOnly: true,
Check: resource.ComposeAggregateTestCheckFunc(
// Confirm the token has the correct values in terraform state
resource.TestCheckResourceAttrWith("buildkite_agent_token.foobar", "token", func(value string) error {
if value == "" {
return errors.New("Token should not be empty.")
}
return nil
}),
resource.TestCheckResourceAttrSet("buildkite_agent_token.foobar", "id"),
resource.TestCheckResourceAttrSet("buildkite_agent_token.foobar", "token"),
resource.TestCheckResourceAttrSet("buildkite_agent_token.foobar", "uuid"),
),
},
},
Expand All @@ -51,6 +51,7 @@ func TestAccAgentToken_add_remove(t *testing.T) {
// Confirm that we can create a new agent token, and then update the description
// Technically tokens can't be updated, so this will actuall do a delete+create
func TestAccAgentToken_update(t *testing.T) {
t.Parallel()
var resourceToken AgentTokenNode

resource.Test(t, resource.TestCase{
Expand Down
Loading