From 7e737a77ac664af74c3c1fab66b15a2f28756aa4 Mon Sep 17 00:00:00 2001 From: James Sammut Date: Thu, 29 Jun 2023 17:33:36 +1000 Subject: [PATCH 01/12] Created Cluster Queue resource, GraphQL queries & generated code --- buildkite/generated.go | 683 +++++++++++++++++++++++ buildkite/graphql/cluster_queue.graphql | 78 +++ buildkite/provider.go | 1 + buildkite/resource_cluster_queue.go | 210 +++++++ buildkite/resource_cluster_queue_test.go | 68 +++ schema.graphql | 69 ++- 6 files changed, 1090 insertions(+), 19 deletions(-) create mode 100644 buildkite/graphql/cluster_queue.graphql create mode 100644 buildkite/resource_cluster_queue.go create mode 100644 buildkite/resource_cluster_queue_test.go diff --git a/buildkite/generated.go b/buildkite/generated.go index bc31b675..1aa62664 100644 --- a/buildkite/generated.go +++ b/buildkite/generated.go @@ -3,6 +3,8 @@ package buildkite import ( + "encoding/json" + "github.com/Khan/genqlient/graphql" ) @@ -26,6 +28,44 @@ const ( BuildRetentionPeriodsYears2 BuildRetentionPeriods = "YEARS_2" ) +// ClusterQueueValues includes the GraphQL fields of ClusterQueue requested by the fragment ClusterQueueValues. +type ClusterQueueValues struct { + Id string `json:"id"` + // The public UUID for this cluster queue + Uuid string `json:"uuid"` + Key string `json:"key"` + Description string `json:"description"` + Cluster ClusterQueueValuesCluster `json:"cluster"` +} + +// GetId returns ClusterQueueValues.Id, and is useful for accessing the field via an interface. +func (v *ClusterQueueValues) GetId() string { return v.Id } + +// GetUuid returns ClusterQueueValues.Uuid, and is useful for accessing the field via an interface. +func (v *ClusterQueueValues) GetUuid() string { return v.Uuid } + +// GetKey returns ClusterQueueValues.Key, and is useful for accessing the field via an interface. +func (v *ClusterQueueValues) GetKey() string { return v.Key } + +// GetDescription returns ClusterQueueValues.Description, and is useful for accessing the field via an interface. +func (v *ClusterQueueValues) GetDescription() string { return v.Description } + +// GetCluster returns ClusterQueueValues.Cluster, and is useful for accessing the field via an interface. +func (v *ClusterQueueValues) GetCluster() ClusterQueueValuesCluster { return v.Cluster } + +// ClusterQueueValuesCluster includes the requested fields of the GraphQL type Cluster. +type ClusterQueueValuesCluster struct { + Id string `json:"id"` + // The public UUID for this cluster + Uuid string `json:"uuid"` +} + +// GetId returns ClusterQueueValuesCluster.Id, and is useful for accessing the field via an interface. +func (v *ClusterQueueValuesCluster) GetId() string { return v.Id } + +// GetUuid returns ClusterQueueValuesCluster.Uuid, and is useful for accessing the field via an interface. +func (v *ClusterQueueValuesCluster) GetUuid() string { return v.Uuid } + // The roles a user can be within a team type GTeamMemberRole string @@ -132,6 +172,8 @@ type PipelineUpdateInput struct { // Autogenerated input type of PipelineUpdate ClusterId *string `json:"clusterId"` // Autogenerated input type of PipelineUpdate + PipelineTemplateId string `json:"pipelineTemplateId"` + // Autogenerated input type of PipelineUpdate Archived bool `json:"archived,omitempty"` // Autogenerated input type of PipelineUpdate Tags []PipelineTagInput `json:"tags"` @@ -211,6 +253,9 @@ func (v *PipelineUpdateInput) GetBuildRetentionNumber() int { return v.BuildRete // GetClusterId returns PipelineUpdateInput.ClusterId, and is useful for accessing the field via an interface. func (v *PipelineUpdateInput) GetClusterId() *string { return v.ClusterId } +// GetPipelineTemplateId returns PipelineUpdateInput.PipelineTemplateId, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetPipelineTemplateId() string { return v.PipelineTemplateId } + // GetArchived returns PipelineUpdateInput.Archived, and is useful for accessing the field via an interface. func (v *PipelineUpdateInput) GetArchived() bool { return v.Archived } @@ -270,6 +315,38 @@ func (v *__createAgentTokenInput) GetOrganizationId() string { return v.Organiza // GetDescription returns __createAgentTokenInput.Description, and is useful for accessing the field via an interface. func (v *__createAgentTokenInput) GetDescription() *string { return v.Description } +// __createClusterQueueInput is used internally by genqlient +type __createClusterQueueInput struct { + OrganizationId string `json:"organizationId"` + ClusterId string `json:"clusterId"` + Key string `json:"key"` + Description *string `json:"description"` +} + +// GetOrganizationId returns __createClusterQueueInput.OrganizationId, and is useful for accessing the field via an interface. +func (v *__createClusterQueueInput) GetOrganizationId() string { return v.OrganizationId } + +// GetClusterId returns __createClusterQueueInput.ClusterId, and is useful for accessing the field via an interface. +func (v *__createClusterQueueInput) GetClusterId() string { return v.ClusterId } + +// GetKey returns __createClusterQueueInput.Key, and is useful for accessing the field via an interface. +func (v *__createClusterQueueInput) GetKey() string { return v.Key } + +// GetDescription returns __createClusterQueueInput.Description, and is useful for accessing the field via an interface. +func (v *__createClusterQueueInput) GetDescription() *string { return v.Description } + +// __deleteClusterQueueInput is used internally by genqlient +type __deleteClusterQueueInput struct { + OrganizationId string `json:"organizationId"` + Id string `json:"id"` +} + +// GetOrganizationId returns __deleteClusterQueueInput.OrganizationId, and is useful for accessing the field via an interface. +func (v *__deleteClusterQueueInput) GetOrganizationId() string { return v.OrganizationId } + +// GetId returns __deleteClusterQueueInput.Id, and is useful for accessing the field via an interface. +func (v *__deleteClusterQueueInput) GetId() string { return v.Id } + // __getAgentTokenInput is used internally by genqlient type __getAgentTokenInput struct { Slug string `json:"slug"` @@ -278,6 +355,18 @@ type __getAgentTokenInput struct { // GetSlug returns __getAgentTokenInput.Slug, and is useful for accessing the field via an interface. func (v *__getAgentTokenInput) GetSlug() string { return v.Slug } +// __getClusterQueuesInput is used internally by genqlient +type __getClusterQueuesInput struct { + OrgSlug string `json:"orgSlug"` + Id string `json:"id"` +} + +// GetOrgSlug returns __getClusterQueuesInput.OrgSlug, and is useful for accessing the field via an interface. +func (v *__getClusterQueuesInput) GetOrgSlug() string { return v.OrgSlug } + +// GetId returns __getClusterQueuesInput.Id, and is useful for accessing the field via an interface. +func (v *__getClusterQueuesInput) GetId() string { return v.Id } + // __getOrganizationInput is used internally by genqlient type __getOrganizationInput struct { Slug string `json:"slug"` @@ -326,6 +415,22 @@ func (v *__setApiIpAddressesInput) GetOrganizationID() string { return v.Organiz // GetIpAddresses returns __setApiIpAddressesInput.IpAddresses, and is useful for accessing the field via an interface. func (v *__setApiIpAddressesInput) GetIpAddresses() string { return v.IpAddresses } +// __updateClusterQueueInput is used internally by genqlient +type __updateClusterQueueInput struct { + OrganizationId string `json:"organizationId"` + Id string `json:"id"` + Description *string `json:"description"` +} + +// GetOrganizationId returns __updateClusterQueueInput.OrganizationId, and is useful for accessing the field via an interface. +func (v *__updateClusterQueueInput) GetOrganizationId() string { return v.OrganizationId } + +// GetId returns __updateClusterQueueInput.Id, and is useful for accessing the field via an interface. +func (v *__updateClusterQueueInput) GetId() string { return v.Id } + +// GetDescription returns __updateClusterQueueInput.Description, and is useful for accessing the field via an interface. +func (v *__updateClusterQueueInput) GetDescription() *string { return v.Description } + // __updatePipelineInput is used internally by genqlient type __updatePipelineInput struct { Input PipelineUpdateInput `json:"input"` @@ -427,6 +532,141 @@ func (v *createAgentTokenResponse) GetAgentTokenCreate() createAgentTokenAgentTo return v.AgentTokenCreate } +// createClusterQueueClusterQueueCreateClusterQueueCreatePayload includes the requested fields of the GraphQL type ClusterQueueCreatePayload. +// The GraphQL type's documentation follows. +// +// Autogenerated return type of ClusterQueueCreate. +type createClusterQueueClusterQueueCreateClusterQueueCreatePayload struct { + ClusterQueue createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue `json:"clusterQueue"` +} + +// GetClusterQueue returns createClusterQueueClusterQueueCreateClusterQueueCreatePayload.ClusterQueue, and is useful for accessing the field via an interface. +func (v *createClusterQueueClusterQueueCreateClusterQueueCreatePayload) GetClusterQueue() createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue { + return v.ClusterQueue +} + +// createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue includes the requested fields of the GraphQL type ClusterQueue. +type createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue struct { + ClusterQueueValues `json:"-"` +} + +// GetId returns createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue.Id, and is useful for accessing the field via an interface. +func (v *createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue) GetId() string { + return v.ClusterQueueValues.Id +} + +// GetUuid returns createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue.Uuid, and is useful for accessing the field via an interface. +func (v *createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue) GetUuid() string { + return v.ClusterQueueValues.Uuid +} + +// GetKey returns createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue.Key, and is useful for accessing the field via an interface. +func (v *createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue) GetKey() string { + return v.ClusterQueueValues.Key +} + +// GetDescription returns createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue.Description, and is useful for accessing the field via an interface. +func (v *createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue) GetDescription() string { + return v.ClusterQueueValues.Description +} + +// GetCluster returns createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue.Cluster, and is useful for accessing the field via an interface. +func (v *createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue) GetCluster() ClusterQueueValuesCluster { + return v.ClusterQueueValues.Cluster +} + +func (v *createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue + graphql.NoUnmarshalJSON + } + firstPass.createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.ClusterQueueValues) + if err != nil { + return err + } + return nil +} + +type __premarshalcreateClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue struct { + Id string `json:"id"` + + Uuid string `json:"uuid"` + + Key string `json:"key"` + + Description string `json:"description"` + + Cluster ClusterQueueValuesCluster `json:"cluster"` +} + +func (v *createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue) __premarshalJSON() (*__premarshalcreateClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue, error) { + var retval __premarshalcreateClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue + + retval.Id = v.ClusterQueueValues.Id + retval.Uuid = v.ClusterQueueValues.Uuid + retval.Key = v.ClusterQueueValues.Key + retval.Description = v.ClusterQueueValues.Description + retval.Cluster = v.ClusterQueueValues.Cluster + return &retval, nil +} + +// createClusterQueueResponse is returned by createClusterQueue on success. +type createClusterQueueResponse struct { + // Create a cluster queue. + ClusterQueueCreate createClusterQueueClusterQueueCreateClusterQueueCreatePayload `json:"clusterQueueCreate"` +} + +// GetClusterQueueCreate returns createClusterQueueResponse.ClusterQueueCreate, and is useful for accessing the field via an interface. +func (v *createClusterQueueResponse) GetClusterQueueCreate() createClusterQueueClusterQueueCreateClusterQueueCreatePayload { + return v.ClusterQueueCreate +} + +// deleteClusterQueueClusterQueueDeleteClusterQueueDeletePayload includes the requested fields of the GraphQL type ClusterQueueDeletePayload. +// The GraphQL type's documentation follows. +// +// Autogenerated return type of ClusterQueueDelete. +type deleteClusterQueueClusterQueueDeleteClusterQueueDeletePayload struct { + // A unique identifier for the client performing the mutation. + ClientMutationId string `json:"clientMutationId"` +} + +// GetClientMutationId returns deleteClusterQueueClusterQueueDeleteClusterQueueDeletePayload.ClientMutationId, and is useful for accessing the field via an interface. +func (v *deleteClusterQueueClusterQueueDeleteClusterQueueDeletePayload) GetClientMutationId() string { + return v.ClientMutationId +} + +// deleteClusterQueueResponse is returned by deleteClusterQueue on success. +type deleteClusterQueueResponse struct { + // Delete a cluster queue. + ClusterQueueDelete deleteClusterQueueClusterQueueDeleteClusterQueueDeletePayload `json:"clusterQueueDelete"` +} + +// GetClusterQueueDelete returns deleteClusterQueueResponse.ClusterQueueDelete, and is useful for accessing the field via an interface. +func (v *deleteClusterQueueResponse) GetClusterQueueDelete() deleteClusterQueueClusterQueueDeleteClusterQueueDeletePayload { + return v.ClusterQueueDelete +} + // getAgentTokenAgentToken includes the requested fields of the GraphQL type AgentToken. // The GraphQL type's documentation follows. // @@ -457,6 +697,147 @@ type getAgentTokenResponse struct { // GetAgentToken returns getAgentTokenResponse.AgentToken, and is useful for accessing the field via an interface. func (v *getAgentTokenResponse) GetAgentToken() getAgentTokenAgentToken { return v.AgentToken } +// getClusterQueuesOrganization includes the requested fields of the GraphQL type Organization. +// The GraphQL type's documentation follows. +// +// An organization +type getClusterQueuesOrganization struct { + // Return cluster in the Organization by UUID + Cluster getClusterQueuesOrganizationCluster `json:"cluster"` +} + +// GetCluster returns getClusterQueuesOrganization.Cluster, and is useful for accessing the field via an interface. +func (v *getClusterQueuesOrganization) GetCluster() getClusterQueuesOrganizationCluster { + return v.Cluster +} + +// getClusterQueuesOrganizationCluster includes the requested fields of the GraphQL type Cluster. +type getClusterQueuesOrganizationCluster struct { + Queues getClusterQueuesOrganizationClusterQueuesClusterQueueConnection `json:"queues"` +} + +// GetQueues returns getClusterQueuesOrganizationCluster.Queues, and is useful for accessing the field via an interface. +func (v *getClusterQueuesOrganizationCluster) GetQueues() getClusterQueuesOrganizationClusterQueuesClusterQueueConnection { + return v.Queues +} + +// getClusterQueuesOrganizationClusterQueuesClusterQueueConnection includes the requested fields of the GraphQL type ClusterQueueConnection. +type getClusterQueuesOrganizationClusterQueuesClusterQueueConnection struct { + Edges []getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdge `json:"edges"` +} + +// GetEdges returns getClusterQueuesOrganizationClusterQueuesClusterQueueConnection.Edges, and is useful for accessing the field via an interface. +func (v *getClusterQueuesOrganizationClusterQueuesClusterQueueConnection) GetEdges() []getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdge { + return v.Edges +} + +// getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdge includes the requested fields of the GraphQL type ClusterQueueEdge. +type getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdge struct { + Node getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue `json:"node"` +} + +// GetNode returns getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdge.Node, and is useful for accessing the field via an interface. +func (v *getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdge) GetNode() getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue { + return v.Node +} + +// getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue includes the requested fields of the GraphQL type ClusterQueue. +type getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue struct { + ClusterQueueValues `json:"-"` +} + +// GetId returns getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue.Id, and is useful for accessing the field via an interface. +func (v *getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue) GetId() string { + return v.ClusterQueueValues.Id +} + +// GetUuid returns getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue.Uuid, and is useful for accessing the field via an interface. +func (v *getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue) GetUuid() string { + return v.ClusterQueueValues.Uuid +} + +// GetKey returns getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue.Key, and is useful for accessing the field via an interface. +func (v *getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue) GetKey() string { + return v.ClusterQueueValues.Key +} + +// GetDescription returns getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue.Description, and is useful for accessing the field via an interface. +func (v *getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue) GetDescription() string { + return v.ClusterQueueValues.Description +} + +// GetCluster returns getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue.Cluster, and is useful for accessing the field via an interface. +func (v *getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue) GetCluster() ClusterQueueValuesCluster { + return v.ClusterQueueValues.Cluster +} + +func (v *getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue + graphql.NoUnmarshalJSON + } + firstPass.getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.ClusterQueueValues) + if err != nil { + return err + } + return nil +} + +type __premarshalgetClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue struct { + Id string `json:"id"` + + Uuid string `json:"uuid"` + + Key string `json:"key"` + + Description string `json:"description"` + + Cluster ClusterQueueValuesCluster `json:"cluster"` +} + +func (v *getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue) __premarshalJSON() (*__premarshalgetClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue, error) { + var retval __premarshalgetClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue + + retval.Id = v.ClusterQueueValues.Id + retval.Uuid = v.ClusterQueueValues.Uuid + retval.Key = v.ClusterQueueValues.Key + retval.Description = v.ClusterQueueValues.Description + retval.Cluster = v.ClusterQueueValues.Cluster + return &retval, nil +} + +// getClusterQueuesResponse is returned by getClusterQueues on success. +type getClusterQueuesResponse struct { + // Find an organization + Organization getClusterQueuesOrganization `json:"organization"` +} + +// GetOrganization returns getClusterQueuesResponse.Organization, and is useful for accessing the field via an interface. +func (v *getClusterQueuesResponse) GetOrganization() getClusterQueuesOrganization { + return v.Organization +} + // getOrganizationOrganization includes the requested fields of the GraphQL type Organization. // The GraphQL type's documentation follows. // @@ -702,6 +1083,116 @@ func (v *setApiIpAddressesResponse) GetOrganizationApiIpAllowlistUpdate() setApi return v.OrganizationApiIpAllowlistUpdate } +// updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayload includes the requested fields of the GraphQL type ClusterQueueUpdatePayload. +// The GraphQL type's documentation follows. +// +// Autogenerated return type of ClusterQueueUpdate. +type updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayload struct { + ClusterQueue updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue `json:"clusterQueue"` +} + +// GetClusterQueue returns updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayload.ClusterQueue, and is useful for accessing the field via an interface. +func (v *updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayload) GetClusterQueue() updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue { + return v.ClusterQueue +} + +// updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue includes the requested fields of the GraphQL type ClusterQueue. +type updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue struct { + ClusterQueueValues `json:"-"` +} + +// GetId returns updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue.Id, and is useful for accessing the field via an interface. +func (v *updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue) GetId() string { + return v.ClusterQueueValues.Id +} + +// GetUuid returns updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue.Uuid, and is useful for accessing the field via an interface. +func (v *updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue) GetUuid() string { + return v.ClusterQueueValues.Uuid +} + +// GetKey returns updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue.Key, and is useful for accessing the field via an interface. +func (v *updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue) GetKey() string { + return v.ClusterQueueValues.Key +} + +// GetDescription returns updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue.Description, and is useful for accessing the field via an interface. +func (v *updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue) GetDescription() string { + return v.ClusterQueueValues.Description +} + +// GetCluster returns updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue.Cluster, and is useful for accessing the field via an interface. +func (v *updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue) GetCluster() ClusterQueueValuesCluster { + return v.ClusterQueueValues.Cluster +} + +func (v *updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue + graphql.NoUnmarshalJSON + } + firstPass.updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.ClusterQueueValues) + if err != nil { + return err + } + return nil +} + +type __premarshalupdateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue struct { + Id string `json:"id"` + + Uuid string `json:"uuid"` + + Key string `json:"key"` + + Description string `json:"description"` + + Cluster ClusterQueueValuesCluster `json:"cluster"` +} + +func (v *updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue) __premarshalJSON() (*__premarshalupdateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue, error) { + var retval __premarshalupdateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue + + retval.Id = v.ClusterQueueValues.Id + retval.Uuid = v.ClusterQueueValues.Uuid + retval.Key = v.ClusterQueueValues.Key + retval.Description = v.ClusterQueueValues.Description + retval.Cluster = v.ClusterQueueValues.Cluster + return &retval, nil +} + +// updateClusterQueueResponse is returned by updateClusterQueue on success. +type updateClusterQueueResponse struct { + // Updates a cluster queue. + ClusterQueueUpdate updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayload `json:"clusterQueueUpdate"` +} + +// GetClusterQueueUpdate returns updateClusterQueueResponse.ClusterQueueUpdate, and is useful for accessing the field via an interface. +func (v *updateClusterQueueResponse) GetClusterQueueUpdate() updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayload { + return v.ClusterQueueUpdate +} + // updatePipelinePipelineUpdatePipelineUpdatePayload includes the requested fields of the GraphQL type PipelineUpdatePayload. // The GraphQL type's documentation follows. // @@ -1098,6 +1589,94 @@ func createAgentToken( return &data, err } +// The query or mutation executed by createClusterQueue. +const createClusterQueue_Operation = ` +mutation createClusterQueue ($organizationId: ID!, $clusterId: ID!, $key: String!, $description: String) { + clusterQueueCreate(input: {organizationId:$organizationId,clusterId:$clusterId,key:$key,description:$description}) { + clusterQueue { + ... ClusterQueueValues + } + } +} +fragment ClusterQueueValues on ClusterQueue { + id + uuid + key + description + cluster { + id + uuid + } +} +` + +func createClusterQueue( + client graphql.Client, + organizationId string, + clusterId string, + key string, + description *string, +) (*createClusterQueueResponse, error) { + req := &graphql.Request{ + OpName: "createClusterQueue", + Query: createClusterQueue_Operation, + Variables: &__createClusterQueueInput{ + OrganizationId: organizationId, + ClusterId: clusterId, + Key: key, + Description: description, + }, + } + var err error + + var data createClusterQueueResponse + resp := &graphql.Response{Data: &data} + + err = client.MakeRequest( + nil, + req, + resp, + ) + + return &data, err +} + +// The query or mutation executed by deleteClusterQueue. +const deleteClusterQueue_Operation = ` +mutation deleteClusterQueue ($organizationId: ID!, $id: ID!) { + clusterQueueDelete(input: {organizationId:$organizationId,id:$id}) { + clientMutationId + } +} +` + +func deleteClusterQueue( + client graphql.Client, + organizationId string, + id string, +) (*deleteClusterQueueResponse, error) { + req := &graphql.Request{ + OpName: "deleteClusterQueue", + Query: deleteClusterQueue_Operation, + Variables: &__deleteClusterQueueInput{ + OrganizationId: organizationId, + Id: id, + }, + } + var err error + + var data deleteClusterQueueResponse + resp := &graphql.Response{Data: &data} + + err = client.MakeRequest( + nil, + req, + resp, + ) + + return &data, err +} + // The query or mutation executed by getAgentToken. const getAgentToken_Operation = ` query getAgentToken ($slug: ID!) { @@ -1134,6 +1713,60 @@ func getAgentToken( return &data, err } +// The query or mutation executed by getClusterQueues. +const getClusterQueues_Operation = ` +query getClusterQueues ($orgSlug: ID!, $id: ID!) { + organization(slug: $orgSlug) { + cluster(id: $id) { + queues(first: 50) { + edges { + node { + ... ClusterQueueValues + } + } + } + } + } +} +fragment ClusterQueueValues on ClusterQueue { + id + uuid + key + description + cluster { + id + uuid + } +} +` + +func getClusterQueues( + client graphql.Client, + orgSlug string, + id string, +) (*getClusterQueuesResponse, error) { + req := &graphql.Request{ + OpName: "getClusterQueues", + Query: getClusterQueues_Operation, + Variables: &__getClusterQueuesInput{ + OrgSlug: orgSlug, + Id: id, + }, + } + var err error + + var data getClusterQueuesResponse + resp := &graphql.Response{Data: &data} + + err = client.MakeRequest( + nil, + req, + resp, + ) + + return &data, err +} + // The query or mutation executed by getOrganization. const getOrganization_Operation = ` query getOrganization ($slug: ID!) { @@ -1332,6 +1965,56 @@ func setApiIpAddresses( return &data, err } +// The query or mutation executed by updateClusterQueue. +const updateClusterQueue_Operation = ` +mutation updateClusterQueue ($organizationId: ID!, $id: ID!, $description: String) { + clusterQueueUpdate(input: {organizationId:$organizationId,id:$id,description:$description}) { + clusterQueue { + ... ClusterQueueValues + } + } +} +fragment ClusterQueueValues on ClusterQueue { + id + uuid + key + description + cluster { + id + uuid + } +} +` + +func updateClusterQueue( + client graphql.Client, + organizationId string, + id string, + description *string, +) (*updateClusterQueueResponse, error) { + req := &graphql.Request{ + OpName: "updateClusterQueue", + Query: updateClusterQueue_Operation, + Variables: &__updateClusterQueueInput{ + OrganizationId: organizationId, + Id: id, + Description: description, + }, + } + var err error + + var data updateClusterQueueResponse + resp := &graphql.Response{Data: &data} + + err = client.MakeRequest( + nil, + req, + resp, + ) + + return &data, err +} + // The query or mutation executed by updatePipeline. const updatePipeline_Operation = ` mutation updatePipeline ($input: PipelineUpdateInput!) { diff --git a/buildkite/graphql/cluster_queue.graphql b/buildkite/graphql/cluster_queue.graphql new file mode 100644 index 00000000..4f07c1a4 --- /dev/null +++ b/buildkite/graphql/cluster_queue.graphql @@ -0,0 +1,78 @@ +fragment ClusterQueueValues on ClusterQueue { + id + uuid + key + description + cluster { + id + uuid + } +} + +query getClusterQueues($orgSlug: ID!, $id: ID!) { + organization(slug: $orgSlug) { + cluster(id: $id) { + queues(first: 50) { + edges { + node { + ...ClusterQueueValues + } + } + } + } + } +} + +mutation createClusterQueue( + $organizationId: ID!, + $clusterId: ID!, + $key: String!, + # @genqlient(pointer: true) + $description: String +) { + clusterQueueCreate( + input: { + organizationId: $organizationId + clusterId: $clusterId, + key: $key + description: $description + } + ) { + clusterQueue { + ...ClusterQueueValues + } + } +} + +mutation updateClusterQueue( + $organizationId: ID!, + $id: ID!, + # @genqlient(pointer: true) + $description: String +) { + clusterQueueUpdate( + input: { + organizationId: $organizationId + id: $id + description: $description + } + ) { + clusterQueue { + ...ClusterQueueValues + } + } +} + +mutation deleteClusterQueue( + $organizationId: ID!, + $id: ID!, +){ + clusterQueueDelete( + input: { + organizationId: $organizationId + id: $id + } + ) { + clientMutationId + } +} \ No newline at end of file diff --git a/buildkite/provider.go b/buildkite/provider.go index 54beb2dc..c3cc2273 100644 --- a/buildkite/provider.go +++ b/buildkite/provider.go @@ -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, + NewClusterQueueResource, } } diff --git a/buildkite/resource_cluster_queue.go b/buildkite/resource_cluster_queue.go new file mode 100644 index 00000000..e9790b14 --- /dev/null +++ b/buildkite/resource_cluster_queue.go @@ -0,0 +1,210 @@ +package buildkite + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + resource_schema "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type ClusterQueueResourceModel struct { + Id types.String `tfsdk:"id"` + Uuid types.String `tfsdk:"uuid"` + ClusterId types.String `tfsdk:"cluster_id"` + ClusterUuid types.String `tfsdk:"cluster_uuid"` + Key types.String `tfsdk:"key"` + Description types.String `tfsdk:"description"` +} + +type ClusterQueueResource struct { + client *Client +} + +func NewClusterQueueResource() resource.Resource { + return &ClusterQueueResource{} +} + +func (ClusterQueueResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_cluster_queue" +} + +func (cq *ClusterQueueResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + cq.client = req.ProviderData.(*Client) +} + +func (ClusterQueueResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = resource_schema.Schema{ + MarkdownDescription: "A Cluster Queue is a queue belonging to a specific Cluster for its Agents to target builds on. ", + 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(), + }, + }, + "cluster_uuid": resource_schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "cluster_id": resource_schema.StringAttribute{ + Required: true, + MarkdownDescription: "The ID of the Cluster that this Cluster Queue belongs to.", + }, + "key": resource_schema.StringAttribute{ + Required: true, + MarkdownDescription: "The key of the Cluster Queue.", + }, + "description": resource_schema.StringAttribute{ + Optional: true, + MarkdownDescription: "A description for the Cluster Queue. ", + }, + }, + } +} + +func (cq *ClusterQueueResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan, state ClusterQueueResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + + if resp.Diagnostics.HasError() { + return + } + + apiResponse, err := createClusterQueue( + cq.client.genqlient, + cq.client.organizationId, + plan.ClusterId.ValueString(), + plan.Key.ValueString(), + plan.Description.ValueStringPointer(), + ) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to create Cluster Queue", + fmt.Sprintf("Unable to create Cluster Queue: %s", err.Error()), + ) + return + } + + state.Id = types.StringValue(apiResponse.ClusterQueueCreate.ClusterQueue.Id) + state.Uuid = types.StringValue(apiResponse.ClusterQueueCreate.ClusterQueue.Uuid) + state.ClusterId = plan.ClusterId + state.ClusterUuid = types.StringValue(apiResponse.ClusterQueueCreate.ClusterQueue.Cluster.Uuid) + state.Key = types.StringValue(apiResponse.ClusterQueueCreate.ClusterQueue.Key) + state.Description = types.StringPointerValue(&apiResponse.ClusterQueueCreate.ClusterQueue.Description) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (cq *ClusterQueueResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state ClusterQueueResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + + if resp.Diagnostics.HasError() { + return + } + + queues, err := getClusterQueues(cq.client.genqlient, cq.client.organization, state.ClusterUuid.ValueString()) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to read Cluster Queue", + fmt.Sprintf("Unable to read Cluster Queue: %s", err.Error()), + ) + return + } + + // Find the Cluster Q from the returned queues to update state + for i := range queues.Organization.Cluster.Queues.Edges { + if queues.Organization.Cluster.Queues.Edges[i].Node.Id == state.Id.ValueString(){ + // Update ClusterQueueResourceModel with Node values and append + updateClusterQueueResource(queues.Organization.Cluster.Queues.Edges[i].Node, &state) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + break + } + } +} + +func (cq *ClusterQueueResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var state ClusterQueueResourceModel + var description string + + //Load state and ontain description from plan (singularly) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("description"), &description)...) + + if resp.Diagnostics.HasError() { + return + } + + apiResponse, err := updateClusterQueue( + cq.client.genqlient, + cq.client.organizationId, + state.Id.ValueString(), + &description, + ) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to update Cluster Queue", + fmt.Sprintf("Unable to update Cluster Queue: %s", err.Error()), + ) + return + } + + state.Description = types.StringPointerValue(&apiResponse.ClusterQueueUpdate.ClusterQueue.Description) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (cq *ClusterQueueResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var plan ClusterQueueResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &plan)...) + + if resp.Diagnostics.HasError() { + return + } + + _, err := deleteClusterQueue( + cq.client.genqlient, + cq.client.organizationId, + plan.Id.ValueString(), + ) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to create Cluster Queue", + fmt.Sprintf("Unable to delete Cluster Queue: %s", err.Error()), + ) + return + } +} + +func updateClusterQueueResource(clusterQueueNode getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue, cq *ClusterQueueResourceModel){ + cq.Id = types.StringValue(clusterQueueNode.Id) + cq.Uuid = types.StringValue(clusterQueueNode.Uuid) + cq.Key = types.StringValue(clusterQueueNode.Key) + cq.Description = types.StringPointerValue(&clusterQueueNode.Description) + cq.ClusterId = types.StringValue(clusterQueueNode.Cluster.Id) + cq.ClusterUuid = types.StringValue(clusterQueueNode.Cluster.Uuid) +} \ No newline at end of file diff --git a/buildkite/resource_cluster_queue_test.go b/buildkite/resource_cluster_queue_test.go new file mode 100644 index 00000000..077789de --- /dev/null +++ b/buildkite/resource_cluster_queue_test.go @@ -0,0 +1,68 @@ +package buildkite + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func testAccClusterQueueConfigBasic(name string) string { + config := ` + + resource "buildkite_cluster" "clusterfoo" { + description = "Acceptance Test Cluster" + } + + resource "buildkite_cluster_queue" "queuebar" { + cluster_id = buildkite_cluster.clusterfoo.id + description = "Acceptance Test %s" + key: "queuebar" + } + ` + + return fmt.Sprintf(config, name) +} + +// Confirm that we can create a new Cluster Queue, and then delete it without error +func TestAccClusterQueue_add_remove(t *testing.T) { + t.Parallel() + var cq ClusterQueueResourceModel + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: protoV6ProviderFactories(), + CheckDestroy: testAccCheckClusterQueueDestroy, + Steps: []resource.TestStep{ + { + Config: testAccClusterQueueConfigBasic("queuebar"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckClusterQueueRemoteValues(&cq, "Acceptance Test queuebar"), + ), + }, + { + RefreshState: true, + PlanOnly: true, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("buildkite_cluster_queue.queuebar", "key"), + resource.TestCheckResourceAttrSet("buildkite_cluster_queue.queuebar", "description"), + ), + }, + }, + }) +} + +func testAccCheckClusterQueueRemoteValues(cq *ClusterQueueResourceModel, description string) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if string(cq.Description.ValueString()) != description { + return fmt.Errorf("Unexpected description: %s", cq.Description) + } + return nil + } +} + +func testAccCheckClusterQueueDestroy(s *terraform.State) error { + return nil +} \ No newline at end of file diff --git a/schema.graphql b/schema.graphql index d97df833..975e9d95 100644 --- a/schema.graphql +++ b/schema.graphql @@ -474,6 +474,9 @@ enum AuditEventType { PIPELINE_SCHEDULE_CREATED PIPELINE_SCHEDULE_DELETED PIPELINE_SCHEDULE_UPDATED + PIPELINE_TEMPLATE_CREATED + PIPELINE_TEMPLATE_DELETED + PIPELINE_TEMPLATE_UPDATED PIPELINE_UPDATED PIPELINE_VISIBILITY_CHANGED PIPELINE_WEBHOOK_URL_ROTATED @@ -542,37 +545,38 @@ type AuditSubject { } """Kinds of subjects which can have audit events performed on them""" -union AuditSubjectNode =APIAccessToken | AgentToken | AuthorizationBitbucket | AuthorizationGitHub | AuthorizationGitHubEnterprise | Cluster | ClusterPermission | ClusterQueue | ClusterToken | Email | NotificationServiceSlack | NotificationServiceWebhook | Organization | OrganizationInvitation | OrganizationMember | Pipeline | PipelineSchedule | SCMPipelineSettings | SCMRepositoryHost | SCMService | SSOProviderGitHubApp | SSOProviderGoogleGSuite | SSOProviderSAML | Subscription | Suite | TOTP | Team | TeamMember | TeamPipeline | TeamSuite | User +union AuditSubjectNode =APIAccessToken | AgentToken | AuthorizationBitbucket | AuthorizationGitHub | AuthorizationGitHubEnterprise | Cluster | ClusterPermission | ClusterQueue | ClusterToken | Email | NotificationServiceSlack | NotificationServiceWebhook | Organization | OrganizationInvitation | OrganizationMember | Pipeline | PipelineSchedule | PipelineTemplate | SCMPipelineSettings | SCMRepositoryHost | SCMService | SSOProviderGitHubApp | SSOProviderGoogleGSuite | SSOProviderSAML | Subscription | Suite | TOTP | Team | TeamMember | TeamPipeline | TeamSuite | User """All the possible types of subjects in an Audit Event""" enum AuditSubjectType { + TEAM_PIPELINE + TEAM_SUITE + SCM_SERVICE + SCM_PIPELINE_SETTINGS + SCM_REPOSITORY_HOST + PIPELINE + SSO_PROVIDER + ORGANIZATION_MEMBER + SUITE_MONITOR + SUITE USER_EMAIL + USER_TOTP USER CLUSTER + AUTHORIZATION + SUBSCRIPTION + CLUSTER_PERMISSION AGENT_TOKEN API_ACCESS_TOKEN CLUSTER_QUEUE - AUTHORIZATION CLUSTER_TOKEN - ORGANIZATION - CLUSTER_PERMISSION NOTIFICATION_SERVICE - PIPELINE_SCHEDULE + ORGANIZATION ORGANIZATION_INVITATION - TEAM_MEMBER + PIPELINE_SCHEDULE + PIPELINE_TEMPLATE TEAM - TEAM_SUITE - TEAM_PIPELINE - SCM_PIPELINE_SETTINGS - SCM_SERVICE - SUITE_MONITOR - SCM_REPOSITORY_HOST - PIPELINE - SUITE - SUBSCRIPTION - USER_TOTP - SSO_PROVIDER - ORGANIZATION_MEMBER + TEAM_MEMBER } """Context for an audit event created during a web request""" @@ -3407,6 +3411,7 @@ type Pipeline implements Node{ nextBuildNumber: Int! organization: Organization! permissions: PipelinePermissions! + pipelineTemplate: PipelineTemplate """Whether this pipeline is visible to everyone, including people outside this organization""" public: Boolean! """The repository for this pipeline""" @@ -3499,7 +3504,7 @@ input PipelineCreateInput { """Autogenerated input type of PipelineCreate""" repository: PipelineRepositoryInput! """Autogenerated input type of PipelineCreate""" - steps: PipelineStepsInput! + steps: PipelineStepsInput """Autogenerated input type of PipelineCreate""" skipIntermediateBuilds: Boolean """Autogenerated input type of PipelineCreate""" @@ -3528,6 +3533,8 @@ input PipelineCreateInput { nextBuildNumber: Int """Autogenerated input type of PipelineCreate""" clusterId: ID +"""Autogenerated input type of PipelineCreate""" + pipelineTemplateId: ID """Autogenerated input type of PipelineCreate""" tags: [PipelineTagInput!] """Autogenerated input type of PipelineCreate""" @@ -3542,6 +3549,7 @@ type PipelineCreatePayload { organization: Organization! pipeline: Pipeline! pipelineEdge: PipelineEdge! + pipelineTemplate: PipelineTemplate } """Autogenerated input type of PipelineCreateWebhook""" @@ -3838,6 +3846,27 @@ input PipelineTeamAssignmentInput { accessLevel: PipelineAccessLevels } +"""A template defining a fixed step configuration for a pipeline""" +type PipelineTemplate implements Node{ +"""A YAML representation of the step configuration""" + configuration: YAML! +"""The time when the template was created""" + createdAt: DateTime! +"""The user who created the template""" + createdBy: User! +"""The short description of the template""" + description: String + id: ID! +"""The name of the template""" + name: String! +"""The last time the template was changed""" + updatedAt: DateTime! +"""The user who last updated the template""" + updatedBy: User! +"""The UUID for the template""" + uuid: ID! +} + """Autogenerated input type of PipelineUnarchive""" input PipelineUnarchiveInput { """Autogenerated input type of PipelineUnarchive""" @@ -3899,6 +3928,8 @@ input PipelineUpdateInput { buildRetentionNumber: Int """Autogenerated input type of PipelineUpdate""" clusterId: ID +"""Autogenerated input type of PipelineUpdate""" + pipelineTemplateId: ID """Autogenerated input type of PipelineUpdate""" archived: Boolean """Autogenerated input type of PipelineUpdate""" From bc1ca94c5eff017de63a3f587d901a913f1ff2fd Mon Sep 17 00:00:00 2001 From: James Sammut Date: Fri, 30 Jun 2023 12:23:00 +1000 Subject: [PATCH 02/12] Formatting, Schema roll-back pre-template inputs, Create/Update CQ tests --- buildkite/resource_cluster_queue.go | 24 ++-- buildkite/resource_cluster_queue_test.go | 145 ++++++++++++++++++++--- schema.graphql | 69 +++-------- 3 files changed, 160 insertions(+), 78 deletions(-) diff --git a/buildkite/resource_cluster_queue.go b/buildkite/resource_cluster_queue.go index e9790b14..399a7d6f 100644 --- a/buildkite/resource_cluster_queue.go +++ b/buildkite/resource_cluster_queue.go @@ -6,19 +6,19 @@ import ( "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" - resource_schema "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/types" ) type ClusterQueueResourceModel struct { - Id types.String `tfsdk:"id"` - Uuid types.String `tfsdk:"uuid"` - ClusterId types.String `tfsdk:"cluster_id"` - ClusterUuid types.String `tfsdk:"cluster_uuid"` - Key types.String `tfsdk:"key"` - Description types.String `tfsdk:"description"` + Id types.String `tfsdk:"id"` + Uuid types.String `tfsdk:"uuid"` + ClusterId types.String `tfsdk:"cluster_id"` + ClusterUuid types.String `tfsdk:"cluster_uuid"` + Key types.String `tfsdk:"key"` + Description types.String `tfsdk:"description"` } type ClusterQueueResource struct { @@ -52,13 +52,13 @@ func (ClusterQueueResource) Schema(ctx context.Context, req resource.SchemaReque }, }, "uuid": resource_schema.StringAttribute{ - Computed: true, + Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, }, "cluster_uuid": resource_schema.StringAttribute{ - Computed: true, + Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, @@ -135,7 +135,7 @@ func (cq *ClusterQueueResource) Read(ctx context.Context, req resource.ReadReque // Find the Cluster Q from the returned queues to update state for i := range queues.Organization.Cluster.Queues.Edges { - if queues.Organization.Cluster.Queues.Edges[i].Node.Id == state.Id.ValueString(){ + if queues.Organization.Cluster.Queues.Edges[i].Node.Id == state.Id.ValueString() { // Update ClusterQueueResourceModel with Node values and append updateClusterQueueResource(queues.Organization.Cluster.Queues.Edges[i].Node, &state) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) @@ -200,11 +200,11 @@ func (cq *ClusterQueueResource) Delete(ctx context.Context, req resource.DeleteR } } -func updateClusterQueueResource(clusterQueueNode getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue, cq *ClusterQueueResourceModel){ +func updateClusterQueueResource(clusterQueueNode getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue, cq *ClusterQueueResourceModel) { cq.Id = types.StringValue(clusterQueueNode.Id) cq.Uuid = types.StringValue(clusterQueueNode.Uuid) cq.Key = types.StringValue(clusterQueueNode.Key) cq.Description = types.StringPointerValue(&clusterQueueNode.Description) cq.ClusterId = types.StringValue(clusterQueueNode.Cluster.Id) cq.ClusterUuid = types.StringValue(clusterQueueNode.Cluster.Uuid) -} \ No newline at end of file +} diff --git a/buildkite/resource_cluster_queue_test.go b/buildkite/resource_cluster_queue_test.go index 077789de..d37e9cb3 100644 --- a/buildkite/resource_cluster_queue_test.go +++ b/buildkite/resource_cluster_queue_test.go @@ -1,6 +1,7 @@ package buildkite import ( + // "context" "fmt" "testing" @@ -11,21 +12,17 @@ import ( func testAccClusterQueueConfigBasic(name string) string { config := ` - resource "buildkite_cluster" "clusterfoo" { - description = "Acceptance Test Cluster" - } - - resource "buildkite_cluster_queue" "queuebar" { - cluster_id = buildkite_cluster.clusterfoo.id + resource "buildkite_cluster_queue" "foobar" { + cluster_id = "Q2x1c3Rlci0tLTMzMDc1ZDhiLTMyMjctNDRkYS05YTk3LTkwN2E2NWZjOGFiNg==" description = "Acceptance Test %s" - key: "queuebar" + key = "foobar" } ` return fmt.Sprintf(config, name) } -// Confirm that we can create a new Cluster Queue, and then delete it without error +// Confirm that we can create a new cluster queue, and then delete it without error func TestAccClusterQueue_add_remove(t *testing.T) { t.Parallel() var cq ClusterQueueResourceModel @@ -36,33 +33,149 @@ func TestAccClusterQueue_add_remove(t *testing.T) { CheckDestroy: testAccCheckClusterQueueDestroy, Steps: []resource.TestStep{ { - Config: testAccClusterQueueConfigBasic("queuebar"), + Config: testAccClusterQueueConfigBasic("foo"), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckClusterQueueRemoteValues(&cq, "Acceptance Test queuebar"), + // Confirm the cluster queue exists in the buildkite API + testAccCheckClusterQueueExists("buildkite_cluster_queue.foobar", &cq), + // Confirm the cluster queue has the correct values in Buildkite's system + testAccCheckClusterQueueRemoteValues(&cq, "Acceptance Test foo", "foobar"), + // Confirm the cluster queue has the correct values in terraform state + resource.TestCheckResourceAttr("buildkite_cluster_queue.foobar", "key", "foobar"), + resource.TestCheckResourceAttr("buildkite_cluster_queue.foobar", "description", "Acceptance Test foo"), + resource.TestCheckResourceAttrSet("buildkite_cluster_queue.foobar", "id"), + resource.TestCheckResourceAttrSet("buildkite_cluster_queue.foobar", "uuid"), ), }, { RefreshState: true, PlanOnly: true, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet("buildkite_cluster_queue.queuebar", "key"), - resource.TestCheckResourceAttrSet("buildkite_cluster_queue.queuebar", "description"), + resource.TestCheckResourceAttrSet("buildkite_cluster_queue.foobar", "key"), + resource.TestCheckResourceAttrSet("buildkite_cluster_queue.foobar", "description"), + ), + }, + }, + }) +} + +func TestAccClusterQueue_update(t *testing.T) { + t.Parallel() + var cq ClusterQueueResourceModel + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: protoV6ProviderFactories(), + CheckDestroy: testAccCheckClusterQueueDestroy, + Steps: []resource.TestStep{ + { + Config: testAccClusterQueueConfigBasic("foo"), + Check: resource.ComposeAggregateTestCheckFunc( + // Confirm the cluster queue exists in the buildkite API + testAccCheckClusterQueueExists("buildkite_cluster_queue.foobar", &cq), + // Confirm the cluster queue has the correct values in Buildkite's system + testAccCheckClusterQueueRemoteValues(&cq, "Acceptance Test foo", "foobar"), + // Confirm the cluster queue has the correct values in terraform state + resource.TestCheckResourceAttr("buildkite_cluster_queue.foobar", "description", "Acceptance Test foo"), + ), + }, + { + Config: testAccClusterQueueConfigBasic("bar"), + Check: resource.ComposeAggregateTestCheckFunc( + // Confirm the cluster queue exists in the buildkite API + testAccCheckClusterQueueExists("buildkite_cluster_queue.foobar", &cq), + // Confirm the cluster queue has the correct values in Buildkite's system + testAccCheckClusterQueueRemoteValues(&cq, "Acceptance Test bar", "foobar"), + // Confirm the cluster queue has the correct values in terraform state + resource.TestCheckResourceAttr("buildkite_cluster_queue.foobar", "description", "Acceptance Test bar"), ), }, }, }) } -func testAccCheckClusterQueueRemoteValues(cq *ClusterQueueResourceModel, description string) resource.TestCheckFunc { +func testAccCheckClusterQueueExists(resourceName string, clusterQueueResourceModel *ClusterQueueResourceModel) resource.TestCheckFunc { + return func(s *terraform.State) error { + resourceState, ok := s.RootModule().Resources[resourceName] + + if !ok { + return fmt.Errorf("Not found in state: %s", resourceName) + } + + if resourceState.Primary.ID == "" { + return fmt.Errorf("No ID is set in state") + } + + // Obtain queues of the queue's cluster from its cluster UUID + queues, err := getClusterQueues( + genqlientGraphql, + getenv("BUILDKITE_ORGANIZATION_SLUG"), + resourceState.Primary.Attributes["cluster_uuid"], + ) + + // If cluster queues were not able to be fetched by Genqlient + if err != nil { + return fmt.Errorf("Error fetching Cluster queues from graphql API: %v", err) + } + + // Obtain the ClusterQueueResourceModel from the queues slice + for i := range queues.Organization.Cluster.Queues.Edges { + if queues.Organization.Cluster.Queues.Edges[i].Node.Id == resourceState.Primary.ID { + // Update ClusterQueueResourceModel with Node values and append + updateClusterQueueResource(queues.Organization.Cluster.Queues.Edges[i].Node, clusterQueueResourceModel) + break + } + } + + // If clusterQueueResourceModel isnt set from the queues slice + if clusterQueueResourceModel.Id.ValueString() == "" { + return fmt.Errorf("No Cluster queue found with graphql id: %s", resourceState.Primary.ID) + } + + return nil + } +} + +func testAccCheckClusterQueueRemoteValues(cq *ClusterQueueResourceModel, description string, key string) resource.TestCheckFunc { return func(s *terraform.State) error { - if string(cq.Description.ValueString()) != description { - return fmt.Errorf("Unexpected description: %s", cq.Description) + if cq.Key.ValueString() != key { + return fmt.Errorf("Remote Cluster queue key (%s) doesn't match expected value (%s)", cq.Description, description) } + + if cq.Description.ValueString() != description { + return fmt.Errorf("Remote Cluster queue description (%s) doesn't match expected value (%s)", cq.Description, description) + } + return nil } } func testAccCheckClusterQueueDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "buildkite_cluster_queue" { + continue + } + + // Obtain queues of the queue's cluster from its cluster UUID + queues, err := getClusterQueues( + genqlientGraphql, + getenv("BUILDKITE_ORGANIZATION_SLUG"), + rs.Primary.Attributes["cluster_uuid"], + ) + + // If cluster queues were not able to be fetched by Genqlient + if err != nil { + return fmt.Errorf("Error fetching Cluster queues from graphql API: %v", err) + } + + for i := range queues.Organization.Cluster.Queues.Edges { + //If the cluster queue's ID matches any of the queues in the cluster, it hasnt been deleted + if queues.Organization.Cluster.Queues.Edges[i].Node.Id == rs.Primary.ID { + return fmt.Errorf("Cluster queue still exists in cluster, expected not to find it") + } + } + + return nil + } return nil -} \ No newline at end of file +} diff --git a/schema.graphql b/schema.graphql index 975e9d95..d97df833 100644 --- a/schema.graphql +++ b/schema.graphql @@ -474,9 +474,6 @@ enum AuditEventType { PIPELINE_SCHEDULE_CREATED PIPELINE_SCHEDULE_DELETED PIPELINE_SCHEDULE_UPDATED - PIPELINE_TEMPLATE_CREATED - PIPELINE_TEMPLATE_DELETED - PIPELINE_TEMPLATE_UPDATED PIPELINE_UPDATED PIPELINE_VISIBILITY_CHANGED PIPELINE_WEBHOOK_URL_ROTATED @@ -545,38 +542,37 @@ type AuditSubject { } """Kinds of subjects which can have audit events performed on them""" -union AuditSubjectNode =APIAccessToken | AgentToken | AuthorizationBitbucket | AuthorizationGitHub | AuthorizationGitHubEnterprise | Cluster | ClusterPermission | ClusterQueue | ClusterToken | Email | NotificationServiceSlack | NotificationServiceWebhook | Organization | OrganizationInvitation | OrganizationMember | Pipeline | PipelineSchedule | PipelineTemplate | SCMPipelineSettings | SCMRepositoryHost | SCMService | SSOProviderGitHubApp | SSOProviderGoogleGSuite | SSOProviderSAML | Subscription | Suite | TOTP | Team | TeamMember | TeamPipeline | TeamSuite | User +union AuditSubjectNode =APIAccessToken | AgentToken | AuthorizationBitbucket | AuthorizationGitHub | AuthorizationGitHubEnterprise | Cluster | ClusterPermission | ClusterQueue | ClusterToken | Email | NotificationServiceSlack | NotificationServiceWebhook | Organization | OrganizationInvitation | OrganizationMember | Pipeline | PipelineSchedule | SCMPipelineSettings | SCMRepositoryHost | SCMService | SSOProviderGitHubApp | SSOProviderGoogleGSuite | SSOProviderSAML | Subscription | Suite | TOTP | Team | TeamMember | TeamPipeline | TeamSuite | User """All the possible types of subjects in an Audit Event""" enum AuditSubjectType { - TEAM_PIPELINE - TEAM_SUITE - SCM_SERVICE - SCM_PIPELINE_SETTINGS - SCM_REPOSITORY_HOST - PIPELINE - SSO_PROVIDER - ORGANIZATION_MEMBER - SUITE_MONITOR - SUITE USER_EMAIL - USER_TOTP USER CLUSTER - AUTHORIZATION - SUBSCRIPTION - CLUSTER_PERMISSION AGENT_TOKEN API_ACCESS_TOKEN CLUSTER_QUEUE + AUTHORIZATION CLUSTER_TOKEN - NOTIFICATION_SERVICE ORGANIZATION - ORGANIZATION_INVITATION + CLUSTER_PERMISSION + NOTIFICATION_SERVICE PIPELINE_SCHEDULE - PIPELINE_TEMPLATE - TEAM + ORGANIZATION_INVITATION TEAM_MEMBER + TEAM + TEAM_SUITE + TEAM_PIPELINE + SCM_PIPELINE_SETTINGS + SCM_SERVICE + SUITE_MONITOR + SCM_REPOSITORY_HOST + PIPELINE + SUITE + SUBSCRIPTION + USER_TOTP + SSO_PROVIDER + ORGANIZATION_MEMBER } """Context for an audit event created during a web request""" @@ -3411,7 +3407,6 @@ type Pipeline implements Node{ nextBuildNumber: Int! organization: Organization! permissions: PipelinePermissions! - pipelineTemplate: PipelineTemplate """Whether this pipeline is visible to everyone, including people outside this organization""" public: Boolean! """The repository for this pipeline""" @@ -3504,7 +3499,7 @@ input PipelineCreateInput { """Autogenerated input type of PipelineCreate""" repository: PipelineRepositoryInput! """Autogenerated input type of PipelineCreate""" - steps: PipelineStepsInput + steps: PipelineStepsInput! """Autogenerated input type of PipelineCreate""" skipIntermediateBuilds: Boolean """Autogenerated input type of PipelineCreate""" @@ -3533,8 +3528,6 @@ input PipelineCreateInput { nextBuildNumber: Int """Autogenerated input type of PipelineCreate""" clusterId: ID -"""Autogenerated input type of PipelineCreate""" - pipelineTemplateId: ID """Autogenerated input type of PipelineCreate""" tags: [PipelineTagInput!] """Autogenerated input type of PipelineCreate""" @@ -3549,7 +3542,6 @@ type PipelineCreatePayload { organization: Organization! pipeline: Pipeline! pipelineEdge: PipelineEdge! - pipelineTemplate: PipelineTemplate } """Autogenerated input type of PipelineCreateWebhook""" @@ -3846,27 +3838,6 @@ input PipelineTeamAssignmentInput { accessLevel: PipelineAccessLevels } -"""A template defining a fixed step configuration for a pipeline""" -type PipelineTemplate implements Node{ -"""A YAML representation of the step configuration""" - configuration: YAML! -"""The time when the template was created""" - createdAt: DateTime! -"""The user who created the template""" - createdBy: User! -"""The short description of the template""" - description: String - id: ID! -"""The name of the template""" - name: String! -"""The last time the template was changed""" - updatedAt: DateTime! -"""The user who last updated the template""" - updatedBy: User! -"""The UUID for the template""" - uuid: ID! -} - """Autogenerated input type of PipelineUnarchive""" input PipelineUnarchiveInput { """Autogenerated input type of PipelineUnarchive""" @@ -3928,8 +3899,6 @@ input PipelineUpdateInput { buildRetentionNumber: Int """Autogenerated input type of PipelineUpdate""" clusterId: ID -"""Autogenerated input type of PipelineUpdate""" - pipelineTemplateId: ID """Autogenerated input type of PipelineUpdate""" archived: Boolean """Autogenerated input type of PipelineUpdate""" From 0de34d8e4b33c2e2585bc71b8506297067a69d5a Mon Sep 17 00:00:00 2001 From: James Sammut Date: Fri, 30 Jun 2023 13:03:00 +1000 Subject: [PATCH 03/12] Added Changelog entry, docs/example entries, TFP test org Cluster --- CHANGELOG.md | 3 ++- buildkite/resource_cluster_queue_test.go | 3 +-- docs/resources/cluster_queue.md | 29 ++++++++++++++++++++++++ examples/main.tf | 7 ++++++ 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 docs/resources/cluster_queue.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ee88caa..21f8ef49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ All notable changes to this project will be documented in this file. ## Unreleased -* Support option to archive on delete [[PR #296](https://github.com/buildkite/terraform-provider-buildkite/pull/296)] +* Support option to archive on delete [[PR #296](https://github.com/buildkite/terraform-provider-buildkite/pull/296)] @mcncl +* SUP-1085: Cluster Queue resource implementation [[PR #297](https://github.com/buildkite/terraform-provider-buildkite/pull/297)] @james2791 ## [v0.19.2](https://github.com/buildkite/terraform-provider-buildkite/compare/v0.19.1...v0.19.2) * Consistent naming for environment variables [[PR #290](https://github.com/buildkite/terraform-provider-buildkite/pull/290)] @mcncl diff --git a/buildkite/resource_cluster_queue_test.go b/buildkite/resource_cluster_queue_test.go index d37e9cb3..debedc95 100644 --- a/buildkite/resource_cluster_queue_test.go +++ b/buildkite/resource_cluster_queue_test.go @@ -1,7 +1,6 @@ package buildkite import ( - // "context" "fmt" "testing" @@ -13,7 +12,7 @@ func testAccClusterQueueConfigBasic(name string) string { config := ` resource "buildkite_cluster_queue" "foobar" { - cluster_id = "Q2x1c3Rlci0tLTMzMDc1ZDhiLTMyMjctNDRkYS05YTk3LTkwN2E2NWZjOGFiNg==" + cluster_id = "Q2x1c3Rlci0tLTFkNmIxOTg5LTJmYjctNDRlMC04MWYyLTAxYjIxNzQ4MTVkMg==" description = "Acceptance Test %s" key = "foobar" } diff --git a/docs/resources/cluster_queue.md b/docs/resources/cluster_queue.md new file mode 100644 index 00000000..8f24d68a --- /dev/null +++ b/docs/resources/cluster_queue.md @@ -0,0 +1,29 @@ +# Resource: cluster_queue + +This resource allows you to create and manage cluster queues. + +Buildkite Documentation: https://buildkite.com/docs/clusters/manage-clusters#set-up-clusters-create-a-queue + +## Example Usage + +```hcl +provider "buildkite" {} + +resource "buildkite_cluster_queue" "queue1" { + cluster_id = "Q2x1c3Rlci0tLTMzMDc0ZDhiLTM4MjctNDRkNC05YTQ3LTkwN2E2NWZjODViNg==" + key = "prod-deploy" + description = "Prod deployment cluster queue" +} +``` + +## Argument Reference + +* `cluster_id` - (Required) The ID of the cluster that this cluster queue belongs to. +* `key` - (Required) The key of the cluster queue. +* `description` - (Required) This is the description of the cluster queue. + +## Attribute Reference + +* `id` - The Graphql ID of the created cluster queue. +* `uuid` - The UUID of the created cluster queue. +* `cluster_uuid` - The UUID of the cluster that this cluster queue belongs to. \ No newline at end of file diff --git a/examples/main.tf b/examples/main.tf index 81d0f366..5d34d3b9 100644 --- a/examples/main.tf +++ b/examples/main.tf @@ -25,6 +25,12 @@ resource "buildkite_agent_token" "fleet" { description = "token used by build fleet" } +resource "buildkite_cluster_queue" "queue1" { + cluster_id = "Q2x1c3Rlci0tLTMzMDc0ZDhiLTM4MjctNDRkNC05YTQ3LTkwN2E2NWZjODViNg==" + key = "dev" + description = "Dev cluster queue" +} + output "agent_token" { value = buildkite_agent_token.fleet.token } @@ -32,3 +38,4 @@ output "agent_token" { output "badge_url" { value = buildkite_pipeline.test.badge_url } + From 8dad4169da03acdbb45b4a058e5427de3e8d64d0 Mon Sep 17 00:00:00 2001 From: James Sammut Date: Fri, 30 Jun 2023 13:52:13 +1000 Subject: [PATCH 04/12] Reverted PipelineTemplateId parameter from PipelineUpdateInput --- buildkite/generated.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/buildkite/generated.go b/buildkite/generated.go index 1aa62664..d439d4d5 100644 --- a/buildkite/generated.go +++ b/buildkite/generated.go @@ -172,8 +172,6 @@ type PipelineUpdateInput struct { // Autogenerated input type of PipelineUpdate ClusterId *string `json:"clusterId"` // Autogenerated input type of PipelineUpdate - PipelineTemplateId string `json:"pipelineTemplateId"` - // Autogenerated input type of PipelineUpdate Archived bool `json:"archived,omitempty"` // Autogenerated input type of PipelineUpdate Tags []PipelineTagInput `json:"tags"` @@ -253,9 +251,6 @@ func (v *PipelineUpdateInput) GetBuildRetentionNumber() int { return v.BuildRete // GetClusterId returns PipelineUpdateInput.ClusterId, and is useful for accessing the field via an interface. func (v *PipelineUpdateInput) GetClusterId() *string { return v.ClusterId } -// GetPipelineTemplateId returns PipelineUpdateInput.PipelineTemplateId, and is useful for accessing the field via an interface. -func (v *PipelineUpdateInput) GetPipelineTemplateId() string { return v.PipelineTemplateId } - // GetArchived returns PipelineUpdateInput.Archived, and is useful for accessing the field via an interface. func (v *PipelineUpdateInput) GetArchived() bool { return v.Archived } From 61570630db4cd427d005b8a3f591c67479baf391 Mon Sep 17 00:00:00 2001 From: James Sammut Date: Mon, 3 Jul 2023 12:36:40 +1000 Subject: [PATCH 05/12] Logging, import functionality, docs, description nullable, loop logic simplification --- buildkite/generated.go | 16 +++---- buildkite/graphql/cluster_queue.graphql | 1 + buildkite/resource_cluster_queue.go | 55 +++++++++++++++++++----- buildkite/resource_cluster_queue_test.go | 13 +++--- docs/resources/cluster_queue.md | 47 +++++++++++++++++++- 5 files changed, 106 insertions(+), 26 deletions(-) diff --git a/buildkite/generated.go b/buildkite/generated.go index d439d4d5..e9056d6f 100644 --- a/buildkite/generated.go +++ b/buildkite/generated.go @@ -34,7 +34,7 @@ type ClusterQueueValues struct { // The public UUID for this cluster queue Uuid string `json:"uuid"` Key string `json:"key"` - Description string `json:"description"` + Description *string `json:"description"` Cluster ClusterQueueValuesCluster `json:"cluster"` } @@ -48,7 +48,7 @@ func (v *ClusterQueueValues) GetUuid() string { return v.Uuid } func (v *ClusterQueueValues) GetKey() string { return v.Key } // GetDescription returns ClusterQueueValues.Description, and is useful for accessing the field via an interface. -func (v *ClusterQueueValues) GetDescription() string { return v.Description } +func (v *ClusterQueueValues) GetDescription() *string { return v.Description } // GetCluster returns ClusterQueueValues.Cluster, and is useful for accessing the field via an interface. func (v *ClusterQueueValues) GetCluster() ClusterQueueValuesCluster { return v.Cluster } @@ -561,7 +561,7 @@ func (v *createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQue } // GetDescription returns createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue.Description, and is useful for accessing the field via an interface. -func (v *createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue) GetDescription() string { +func (v *createClusterQueueClusterQueueCreateClusterQueueCreatePayloadClusterQueue) GetDescription() *string { return v.ClusterQueueValues.Description } @@ -602,7 +602,7 @@ type __premarshalcreateClusterQueueClusterQueueCreateClusterQueueCreatePayloadCl Key string `json:"key"` - Description string `json:"description"` + Description *string `json:"description"` Cluster ClusterQueueValuesCluster `json:"cluster"` } @@ -757,7 +757,7 @@ func (v *getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClu } // GetDescription returns getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue.Description, and is useful for accessing the field via an interface. -func (v *getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue) GetDescription() string { +func (v *getClusterQueuesOrganizationClusterQueuesClusterQueueConnectionEdgesClusterQueueEdgeNodeClusterQueue) GetDescription() *string { return v.ClusterQueueValues.Description } @@ -798,7 +798,7 @@ type __premarshalgetClusterQueuesOrganizationClusterQueuesClusterQueueConnection Key string `json:"key"` - Description string `json:"description"` + Description *string `json:"description"` Cluster ClusterQueueValuesCluster `json:"cluster"` } @@ -1112,7 +1112,7 @@ func (v *updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQue } // GetDescription returns updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue.Description, and is useful for accessing the field via an interface. -func (v *updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue) GetDescription() string { +func (v *updateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadClusterQueue) GetDescription() *string { return v.ClusterQueueValues.Description } @@ -1153,7 +1153,7 @@ type __premarshalupdateClusterQueueClusterQueueUpdateClusterQueueUpdatePayloadCl Key string `json:"key"` - Description string `json:"description"` + Description *string `json:"description"` Cluster ClusterQueueValuesCluster `json:"cluster"` } diff --git a/buildkite/graphql/cluster_queue.graphql b/buildkite/graphql/cluster_queue.graphql index 4f07c1a4..67388cfc 100644 --- a/buildkite/graphql/cluster_queue.graphql +++ b/buildkite/graphql/cluster_queue.graphql @@ -2,6 +2,7 @@ fragment ClusterQueueValues on ClusterQueue { id uuid key + # @genqlient(pointer: true) description cluster { id diff --git a/buildkite/resource_cluster_queue.go b/buildkite/resource_cluster_queue.go index 399a7d6f..230a3872 100644 --- a/buildkite/resource_cluster_queue.go +++ b/buildkite/resource_cluster_queue.go @@ -3,6 +3,8 @@ package buildkite import ( "context" "fmt" + "log" + "strings" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -88,6 +90,7 @@ func (cq *ClusterQueueResource) Create(ctx context.Context, req resource.CreateR return } + log.Printf("Creating cluster queue with key %s into cluster %s ...", plan.Key.ValueString(), plan.ClusterId.ValueString()) apiResponse, err := createClusterQueue( cq.client.genqlient, cq.client.organizationId, @@ -109,13 +112,14 @@ func (cq *ClusterQueueResource) Create(ctx context.Context, req resource.CreateR state.ClusterId = plan.ClusterId state.ClusterUuid = types.StringValue(apiResponse.ClusterQueueCreate.ClusterQueue.Cluster.Uuid) state.Key = types.StringValue(apiResponse.ClusterQueueCreate.ClusterQueue.Key) - state.Description = types.StringPointerValue(&apiResponse.ClusterQueueCreate.ClusterQueue.Description) + state.Description = types.StringPointerValue(apiResponse.ClusterQueueCreate.ClusterQueue.Description) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } func (cq *ClusterQueueResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state ClusterQueueResourceModel + var queueFound bool = false resp.Diagnostics.Append(req.State.Get(ctx, &state)...) @@ -123,25 +127,54 @@ func (cq *ClusterQueueResource) Read(ctx context.Context, req resource.ReadReque return } + log.Printf("Getting cluster queues for cluster %s ...", state.ClusterUuid.ValueString()) queues, err := getClusterQueues(cq.client.genqlient, cq.client.organization, state.ClusterUuid.ValueString()) if err != nil { resp.Diagnostics.AddError( - "Unable to read Cluster Queue", - fmt.Sprintf("Unable to read Cluster Queue: %s", err.Error()), + "Unable to read Cluster Queues", + fmt.Sprintf("Unable to read Cluster Queues: %s", err.Error()), ) return } - // Find the Cluster Q from the returned queues to update state - for i := range queues.Organization.Cluster.Queues.Edges { - if queues.Organization.Cluster.Queues.Edges[i].Node.Id == state.Id.ValueString() { + // Find the cluster queue from the returned queues to update state + for _, edge := range queues.Organization.Cluster.Queues.Edges { + if edge.Node.Id == state.Id.ValueString() { + log.Printf("Found cluster queue with ID %s in cluster %s", edge.Node.Id, state.ClusterUuid.ValueString()) // Update ClusterQueueResourceModel with Node values and append - updateClusterQueueResource(queues.Organization.Cluster.Queues.Edges[i].Node, &state) + updateClusterQueueResource(edge.Node, &state) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) - break + return } } + + // If not returned by this point, the cluster queue could not be found + // This is a tradeoff of the current getClusterQueues Genqlient query (searches for 50 queues via the cluster UUID in state) + if !queueFound { + resp.Diagnostics.AddError( + "Unable to find Cluster Queue", + fmt.Sprintf("Unable to find Cluster Queue: %s", err.Error()), + ) + return + } +} + +func (cq *ClusterQueueResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse){ + importComponents := strings.Split(req.ID, ",") + + if len(importComponents) != 2 || importComponents[0] == "" || importComponents[1] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: id,cluster_uuid. Got: %q", req.ID), + ) + return + } + + // Adding the cluster queue ID/cluster UUID to state for Read + log.Printf("Importing cluster queue %s ...", importComponents[0]) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), importComponents[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("cluster_uuid"), importComponents[1])...) } func (cq *ClusterQueueResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { @@ -156,6 +189,7 @@ func (cq *ClusterQueueResource) Update(ctx context.Context, req resource.UpdateR return } + log.Printf("Updating cluster queue %s ...", state.Id.ValueString()) apiResponse, err := updateClusterQueue( cq.client.genqlient, cq.client.organizationId, @@ -171,7 +205,7 @@ func (cq *ClusterQueueResource) Update(ctx context.Context, req resource.UpdateR return } - state.Description = types.StringPointerValue(&apiResponse.ClusterQueueUpdate.ClusterQueue.Description) + state.Description = types.StringPointerValue(apiResponse.ClusterQueueUpdate.ClusterQueue.Description) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } @@ -185,6 +219,7 @@ func (cq *ClusterQueueResource) Delete(ctx context.Context, req resource.DeleteR return } + log.Printf("Deleting cluster queue %s ...", plan.Id.ValueString()) _, err := deleteClusterQueue( cq.client.genqlient, cq.client.organizationId, @@ -204,7 +239,7 @@ func updateClusterQueueResource(clusterQueueNode getClusterQueuesOrganizationClu cq.Id = types.StringValue(clusterQueueNode.Id) cq.Uuid = types.StringValue(clusterQueueNode.Uuid) cq.Key = types.StringValue(clusterQueueNode.Key) - cq.Description = types.StringPointerValue(&clusterQueueNode.Description) + cq.Description = types.StringPointerValue(clusterQueueNode.Description) cq.ClusterId = types.StringValue(clusterQueueNode.Cluster.Id) cq.ClusterUuid = types.StringValue(clusterQueueNode.Cluster.Uuid) } diff --git a/buildkite/resource_cluster_queue_test.go b/buildkite/resource_cluster_queue_test.go index debedc95..74dce424 100644 --- a/buildkite/resource_cluster_queue_test.go +++ b/buildkite/resource_cluster_queue_test.go @@ -117,10 +117,9 @@ func testAccCheckClusterQueueExists(resourceName string, clusterQueueResourceMod } // Obtain the ClusterQueueResourceModel from the queues slice - for i := range queues.Organization.Cluster.Queues.Edges { - if queues.Organization.Cluster.Queues.Edges[i].Node.Id == resourceState.Primary.ID { - // Update ClusterQueueResourceModel with Node values and append - updateClusterQueueResource(queues.Organization.Cluster.Queues.Edges[i].Node, clusterQueueResourceModel) + for _, edge := range queues.Organization.Cluster.Queues.Edges { + if edge.Node.Id == resourceState.Primary.ID { + updateClusterQueueResource(edge.Node, clusterQueueResourceModel) break } } @@ -167,9 +166,9 @@ func testAccCheckClusterQueueDestroy(s *terraform.State) error { return fmt.Errorf("Error fetching Cluster queues from graphql API: %v", err) } - for i := range queues.Organization.Cluster.Queues.Edges { - //If the cluster queue's ID matches any of the queues in the cluster, it hasnt been deleted - if queues.Organization.Cluster.Queues.Edges[i].Node.Id == rs.Primary.ID { + // Loop over the cluster's queues, error if the queue still exists + for _, edge := range queues.Organization.Cluster.Queues.Edges { + if edge.Node.Id == rs.Primary.ID { return fmt.Errorf("Cluster queue still exists in cluster, expected not to find it") } } diff --git a/docs/resources/cluster_queue.md b/docs/resources/cluster_queue.md index 8f24d68a..db3aeeba 100644 --- a/docs/resources/cluster_queue.md +++ b/docs/resources/cluster_queue.md @@ -26,4 +26,49 @@ resource "buildkite_cluster_queue" "queue1" { * `id` - The Graphql ID of the created cluster queue. * `uuid` - The UUID of the created cluster queue. -* `cluster_uuid` - The UUID of the cluster that this cluster queue belongs to. \ No newline at end of file +* `cluster_uuid` - The UUID of the cluster that this cluster queue belongs to. + +## Import + +Cluster queues can be imported using its `GraphQL ID`, along with its respective cluster `UUID`, sepeated by a comma. e.g. + +``` +$ terraform import buildkite_cluster_queue.test Q2x1c3RlclF1ZXVlLS0tYjJiOGRhNTEtOWY5My00Y2MyLTkyMjktMGRiNzg3ZDQzOTAz,35498aaf-ad05-4fa5-9a07-91bf6cacd2bd +``` + +To find the cluster's `UUID` to utilise, you can use the below GraphQL query below. Alternatively, you can use this [pre-saved query](https://buildkite.com/user/graphql/console/3adf0389-02bd-45ef-adcd-4e8e5ae57f25), where you will need fo fill in the organization slug (ORGANIZATION_SLUG) for obtaining the relevant cluster name/`UUID` that the cluster queue is in. + +```graphql +query getClusters { + organization(slug: "ORGANIZATION_SLUG") { + clusters(first: 50) { + edges{ + node{ + name + uuid + } + } + } + } +} +``` + +After the cluster `UUID` has been found, you can use the below GraphQL query to find the cluster queue's `GraphQL ID`. Alternatively, this [pre-saved query](https://buildkite.com/user/graphql/console/1d913905-900e-40e7-8f46-651543487b5a) can be used, specifying the organisation slug (ORGANIZATION_SLUG) and the cluster `UUID` from above (CLUSTER_UUID). + +```graphql +query getClusterQueues { + organization(slug: "ORGANIZATION_SLUG") { + cluster(id: "CLUSTER_UUID") { + queues(first: 50) { + edges { + node { + id + key + } + } + } + } + } +} +``` + From 595439eee384f25aae82b50bdb818062ef617ed8 Mon Sep 17 00:00:00 2001 From: James Sammut Date: Mon, 3 Jul 2023 13:11:23 +1000 Subject: [PATCH 06/12] Formatting, added Cluster Queue import test --- buildkite/resource_cluster_queue.go | 18 +++++----- buildkite/resource_cluster_queue_test.go | 42 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/buildkite/resource_cluster_queue.go b/buildkite/resource_cluster_queue.go index 230a3872..bb87a641 100644 --- a/buildkite/resource_cluster_queue.go +++ b/buildkite/resource_cluster_queue.go @@ -150,7 +150,7 @@ func (cq *ClusterQueueResource) Read(ctx context.Context, req resource.ReadReque } // If not returned by this point, the cluster queue could not be found - // This is a tradeoff of the current getClusterQueues Genqlient query (searches for 50 queues via the cluster UUID in state) + // This is a tradeoff of the current getClusterQueues Genqlient query (searches for 50 queues via the cluster UUID in state) if !queueFound { resp.Diagnostics.AddError( "Unable to find Cluster Queue", @@ -160,16 +160,16 @@ func (cq *ClusterQueueResource) Read(ctx context.Context, req resource.ReadReque } } -func (cq *ClusterQueueResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse){ +func (cq *ClusterQueueResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { importComponents := strings.Split(req.ID, ",") - if len(importComponents) != 2 || importComponents[0] == "" || importComponents[1] == "" { - resp.Diagnostics.AddError( - "Unexpected Import Identifier", - fmt.Sprintf("Expected import identifier with format: id,cluster_uuid. Got: %q", req.ID), - ) - return - } + if len(importComponents) != 2 || importComponents[0] == "" || importComponents[1] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: id,cluster_uuid. Got: %q", req.ID), + ) + return + } // Adding the cluster queue ID/cluster UUID to state for Read log.Printf("Importing cluster queue %s ...", importComponents[0]) diff --git a/buildkite/resource_cluster_queue_test.go b/buildkite/resource_cluster_queue_test.go index 74dce424..913fe473 100644 --- a/buildkite/resource_cluster_queue_test.go +++ b/buildkite/resource_cluster_queue_test.go @@ -2,6 +2,7 @@ package buildkite import ( "fmt" + "strings" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -92,6 +93,36 @@ func TestAccClusterQueue_update(t *testing.T) { }) } +func TestAccClusterQueue_import(t *testing.T) { + t.Parallel() + var cq ClusterQueueResourceModel + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: protoV6ProviderFactories(), + CheckDestroy: testAccCheckClusterQueueDestroy, + Steps: []resource.TestStep{ + { + Config: testAccClusterQueueConfigBasic("foo"), + Check: resource.ComposeAggregateTestCheckFunc( + // Confirm the cluster queue exists in the buildkite API + testAccCheckClusterQueueExists("buildkite_cluster_queue.foobar", &cq), + // Check to confirm the local state is correct before we re-import it + resource.TestCheckResourceAttr("buildkite_cluster_queue.foobar", "key", "foobar"), + resource.TestCheckResourceAttr("buildkite_cluster_queue.foobar", "description", "Acceptance Test foo"), + ), + }, + { + // re-import the resource (using the graphql token of the existing resource) and confirm they match + ResourceName: "buildkite_cluster_queue.foobar", + ImportStateIdFunc: testAccGetImportClusterQueueId(&cq), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckClusterQueueExists(resourceName string, clusterQueueResourceModel *ClusterQueueResourceModel) resource.TestCheckFunc { return func(s *terraform.State) error { resourceState, ok := s.RootModule().Resources[resourceName] @@ -148,6 +179,17 @@ func testAccCheckClusterQueueRemoteValues(cq *ClusterQueueResourceModel, descrip } } +func testAccGetImportClusterQueueId(cq *ClusterQueueResourceModel) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + // Obtain trimmed cluster ID and cluster UUID + clusterUuid := strings.Trim(cq.Id.ValueString(), "\"") + clusterQueueID := strings.Trim(cq.ClusterUuid.ValueString(), "\"") + // Set ID for import + id := fmt.Sprintf("%s,%s", clusterUuid, clusterQueueID) + return id, nil + } +} + func testAccCheckClusterQueueDestroy(s *terraform.State) error { for _, rs := range s.RootModule().Resources { if rs.Type != "buildkite_cluster_queue" { From 33b7e09cb2c8be5293f8cc170670c204e877e300 Mon Sep 17 00:00:00 2001 From: James Sammut Date: Mon, 3 Jul 2023 13:20:39 +1000 Subject: [PATCH 07/12] Un-parallelised Cluster queue tests --- buildkite/resource_cluster_queue_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/buildkite/resource_cluster_queue_test.go b/buildkite/resource_cluster_queue_test.go index 913fe473..49543d71 100644 --- a/buildkite/resource_cluster_queue_test.go +++ b/buildkite/resource_cluster_queue_test.go @@ -13,7 +13,7 @@ func testAccClusterQueueConfigBasic(name string) string { config := ` resource "buildkite_cluster_queue" "foobar" { - cluster_id = "Q2x1c3Rlci0tLTFkNmIxOTg5LTJmYjctNDRlMC04MWYyLTAxYjIxNzQ4MTVkMg==" + cluster_id = "Q2x1c3Rlci0tLTMzMDc1ZDhiLTMyMjctNDRkYS05YTk3LTkwN2E2NWZjOGFiNg==" description = "Acceptance Test %s" key = "foobar" } @@ -24,7 +24,6 @@ func testAccClusterQueueConfigBasic(name string) string { // Confirm that we can create a new cluster queue, and then delete it without error func TestAccClusterQueue_add_remove(t *testing.T) { - t.Parallel() var cq ClusterQueueResourceModel resource.Test(t, resource.TestCase{ @@ -59,7 +58,6 @@ func TestAccClusterQueue_add_remove(t *testing.T) { } func TestAccClusterQueue_update(t *testing.T) { - t.Parallel() var cq ClusterQueueResourceModel resource.Test(t, resource.TestCase{ @@ -94,7 +92,6 @@ func TestAccClusterQueue_update(t *testing.T) { } func TestAccClusterQueue_import(t *testing.T) { - t.Parallel() var cq ClusterQueueResourceModel resource.Test(t, resource.TestCase{ From d2c637c10d26039a4f0671fa3d4622972376b6d3 Mon Sep 17 00:00:00 2001 From: James Sammut Date: Mon, 3 Jul 2023 13:25:42 +1000 Subject: [PATCH 08/12] Revert to TF Test Org Cluster ID --- buildkite/resource_cluster_queue_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildkite/resource_cluster_queue_test.go b/buildkite/resource_cluster_queue_test.go index 49543d71..0f7ccdd5 100644 --- a/buildkite/resource_cluster_queue_test.go +++ b/buildkite/resource_cluster_queue_test.go @@ -13,7 +13,7 @@ func testAccClusterQueueConfigBasic(name string) string { config := ` resource "buildkite_cluster_queue" "foobar" { - cluster_id = "Q2x1c3Rlci0tLTMzMDc1ZDhiLTMyMjctNDRkYS05YTk3LTkwN2E2NWZjOGFiNg==" + cluster_id = "Q2x1c3Rlci0tLTFkNmIxOTg5LTJmYjctNDRlMC04MWYyLTAxYjIxNzQ4MTVkMg==" description = "Acceptance Test %s" key = "foobar" } From 4870eae38996847c9603ea3e5d46573c7a70d951 Mon Sep 17 00:00:00 2001 From: James Sammut Date: Mon, 3 Jul 2023 13:35:39 +1000 Subject: [PATCH 09/12] Docs and spelling touchup --- docs/resources/cluster_queue.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/resources/cluster_queue.md b/docs/resources/cluster_queue.md index db3aeeba..479fb123 100644 --- a/docs/resources/cluster_queue.md +++ b/docs/resources/cluster_queue.md @@ -20,7 +20,7 @@ resource "buildkite_cluster_queue" "queue1" { * `cluster_id` - (Required) The ID of the cluster that this cluster queue belongs to. * `key` - (Required) The key of the cluster queue. -* `description` - (Required) This is the description of the cluster queue. +* `description` - (Optional) The description of the cluster queue. ## Attribute Reference @@ -30,13 +30,13 @@ resource "buildkite_cluster_queue" "queue1" { ## Import -Cluster queues can be imported using its `GraphQL ID`, along with its respective cluster `UUID`, sepeated by a comma. e.g. +Cluster queues can be imported using its `GraphQL ID`, along with its respective cluster `UUID`, separated by a comma. e.g. ``` $ terraform import buildkite_cluster_queue.test Q2x1c3RlclF1ZXVlLS0tYjJiOGRhNTEtOWY5My00Y2MyLTkyMjktMGRiNzg3ZDQzOTAz,35498aaf-ad05-4fa5-9a07-91bf6cacd2bd ``` -To find the cluster's `UUID` to utilise, you can use the below GraphQL query below. Alternatively, you can use this [pre-saved query](https://buildkite.com/user/graphql/console/3adf0389-02bd-45ef-adcd-4e8e5ae57f25), where you will need fo fill in the organization slug (ORGANIZATION_SLUG) for obtaining the relevant cluster name/`UUID` that the cluster queue is in. +To find the cluster's `UUID` to utilize, you can use the below GraphQL query below. Alternatively, you can use this [pre-saved query](https://buildkite.com/user/graphql/console/3adf0389-02bd-45ef-adcd-4e8e5ae57f25), where you will need fo fill in the organization slug (ORGANIZATION_SLUG) for obtaining the relevant cluster name/`UUID` that the cluster queue is in. ```graphql query getClusters { @@ -48,12 +48,12 @@ query getClusters { uuid } } - } + } } } ``` -After the cluster `UUID` has been found, you can use the below GraphQL query to find the cluster queue's `GraphQL ID`. Alternatively, this [pre-saved query](https://buildkite.com/user/graphql/console/1d913905-900e-40e7-8f46-651543487b5a) can be used, specifying the organisation slug (ORGANIZATION_SLUG) and the cluster `UUID` from above (CLUSTER_UUID). +After the cluster `UUID` has been found, you can use the below GraphQL query to find the cluster queue's `GraphQL ID`. Alternatively, this [pre-saved query](https://buildkite.com/user/graphql/console/1d913905-900e-40e7-8f46-651543487b5a) can be used, specifying the organization slug (ORGANIZATION_SLUG) and the cluster `UUID` from above (CLUSTER_UUID). ```graphql query getClusterQueues { From cdb0a1d885d6823b42467a249141da14ee7fa230 Mon Sep 17 00:00:00 2001 From: James Sammut Date: Mon, 3 Jul 2023 14:46:46 +1000 Subject: [PATCH 10/12] queueFound removal, cleaner testAccCheckClusterQueueRemoteValues method signature --- buildkite/resource_cluster_queue.go | 13 +++++-------- buildkite/resource_cluster_queue_test.go | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/buildkite/resource_cluster_queue.go b/buildkite/resource_cluster_queue.go index bb87a641..59e6adcc 100644 --- a/buildkite/resource_cluster_queue.go +++ b/buildkite/resource_cluster_queue.go @@ -119,7 +119,6 @@ func (cq *ClusterQueueResource) Create(ctx context.Context, req resource.CreateR func (cq *ClusterQueueResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state ClusterQueueResourceModel - var queueFound bool = false resp.Diagnostics.Append(req.State.Get(ctx, &state)...) @@ -151,13 +150,11 @@ func (cq *ClusterQueueResource) Read(ctx context.Context, req resource.ReadReque // If not returned by this point, the cluster queue could not be found // This is a tradeoff of the current getClusterQueues Genqlient query (searches for 50 queues via the cluster UUID in state) - if !queueFound { - resp.Diagnostics.AddError( - "Unable to find Cluster Queue", - fmt.Sprintf("Unable to find Cluster Queue: %s", err.Error()), - ) - return - } + resp.Diagnostics.AddError( + "Unable to find Cluster Queue", + fmt.Sprintf("Unable to find Cluster Queue: %s", err.Error()), + ) + return } func (cq *ClusterQueueResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { diff --git a/buildkite/resource_cluster_queue_test.go b/buildkite/resource_cluster_queue_test.go index 0f7ccdd5..cd2aa5b2 100644 --- a/buildkite/resource_cluster_queue_test.go +++ b/buildkite/resource_cluster_queue_test.go @@ -161,7 +161,7 @@ func testAccCheckClusterQueueExists(resourceName string, clusterQueueResourceMod } } -func testAccCheckClusterQueueRemoteValues(cq *ClusterQueueResourceModel, description string, key string) resource.TestCheckFunc { +func testAccCheckClusterQueueRemoteValues(cq *ClusterQueueResourceModel, description, key string) resource.TestCheckFunc { return func(s *terraform.State) error { if cq.Key.ValueString() != key { From 6abb7846b910bef9462c390c3fd309bad9a88f6a Mon Sep 17 00:00:00 2001 From: James Sammut Date: Mon, 3 Jul 2023 16:08:56 +1000 Subject: [PATCH 11/12] Import docs getClusters query formatting --- docs/resources/cluster_queue.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/resources/cluster_queue.md b/docs/resources/cluster_queue.md index 479fb123..8c2017d1 100644 --- a/docs/resources/cluster_queue.md +++ b/docs/resources/cluster_queue.md @@ -41,14 +41,14 @@ To find the cluster's `UUID` to utilize, you can use the below GraphQL query bel ```graphql query getClusters { organization(slug: "ORGANIZATION_SLUG") { - clusters(first: 50) { + clusters(first: 50) { edges{ node{ name uuid } } - } + } } } ``` From 13b4dd91e83bb06543c5f204acc13732c319f877 Mon Sep 17 00:00:00 2001 From: James Sammut Date: Mon, 3 Jul 2023 17:12:41 +1000 Subject: [PATCH 12/12] testAccCheckClusterQueueRemoteValues Key error line fix --- buildkite/resource_cluster_queue_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildkite/resource_cluster_queue_test.go b/buildkite/resource_cluster_queue_test.go index cd2aa5b2..370bce7a 100644 --- a/buildkite/resource_cluster_queue_test.go +++ b/buildkite/resource_cluster_queue_test.go @@ -165,7 +165,7 @@ func testAccCheckClusterQueueRemoteValues(cq *ClusterQueueResourceModel, descrip return func(s *terraform.State) error { if cq.Key.ValueString() != key { - return fmt.Errorf("Remote Cluster queue key (%s) doesn't match expected value (%s)", cq.Description, description) + return fmt.Errorf("Remote Cluster queue key (%s) doesn't match expected value (%s)", cq.Key, key) } if cq.Description.ValueString() != description {