From 291999a159d95ecb007b3e046786ac4041c3e585 Mon Sep 17 00:00:00 2001 From: Jarryd Tilbrook Date: Thu, 15 Jun 2023 11:17:08 +0800 Subject: [PATCH] SUP-863 Allow pipeline to be removed from a cluster (#279) * SUP-863 Add generated code for pipeline update * SUP-863 Change ClusterId to pointer * SUP-863 Add omitempty to some fields * SUP-863 Fix removing pipeline from cluster * SUP-863 Add graphqlrc.yml * SUP-863 Update changelog with pending change * SUP-863 Add test for removing from cluster --- .graphqlrc.yml | 1 + CHANGELOG.md | 1 + buildkite/generated.go | 630 ++++++++++++++++++++++++++++ buildkite/graphql/pipeline.graphql | 59 +++ buildkite/resource_pipeline.go | 167 +++++--- buildkite/resource_pipeline_test.go | 46 +- buildkite/resource_team.go | 3 - genqlient.yaml | 4 + schema.graphql | 187 ++++++++- 9 files changed, 1018 insertions(+), 80 deletions(-) create mode 100644 .graphqlrc.yml diff --git a/.graphqlrc.yml b/.graphqlrc.yml new file mode 100644 index 00000000..9484b9ca --- /dev/null +++ b/.graphqlrc.yml @@ -0,0 +1 @@ +schema: schema.graphql diff --git a/CHANGELOG.md b/CHANGELOG.md index e02140aa..173de141 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. ## Unreleased +* Allow pipeline to be removed from a cluster [[PR #279](https://github.com/buildkite/terraform-provider-buildkite/pull/279)] * Change default provider settings to match new pipeline [[PR #282](https://github.com/buildkite/terraform-provider-buildkite/pull/282)] ## [v0.18.1](https://github.com/buildkite/terraform-provider-buildkite/compare/v0.18.0...v0.18.1) diff --git a/buildkite/generated.go b/buildkite/generated.go index 9c9d860a..3211ce0d 100644 --- a/buildkite/generated.go +++ b/buildkite/generated.go @@ -6,6 +6,26 @@ import ( "github.com/Khan/genqlient/graphql" ) +// All the possible build retention periods, depending on your billing plan +type BuildRetentionPeriods string + +const ( + // 30 days + BuildRetentionPeriodsDays30 BuildRetentionPeriods = "DAYS_30" + // 60 days + BuildRetentionPeriodsDays60 BuildRetentionPeriods = "DAYS_60" + // 90 days + BuildRetentionPeriodsDays90 BuildRetentionPeriods = "DAYS_90" + // 6 months + BuildRetentionPeriodsMonths6 BuildRetentionPeriods = "MONTHS_6" + // 12 months + BuildRetentionPeriodsMonths12 BuildRetentionPeriods = "MONTHS_12" + // 18 months + BuildRetentionPeriodsMonths18 BuildRetentionPeriods = "MONTHS_18" + // 2 years + BuildRetentionPeriodsYears2 BuildRetentionPeriods = "YEARS_2" +) + // The roles a user can be within a team type GTeamMemberRole string @@ -26,6 +46,210 @@ const ( GenqlientTeamPrivacySecret GenqlientTeamPrivacy = "SECRET" ) +// The access levels that can be assigned to a pipeline +type PipelineAccessLevels string + +const ( + // Allows edits, builds and reads + PipelineAccessLevelsManageBuildAndRead PipelineAccessLevels = "MANAGE_BUILD_AND_READ" + // Allows builds and read only + PipelineAccessLevelsBuildAndRead PipelineAccessLevels = "BUILD_AND_READ" + // Read only - no builds or edits + PipelineAccessLevelsReadOnly PipelineAccessLevels = "READ_ONLY" +) + +// Repository information for a pipeline +type PipelineRepositoryInput struct { + // Repository information for a pipeline + Url string `json:"url"` +} + +// GetUrl returns PipelineRepositoryInput.Url, and is useful for accessing the field via an interface. +func (v *PipelineRepositoryInput) GetUrl() string { return v.Url } + +// Step definition for a pipeline +type PipelineStepsInput struct { + // Step definition for a pipeline + Yaml string `json:"yaml"` +} + +// GetYaml returns PipelineStepsInput.Yaml, and is useful for accessing the field via an interface. +func (v *PipelineStepsInput) GetYaml() string { return v.Yaml } + +// Tag associated with a pipeline +type PipelineTagInput struct { + // Tag associated with a pipeline + Label string `json:"label"` +} + +// GetLabel returns PipelineTagInput.Label, and is useful for accessing the field via an interface. +func (v *PipelineTagInput) GetLabel() string { return v.Label } + +// Autogenerated input type of PipelineUpdate +type PipelineUpdateInput struct { + // Autogenerated input type of PipelineUpdate + ClientMutationId string `json:"clientMutationId"` + // Autogenerated input type of PipelineUpdate + Id string `json:"id"` + // Autogenerated input type of PipelineUpdate + Name string `json:"name"` + // Autogenerated input type of PipelineUpdate + Description string `json:"description"` + // Autogenerated input type of PipelineUpdate + Emoji string `json:"emoji"` + // Autogenerated input type of PipelineUpdate + Color string `json:"color"` + // Autogenerated input type of PipelineUpdate + Visibility PipelineVisibility `json:"visibility,omitempty"` + // Autogenerated input type of PipelineUpdate + Repository PipelineRepositoryInput `json:"repository"` + // Autogenerated input type of PipelineUpdate + Steps PipelineStepsInput `json:"steps"` + // Autogenerated input type of PipelineUpdate + DefaultBranch string `json:"defaultBranch"` + // Autogenerated input type of PipelineUpdate + NextBuildNumber int `json:"nextBuildNumber,omitempty"` + // Autogenerated input type of PipelineUpdate + SkipIntermediateBuilds bool `json:"skipIntermediateBuilds"` + // Autogenerated input type of PipelineUpdate + SkipIntermediateBuildsBranchFilter string `json:"skipIntermediateBuildsBranchFilter"` + // Autogenerated input type of PipelineUpdate + CancelIntermediateBuilds bool `json:"cancelIntermediateBuilds"` + // Autogenerated input type of PipelineUpdate + CancelIntermediateBuildsBranchFilter string `json:"cancelIntermediateBuildsBranchFilter"` + // Autogenerated input type of PipelineUpdate + AllowRebuilds bool `json:"allowRebuilds"` + // Autogenerated input type of PipelineUpdate + DefaultTimeoutInMinutes int `json:"defaultTimeoutInMinutes"` + // Autogenerated input type of PipelineUpdate + MaximumTimeoutInMinutes int `json:"maximumTimeoutInMinutes"` + // Autogenerated input type of PipelineUpdate + BuildRetentionEnabled bool `json:"buildRetentionEnabled"` + // Autogenerated input type of PipelineUpdate + BuildRetentionPeriod BuildRetentionPeriods `json:"buildRetentionPeriod,omitempty"` + // Autogenerated input type of PipelineUpdate + BuildRetentionNumber int `json:"buildRetentionNumber,omitempty"` + // Autogenerated input type of PipelineUpdate + ClusterId *string `json:"clusterId"` + // Autogenerated input type of PipelineUpdate + Archived bool `json:"archived,omitempty"` + // Autogenerated input type of PipelineUpdate + Tags []PipelineTagInput `json:"tags"` + // Autogenerated input type of PipelineUpdate + BranchConfiguration string `json:"branchConfiguration"` +} + +// GetClientMutationId returns PipelineUpdateInput.ClientMutationId, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetClientMutationId() string { return v.ClientMutationId } + +// GetId returns PipelineUpdateInput.Id, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetId() string { return v.Id } + +// GetName returns PipelineUpdateInput.Name, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetName() string { return v.Name } + +// GetDescription returns PipelineUpdateInput.Description, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetDescription() string { return v.Description } + +// GetEmoji returns PipelineUpdateInput.Emoji, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetEmoji() string { return v.Emoji } + +// GetColor returns PipelineUpdateInput.Color, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetColor() string { return v.Color } + +// GetVisibility returns PipelineUpdateInput.Visibility, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetVisibility() PipelineVisibility { return v.Visibility } + +// GetRepository returns PipelineUpdateInput.Repository, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetRepository() PipelineRepositoryInput { return v.Repository } + +// GetSteps returns PipelineUpdateInput.Steps, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetSteps() PipelineStepsInput { return v.Steps } + +// GetDefaultBranch returns PipelineUpdateInput.DefaultBranch, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetDefaultBranch() string { return v.DefaultBranch } + +// GetNextBuildNumber returns PipelineUpdateInput.NextBuildNumber, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetNextBuildNumber() int { return v.NextBuildNumber } + +// GetSkipIntermediateBuilds returns PipelineUpdateInput.SkipIntermediateBuilds, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetSkipIntermediateBuilds() bool { return v.SkipIntermediateBuilds } + +// GetSkipIntermediateBuildsBranchFilter returns PipelineUpdateInput.SkipIntermediateBuildsBranchFilter, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetSkipIntermediateBuildsBranchFilter() string { + return v.SkipIntermediateBuildsBranchFilter +} + +// GetCancelIntermediateBuilds returns PipelineUpdateInput.CancelIntermediateBuilds, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetCancelIntermediateBuilds() bool { return v.CancelIntermediateBuilds } + +// GetCancelIntermediateBuildsBranchFilter returns PipelineUpdateInput.CancelIntermediateBuildsBranchFilter, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetCancelIntermediateBuildsBranchFilter() string { + return v.CancelIntermediateBuildsBranchFilter +} + +// GetAllowRebuilds returns PipelineUpdateInput.AllowRebuilds, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetAllowRebuilds() bool { return v.AllowRebuilds } + +// GetDefaultTimeoutInMinutes returns PipelineUpdateInput.DefaultTimeoutInMinutes, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetDefaultTimeoutInMinutes() int { return v.DefaultTimeoutInMinutes } + +// GetMaximumTimeoutInMinutes returns PipelineUpdateInput.MaximumTimeoutInMinutes, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetMaximumTimeoutInMinutes() int { return v.MaximumTimeoutInMinutes } + +// GetBuildRetentionEnabled returns PipelineUpdateInput.BuildRetentionEnabled, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetBuildRetentionEnabled() bool { return v.BuildRetentionEnabled } + +// GetBuildRetentionPeriod returns PipelineUpdateInput.BuildRetentionPeriod, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetBuildRetentionPeriod() BuildRetentionPeriods { + return v.BuildRetentionPeriod +} + +// GetBuildRetentionNumber returns PipelineUpdateInput.BuildRetentionNumber, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetBuildRetentionNumber() int { return v.BuildRetentionNumber } + +// GetClusterId returns PipelineUpdateInput.ClusterId, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetClusterId() *string { return v.ClusterId } + +// GetArchived returns PipelineUpdateInput.Archived, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetArchived() bool { return v.Archived } + +// GetTags returns PipelineUpdateInput.Tags, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetTags() []PipelineTagInput { return v.Tags } + +// GetBranchConfiguration returns PipelineUpdateInput.BranchConfiguration, and is useful for accessing the field via an interface. +func (v *PipelineUpdateInput) GetBranchConfiguration() string { return v.BranchConfiguration } + +// The visibility of the pipeline +type PipelineVisibility string + +const ( + // The pipeline is public + PipelineVisibilityPublic PipelineVisibility = "PUBLIC" + // The pipeline is private + PipelineVisibilityPrivate PipelineVisibility = "PRIVATE" +) + +// The roles a user can be within a team +type TeamMemberRole string + +const ( + // The user is a regular member of the team + TeamMemberRoleMember TeamMemberRole = "MEMBER" + // The user can manage pipelines and users within the team + TeamMemberRoleMaintainer TeamMemberRole = "MAINTAINER" +) + +// Whether a team is visible or secret within an organization +type TeamPrivacy string + +const ( + // Visible to all members of the organization + TeamPrivacyVisible TeamPrivacy = "VISIBLE" + // Visible to organization administrators and members + TeamPrivacySecret TeamPrivacy = "SECRET" +) + // __getOrganizationInput is used internally by genqlient type __getOrganizationInput struct { Slug string `json:"slug"` @@ -62,6 +286,14 @@ 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 } +// __updatePipelineInput is used internally by genqlient +type __updatePipelineInput struct { + Input PipelineUpdateInput `json:"input"` +} + +// GetInput returns __updatePipelineInput.Input, and is useful for accessing the field via an interface. +func (v *__updatePipelineInput) GetInput() PipelineUpdateInput { return v.Input } + // getOrganizationOrganization includes the requested fields of the GraphQL type Organization. // The GraphQL type's documentation follows. // @@ -256,6 +488,325 @@ func (v *setApiIpAddressesResponse) GetOrganizationApiIpAllowlistUpdate() setApi return v.OrganizationApiIpAllowlistUpdate } +// updatePipelinePipelineUpdatePipelineUpdatePayload includes the requested fields of the GraphQL type PipelineUpdatePayload. +// The GraphQL type's documentation follows. +// +// Autogenerated return type of PipelineUpdate. +type updatePipelinePipelineUpdatePipelineUpdatePayload struct { + Pipeline updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline `json:"pipeline"` +} + +// GetPipeline returns updatePipelinePipelineUpdatePipelineUpdatePayload.Pipeline, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayload) GetPipeline() updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline { + return v.Pipeline +} + +// updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline includes the requested fields of the GraphQL type Pipeline. +// The GraphQL type's documentation follows. +// +// A pipeline +type updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline struct { + Id string `json:"id"` + // Whether existing builds can be rebuilt as new builds. + AllowRebuilds bool `json:"allowRebuilds"` + // When a new build is created on a branch, any previous builds that are running on the same branch will be automatically cancelled + CancelIntermediateBuilds bool `json:"cancelIntermediateBuilds"` + // Limit which branches build cancelling applies to, for example `!main` will ensure that the main branch won't have it's builds automatically cancelled. + CancelIntermediateBuildsBranchFilter string `json:"cancelIntermediateBuildsBranchFilter"` + Cluster updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineCluster `json:"cluster"` + // The default branch for this pipeline + DefaultBranch string `json:"defaultBranch"` + // The default timeout in minutes for all command steps in this pipeline. This can still be overridden in any command step + DefaultTimeoutInMinutes int `json:"defaultTimeoutInMinutes"` + // The maximum timeout in minutes for all command steps in this pipeline. Any command step without a timeout or with a timeout greater than this value will be set to this value. + MaximumTimeoutInMinutes int `json:"maximumTimeoutInMinutes"` + // The short description of the pipeline + Description string `json:"description"` + // The name of the pipeline + Name string `json:"name"` + // The repository for this pipeline + Repository updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineRepository `json:"repository"` + // When a new build is created on a branch, any previous builds that haven't yet started on the same branch will be automatically marked as skipped. + SkipIntermediateBuilds bool `json:"skipIntermediateBuilds"` + // Limit which branches build skipping applies to, for example `!main` will ensure that the main branch won't have it's builds automatically skipped. + SkipIntermediateBuildsBranchFilter string `json:"skipIntermediateBuildsBranchFilter"` + // The slug of the pipeline + Slug string `json:"slug"` + Steps updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineSteps `json:"steps"` + // Tags that have been given to this pipeline + Tags []updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTagsPipelineTag `json:"tags"` + // Teams associated with this pipeline + Teams updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnection `json:"teams"` + // The URL to use in your repository settings for commit webhooks + WebhookURL string `json:"webhookURL"` +} + +// GetId returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.Id, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetId() string { return v.Id } + +// GetAllowRebuilds returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.AllowRebuilds, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetAllowRebuilds() bool { + return v.AllowRebuilds +} + +// GetCancelIntermediateBuilds returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.CancelIntermediateBuilds, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetCancelIntermediateBuilds() bool { + return v.CancelIntermediateBuilds +} + +// GetCancelIntermediateBuildsBranchFilter returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.CancelIntermediateBuildsBranchFilter, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetCancelIntermediateBuildsBranchFilter() string { + return v.CancelIntermediateBuildsBranchFilter +} + +// GetCluster returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.Cluster, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetCluster() updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineCluster { + return v.Cluster +} + +// GetDefaultBranch returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.DefaultBranch, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetDefaultBranch() string { + return v.DefaultBranch +} + +// GetDefaultTimeoutInMinutes returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.DefaultTimeoutInMinutes, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetDefaultTimeoutInMinutes() int { + return v.DefaultTimeoutInMinutes +} + +// GetMaximumTimeoutInMinutes returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.MaximumTimeoutInMinutes, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetMaximumTimeoutInMinutes() int { + return v.MaximumTimeoutInMinutes +} + +// GetDescription returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.Description, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetDescription() string { + return v.Description +} + +// GetName returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.Name, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetName() string { return v.Name } + +// GetRepository returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.Repository, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetRepository() updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineRepository { + return v.Repository +} + +// GetSkipIntermediateBuilds returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.SkipIntermediateBuilds, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetSkipIntermediateBuilds() bool { + return v.SkipIntermediateBuilds +} + +// GetSkipIntermediateBuildsBranchFilter returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.SkipIntermediateBuildsBranchFilter, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetSkipIntermediateBuildsBranchFilter() string { + return v.SkipIntermediateBuildsBranchFilter +} + +// GetSlug returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.Slug, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetSlug() string { return v.Slug } + +// GetSteps returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.Steps, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetSteps() updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineSteps { + return v.Steps +} + +// GetTags returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.Tags, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetTags() []updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTagsPipelineTag { + return v.Tags +} + +// GetTeams returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.Teams, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetTeams() updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnection { + return v.Teams +} + +// GetWebhookURL returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline.WebhookURL, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipeline) GetWebhookURL() string { + return v.WebhookURL +} + +// updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineCluster includes the requested fields of the GraphQL type Cluster. +type updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineCluster struct { + Id string `json:"id"` +} + +// GetId returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineCluster.Id, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineCluster) GetId() string { + return v.Id +} + +// updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineRepository includes the requested fields of the GraphQL type Repository. +// The GraphQL type's documentation follows. +// +// A repository associated with a pipeline +type updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineRepository struct { + // The git URL for this repository + Url string `json:"url"` +} + +// GetUrl returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineRepository.Url, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineRepository) GetUrl() string { + return v.Url +} + +// updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineSteps includes the requested fields of the GraphQL type PipelineSteps. +// The GraphQL type's documentation follows. +// +// Steps defined on a pipeline +type updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineSteps struct { + // A YAML representation of the pipeline steps + Yaml string `json:"yaml"` +} + +// GetYaml returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineSteps.Yaml, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineSteps) GetYaml() string { + return v.Yaml +} + +// updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTagsPipelineTag includes the requested fields of the GraphQL type PipelineTag. +// The GraphQL type's documentation follows. +// +// A tag associated with a pipeline +type updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTagsPipelineTag struct { + // The label for this tag + Label string `json:"label"` +} + +// GetLabel returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTagsPipelineTag.Label, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTagsPipelineTag) GetLabel() string { + return v.Label +} + +// updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnection includes the requested fields of the GraphQL type TeamPipelineConnection. +// The GraphQL type's documentation follows. +// +// A collection of TeamPipeline records +type updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnection struct { + Edges []updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdge `json:"edges"` +} + +// GetEdges returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnection.Edges, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnection) GetEdges() []updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdge { + return v.Edges +} + +// updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdge includes the requested fields of the GraphQL type TeamPipelineEdge. +type updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdge struct { + Node updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipeline `json:"node"` +} + +// GetNode returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdge.Node, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdge) GetNode() updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipeline { + return v.Node +} + +// updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipeline includes the requested fields of the GraphQL type TeamPipeline. +// The GraphQL type's documentation follows. +// +// An pipeline that's been assigned to a team +type updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipeline struct { + // The access level users have to this pipeline + AccessLevel PipelineAccessLevels `json:"accessLevel"` + Id string `json:"id"` + // The team associated with this team member + Team updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam `json:"team"` +} + +// GetAccessLevel returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipeline.AccessLevel, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipeline) GetAccessLevel() PipelineAccessLevels { + return v.AccessLevel +} + +// GetId returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipeline.Id, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipeline) GetId() string { + return v.Id +} + +// GetTeam returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipeline.Team, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipeline) GetTeam() updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam { + return v.Team +} + +// updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam includes the requested fields of the GraphQL type Team. +// The GraphQL type's documentation follows. +// +// An organization team +type updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam struct { + // A description of the team + Description string `json:"description"` + Id string `json:"id"` + // Add new organization members to this team by default + IsDefaultTeam bool `json:"isDefaultTeam"` + // New organization members will be granted this role on this team + DefaultMemberRole TeamMemberRole `json:"defaultMemberRole"` + // The name of the team + Name string `json:"name"` + // Whether or not team members can create new pipelines in this team + MembersCanCreatePipelines bool `json:"membersCanCreatePipelines"` + // The privacy setting for this team + Privacy TeamPrivacy `json:"privacy"` + // The slug of the team + Slug string `json:"slug"` + // The public UUID for this team + Uuid string `json:"uuid"` +} + +// GetDescription returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam.Description, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam) GetDescription() string { + return v.Description +} + +// GetId returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam.Id, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam) GetId() string { + return v.Id +} + +// GetIsDefaultTeam returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam.IsDefaultTeam, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam) GetIsDefaultTeam() bool { + return v.IsDefaultTeam +} + +// GetDefaultMemberRole returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam.DefaultMemberRole, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam) GetDefaultMemberRole() TeamMemberRole { + return v.DefaultMemberRole +} + +// GetName returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam.Name, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam) GetName() string { + return v.Name +} + +// GetMembersCanCreatePipelines returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam.MembersCanCreatePipelines, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam) GetMembersCanCreatePipelines() bool { + return v.MembersCanCreatePipelines +} + +// GetPrivacy returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam.Privacy, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam) GetPrivacy() TeamPrivacy { + return v.Privacy +} + +// GetSlug returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam.Slug, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam) GetSlug() string { + return v.Slug +} + +// GetUuid returns updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam.Uuid, and is useful for accessing the field via an interface. +func (v *updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdgeNodeTeamPipelineTeam) GetUuid() string { + return v.Uuid +} + +// updatePipelineResponse is returned by updatePipeline on success. +type updatePipelineResponse struct { + // Change the settings for a pipeline. + PipelineUpdate updatePipelinePipelineUpdatePipelineUpdatePayload `json:"pipelineUpdate"` +} + +// GetPipelineUpdate returns updatePipelineResponse.PipelineUpdate, and is useful for accessing the field via an interface. +func (v *updatePipelineResponse) GetPipelineUpdate() updatePipelinePipelineUpdatePipelineUpdatePayload { + return v.PipelineUpdate +} + // The query or mutation executed by getOrganization. const getOrganization_Operation = ` query getOrganization ($slug: ID!) { @@ -413,3 +964,82 @@ func setApiIpAddresses( return &data, err } + +// The query or mutation executed by updatePipeline. +const updatePipeline_Operation = ` +mutation updatePipeline ($input: PipelineUpdateInput!) { + pipelineUpdate(input: $input) { + pipeline { + id + allowRebuilds + cancelIntermediateBuilds + cancelIntermediateBuildsBranchFilter + cluster { + id + } + defaultBranch + defaultTimeoutInMinutes + maximumTimeoutInMinutes + description + name + repository { + url + } + skipIntermediateBuilds + skipIntermediateBuildsBranchFilter + slug + steps { + yaml + } + tags { + label + } + teams(first: 50) { + edges { + node { + accessLevel + id + team { + description + id + isDefaultTeam + defaultMemberRole + name + membersCanCreatePipelines + privacy + slug + uuid + } + } + } + } + webhookURL + } + } +} +` + +func updatePipeline( + client graphql.Client, + input PipelineUpdateInput, +) (*updatePipelineResponse, error) { + req := &graphql.Request{ + OpName: "updatePipeline", + Query: updatePipeline_Operation, + Variables: &__updatePipelineInput{ + Input: input, + }, + } + var err error + + var data updatePipelineResponse + resp := &graphql.Response{Data: &data} + + err = client.MakeRequest( + nil, + req, + resp, + ) + + return &data, err +} diff --git a/buildkite/graphql/pipeline.graphql b/buildkite/graphql/pipeline.graphql index a05ce589..bd4b5dad 100644 --- a/buildkite/graphql/pipeline.graphql +++ b/buildkite/graphql/pipeline.graphql @@ -11,3 +11,62 @@ query getPipeline($slug: ID!) { webhookURL } } + +# @genqlient(for: "PipelineUpdateInput.clusterId", pointer: true) +# @genqlient(for: "PipelineUpdateInput.visibility", omitempty: true) +# @genqlient(for: "PipelineUpdateInput.buildRetentionPeriod", omitempty: true) +# @genqlient(for: "PipelineUpdateInput.buildRetentionNumber", omitempty: true) +# @genqlient(for: "PipelineUpdateInput.archived", omitempty: true) +# @genqlient(for: "PipelineUpdateInput.nextBuildNumber", omitempty: true) +mutation updatePipeline( + $input: PipelineUpdateInput! +) { + pipelineUpdate(input: $input) { + pipeline { + id + allowRebuilds + cancelIntermediateBuilds + cancelIntermediateBuildsBranchFilter + cluster { + id + } + defaultBranch + defaultTimeoutInMinutes + maximumTimeoutInMinutes + description + name + repository { + url + } + skipIntermediateBuilds + skipIntermediateBuildsBranchFilter + slug + steps { + yaml + } + tags { + label + } + teams (first: 50) { + edges { + node { + accessLevel + id + team { + description + id + isDefaultTeam + defaultMemberRole + name + membersCanCreatePipelines + privacy + slug + uuid + } + } + } + } + webhookURL + } + } +} diff --git a/buildkite/resource_pipeline.go b/buildkite/resource_pipeline.go index a2cb0622..6f768f8e 100644 --- a/buildkite/resource_pipeline.go +++ b/buildkite/resource_pipeline.go @@ -15,30 +15,33 @@ const defaultSteps = `steps: command: buildkite-agent pipeline upload` // PipelineNode represents a pipeline as returned from the GraphQL API +type Cluster struct { + ID graphql.String +} +type Repository struct { + URL graphql.String +} +type Steps struct { + YAML graphql.String +} type PipelineNode struct { AllowRebuilds graphql.Boolean CancelIntermediateBuilds graphql.Boolean CancelIntermediateBuildsBranchFilter graphql.String - Cluster struct { - ID graphql.String - } - DefaultBranch graphql.String - DefaultTimeoutInMinutes graphql.Int - MaximumTimeoutInMinutes graphql.Int - Description graphql.String - ID graphql.String - Name graphql.String - Repository struct { - URL graphql.String - } - SkipIntermediateBuilds graphql.Boolean - SkipIntermediateBuildsBranchFilter graphql.String - Slug graphql.String - Steps struct { - YAML graphql.String - } - Tags []PipelineTag - Teams struct { + Cluster Cluster + DefaultBranch graphql.String + DefaultTimeoutInMinutes graphql.Int + MaximumTimeoutInMinutes graphql.Int + Description graphql.String + ID graphql.String + Name graphql.String + Repository Repository + SkipIntermediateBuilds graphql.Boolean + SkipIntermediateBuildsBranchFilter graphql.String + Slug graphql.String + Steps Steps + Tags []PipelineTag + Teams struct { Edges []struct { Node TeamPipelineNode } @@ -46,15 +49,9 @@ type PipelineNode struct { WebhookURL graphql.String `graphql:"webhookURL"` } -// PipelineAccessLevels represents a pipeline access levels as returned from the GraphQL API -type PipelineAccessLevels graphql.String - type PipelineTag struct { Label graphql.String } -type PipelineTagInput struct { - Label graphql.String `json:"label"` -} // TeamPipelineNode represents a team pipeline as returned from the GraphQL API type TeamPipelineNode struct { @@ -101,7 +98,6 @@ func resourcePipeline() *schema.Resource { Type: schema.TypeString, }, "cluster_id": { - Computed: true, Optional: true, Type: schema.TypeString, }, @@ -435,58 +431,71 @@ func ReadPipeline(ctx context.Context, d *schema.ResourceData, m interface{}) di func UpdatePipeline(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*Client) var err error - var mutation struct { - PipelineUpdate struct { - Pipeline PipelineNode - } `graphql:"pipelineUpdate(input: {allowRebuilds: $allow_rebuilds, cancelIntermediateBuilds: $cancel_intermediate_builds, cancelIntermediateBuildsBranchFilter: $cancel_intermediate_builds_branch_filter, defaultBranch: $default_branch, defaultTimeoutInMinutes: $default_timeout_in_minutes, maximumTimeoutInMinutes: $maximum_timeout_in_minutes, description: $desc, id: $id, name: $name, repository: {url: $repository_url}, skipIntermediateBuilds: $skip_intermediate_builds, skipIntermediateBuildsBranchFilter: $skip_intermediate_builds_branch_filter, steps: {yaml: $steps}, tags: $tags})"` - } - vars := map[string]interface{}{ - "allow_rebuilds": graphql.Boolean(d.Get("allow_rebuilds").(bool)), - "cancel_intermediate_builds": graphql.Boolean(d.Get("cancel_intermediate_builds").(bool)), - "cancel_intermediate_builds_branch_filter": graphql.String(d.Get("cancel_intermediate_builds_branch_filter").(string)), - "default_branch": graphql.String(d.Get("default_branch").(string)), - "default_timeout_in_minutes": graphql.Int(d.Get("default_timeout_in_minutes").(int)), - "maximum_timeout_in_minutes": graphql.Int(d.Get("maximum_timeout_in_minutes").(int)), - "desc": graphql.String(d.Get("description").(string)), - "id": graphql.ID(d.Id()), - "name": graphql.String(d.Get("name").(string)), - "repository_url": graphql.String(d.Get("repository").(string)), - "skip_intermediate_builds": graphql.Boolean(d.Get("skip_intermediate_builds").(bool)), - "skip_intermediate_builds_branch_filter": graphql.String(d.Get("skip_intermediate_builds_branch_filter").(string)), - "steps": graphql.String(d.Get("steps").(string)), - "tags": getTagsFromSchema(d), - } - log.Printf("Updating pipeline %s ...", vars["name"]) - - // If the cluster_id key is present in the mutation, GraphQL expects a valid ID. - // Check if cluster_id exists in the configuration before adding to mutation. - if clusterID, ok := d.GetOk("cluster_id"); ok { - var mutationWithClusterID struct { - PipelineUpdate struct { - Pipeline PipelineNode - } `graphql:"pipelineUpdate(input: {allowRebuilds: $allow_rebuilds, cancelIntermediateBuilds: $cancel_intermediate_builds, cancelIntermediateBuildsBranchFilter: $cancel_intermediate_builds_branch_filter, clusterId: $cluster_id, defaultBranch: $default_branch, defaultTimeoutInMinutes: $default_timeout_in_minutes, maximumTimeoutInMinutes: $maximum_timeout_in_minutes, description: $desc, id: $id, name: $name, repository: {url: $repository_url}, skipIntermediateBuilds: $skip_intermediate_builds, skipIntermediateBuildsBranchFilter: $skip_intermediate_builds_branch_filter, steps: {yaml: $steps}, tags: $tags})"` + input := PipelineUpdateInput{ + AllowRebuilds: d.Get("allow_rebuilds").(bool), + CancelIntermediateBuilds: d.Get("cancel_intermediate_builds").(bool), + CancelIntermediateBuildsBranchFilter: d.Get("cancel_intermediate_builds_branch_filter").(string), + DefaultBranch: d.Get("default_branch").(string), + DefaultTimeoutInMinutes: d.Get("default_timeout_in_minutes").(int), + MaximumTimeoutInMinutes: d.Get("maximum_timeout_in_minutes").(int), + Description: d.Get("description").(string), + Id: d.Id(), + Name: d.Get("name").(string), + Repository: PipelineRepositoryInput{Url: d.Get("repository").(string)}, + SkipIntermediateBuilds: d.Get("skip_intermediate_builds").(bool), + SkipIntermediateBuildsBranchFilter: d.Get("skip_intermediate_builds_branch_filter").(string), + Steps: PipelineStepsInput{Yaml: d.Get("steps").(string)}, + Tags: getTagsFromSchema(d), + } + + // If cluster_id exists in the schema it must be a non-empty string + // Otherwise, if its not present it will be set to nil by default + if clusterID, clusterIdPresent := d.GetOk("cluster_id"); clusterIdPresent { + if value, isString := clusterID.(string); isString && value != "" { + input.ClusterId = &value } - vars["cluster_id"] = graphql.ID(clusterID.(string)) - err = client.graphql.Mutate(context.Background(), &mutationWithClusterID, vars) - mutation.PipelineUpdate.Pipeline = mutationWithClusterID.PipelineUpdate.Pipeline - } else { - err = client.graphql.Mutate(context.Background(), &mutation, vars) } + log.Printf("Updating pipeline %s ...", input.Name) + + response, err := updatePipeline(client.genqlient, input) + if err != nil { log.Printf("Unable to update pipeline %s", d.Get("name")) return diag.FromErr(err) } + // While transitioning from shurcool to genqlient, we'll map the response here to utilise existing functionality + pipeline := PipelineNode{ + AllowRebuilds: graphql.Boolean(response.PipelineUpdate.Pipeline.AllowRebuilds), + CancelIntermediateBuilds: graphql.Boolean(response.PipelineUpdate.Pipeline.CancelIntermediateBuilds), + CancelIntermediateBuildsBranchFilter: graphql.String(response.PipelineUpdate.Pipeline.CancelIntermediateBuildsBranchFilter), + Cluster: Cluster{ID: graphql.String(response.PipelineUpdate.Pipeline.Cluster.Id)}, + DefaultBranch: graphql.String(response.PipelineUpdate.Pipeline.DefaultBranch), + DefaultTimeoutInMinutes: graphql.Int(response.PipelineUpdate.Pipeline.DefaultTimeoutInMinutes), + MaximumTimeoutInMinutes: graphql.Int(response.PipelineUpdate.Pipeline.MaximumTimeoutInMinutes), + Description: graphql.String(response.PipelineUpdate.Pipeline.Description), + ID: graphql.String(response.PipelineUpdate.Pipeline.Id), + Name: graphql.String(response.PipelineUpdate.Pipeline.Name), + Repository: Repository{URL: graphql.String(response.PipelineUpdate.Pipeline.Repository.Url)}, + SkipIntermediateBuilds: graphql.Boolean(response.PipelineUpdate.Pipeline.SkipIntermediateBuilds), + SkipIntermediateBuildsBranchFilter: graphql.String(response.PipelineUpdate.Pipeline.SkipIntermediateBuildsBranchFilter), + Slug: graphql.String(response.PipelineUpdate.Pipeline.Slug), + Steps: Steps{YAML: graphql.String(response.PipelineUpdate.Pipeline.Steps.Yaml)}, + Tags: mapTagsFromGenqlient(response.PipelineUpdate.Pipeline.Tags), + Teams: mapTeamPipelinesFromGenqlient(response.PipelineUpdate.Pipeline.Teams.Edges), + WebhookURL: graphql.String(response.PipelineUpdate.Pipeline.WebhookURL), + } + teamPipelines := getTeamPipelinesFromSchema(d) - err = reconcileTeamPipelines(teamPipelines, &mutation.PipelineUpdate.Pipeline, client) + err = reconcileTeamPipelines(teamPipelines, &pipeline, client) if err != nil { log.Print("Unable to reconcile team pipelines") return diag.FromErr(err) } - updatePipelineResource(d, &mutation.PipelineUpdate.Pipeline) + updatePipelineResource(d, &pipeline) pipelineExtraInfo, err := updatePipelineExtraInfo(d, client) if err != nil { @@ -589,17 +598,45 @@ func updatePipelineExtraInfo(d *schema.ResourceData, client *Client) (PipelineEx return pipelineExtraInfo, nil } +func mapTagsFromGenqlient(tags []updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTagsPipelineTag) []PipelineTag { + newTags := make([]PipelineTag, len(tags)) + + for i, v := range tags { + newTags[i] = PipelineTag{Label: graphql.String(v.Label)} + } + return newTags +} + func getTagsFromSchema(d *schema.ResourceData) []PipelineTagInput { tagSet := d.Get("tags").(*schema.Set) tags := make([]PipelineTagInput, tagSet.Len()) for i, v := range tagSet.List() { tags[i] = PipelineTagInput{ - Label: graphql.String(v.(string)), + Label: v.(string), } } return tags } +func mapTeamPipelinesFromGenqlient(tags []updatePipelinePipelineUpdatePipelineUpdatePayloadPipelineTeamsTeamPipelineConnectionEdgesTeamPipelineEdge) struct { + Edges []struct{ Node TeamPipelineNode } +} { + teamPipelineNodes := make([]struct{ Node TeamPipelineNode }, len(tags)) + for i, v := range tags { + teamPipelineNodes[i] = struct{ Node TeamPipelineNode }{ + Node: TeamPipelineNode{ + AccessLevel: v.Node.AccessLevel, + ID: graphql.String(v.Node.Id), + Team: TeamNode{ + Slug: graphql.String(v.Node.Team.Slug), + }}, + } + } + return struct { + Edges []struct{ Node TeamPipelineNode } + }{Edges: teamPipelineNodes} +} + func getTeamPipelinesFromSchema(d *schema.ResourceData) []TeamPipelineNode { teamsInput := d.Get("team").(*schema.Set).List() teamPipelineNodes := make([]TeamPipelineNode, len(teamsInput)) diff --git a/buildkite/resource_pipeline_test.go b/buildkite/resource_pipeline_test.go index e2515e44..43ea8ee1 100644 --- a/buildkite/resource_pipeline_test.go +++ b/buildkite/resource_pipeline_test.go @@ -35,7 +35,7 @@ func TestAccPipeline_add_remove(t *testing.T) { } // Confirm that we can create a new pipeline with a cluster, and then delete it without error -func TestAccPipeline_add_remove_withcluster(t *testing.T) { +func TestAccPipeline_add_delete_withcluster(t *testing.T) { var resourcePipeline PipelineNode resource.Test(t, resource.TestCase{ @@ -73,6 +73,45 @@ func TestAccPipeline_add_remove_withcluster(t *testing.T) { }) } +// Confirm that we can create a new pipeline with a cluster, and then remove it from the cluster +func TestAccPipeline_add_remove_withcluster(t *testing.T) { + var resourcePipeline PipelineNode + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPipelineResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPipelineConfigBasicWithCluster("foo"), + Check: resource.ComposeAggregateTestCheckFunc( + // Confirm the pipeline exists in the buildkite API + testAccCheckPipelineExists("buildkite_pipeline.foobar", &resourcePipeline), + // Confirm the pipeline has the correct values in Buildkite's system + testAccCheckPipelineRemoteValues(&resourcePipeline, "Test Pipeline foo"), + // Confirm the pipeline has the correct values in terraform state + resource.TestCheckResourceAttr("buildkite_pipeline.foobar", "name", "Test Pipeline foo"), + resource.TestCheckResourceAttr("buildkite_pipeline.foobar", "cluster_id", "Q2x1c3Rlci0tLTRlN2JmM2FjLWUzMjMtNGY1OS05MGY2LTQ5OTljZmI2MGQyYg=="), + resource.TestCheckResourceAttr("buildkite_pipeline.foobar", "allow_rebuilds", "true"), + ), + }, + { + Config: testAccPipelineConfigBasicWithTeam("foo"), + Check: resource.ComposeAggregateTestCheckFunc( + // Confirm the pipeline exists in the buildkite API + testAccCheckPipelineExists("buildkite_pipeline.foobar", &resourcePipeline), + // Confirm the pipeline has the correct values in Buildkite's system + testAccCheckPipelineRemoteValues(&resourcePipeline, "Test Pipeline foo"), + // Confirm the pipeline has the correct values in terraform state + resource.TestCheckResourceAttr("buildkite_pipeline.foobar", "name", "Test Pipeline foo"), + resource.TestCheckResourceAttr("buildkite_pipeline.foobar", "cluster_id", ""), + resource.TestCheckResourceAttr("buildkite_pipeline.foobar", "allow_rebuilds", "true"), + ), + }, + }, + }) +} + func TestAccPipeline_add_remove_complex(t *testing.T) { var resourcePipeline PipelineNode steps := `"steps:\n- command: buildkite-agent pipeline upload\n"` @@ -540,9 +579,8 @@ func testAccPipelineConfigBasicWithCluster(name string) string { resource "buildkite_pipeline" "foobar" { name = "Test Pipeline %s" repository = "https://github.com/buildkite/terraform-provider-buildkite.git" - steps = "" - cluster_id = "Q2x1c3Rlci0tLTRlN2JmM2FjLWUzMjMtNGY1OS05MGY2LTQ5OTljZmI2MGQyYg==" - allow_rebuilds = true + cluster_id = "Q2x1c3Rlci0tLTRlN2JmM2FjLWUzMjMtNGY1OS05MGY2LTQ5OTljZmI2MGQyYg==" + allow_rebuilds = true team { slug = "everyone" diff --git a/buildkite/resource_team.go b/buildkite/resource_team.go index d22b2282..45e91414 100644 --- a/buildkite/resource_team.go +++ b/buildkite/resource_team.go @@ -9,9 +9,6 @@ import ( "github.com/shurcooL/graphql" ) -type TeamPrivacy graphql.String -type TeamMemberRole graphql.String - type TeamNode struct { Description graphql.String ID graphql.String diff --git a/genqlient.yaml b/genqlient.yaml index 68a7d68b..43339711 100644 --- a/genqlient.yaml +++ b/genqlient.yaml @@ -7,3 +7,7 @@ generated: buildkite/generated.go # We pass context.Background() everywhere so just leave it off context_type: "-" + +bindings: + YAML: + type: string diff --git a/schema.graphql b/schema.graphql index 8ca62394..31d42d01 100644 --- a/schema.graphql +++ b/schema.graphql @@ -70,6 +70,7 @@ enum APIAccessTokenScopes { READ_NOTIFICATION_SERVICES READ_ORGANIZATIONS READ_PIPELINES + READ_SUITES READ_TEAMS READ_USER WRITE_AGENTS @@ -79,6 +80,7 @@ enum APIAccessTokenScopes { WRITE_CLUSTERS WRITE_NOTIFICATION_SERVICES WRITE_PIPELINES + WRITE_SUITES } """An API Application""" @@ -542,33 +544,33 @@ union AuditSubjectNode =APIAccessToken | AgentToken | AuthorizationBitbucket | A """All the possible types of subjects in an Audit Event""" enum AuditSubjectType { + CLUSTER_PERMISSION AGENT_TOKEN API_ACCESS_TOKEN CLUSTER_QUEUE CLUSTER_TOKEN - SUBSCRIPTION + NOTIFICATION_SERVICE ORGANIZATION ORGANIZATION_INVITATION - NOTIFICATION_SERVICE PIPELINE_SCHEDULE TEAM TEAM_MEMBER + TEAM_PIPELINE TEAM_SUITE SCM_SERVICE SCM_PIPELINE_SETTINGS SCM_REPOSITORY_HOST SUITE_MONITOR - CLUSTER_PERMISSION + SUBSCRIPTION + SSO_PROVIDER + ORGANIZATION_MEMBER SUITE CLUSTER USER_TOTP - TEAM_PIPELINE - USER USER_EMAIL + USER AUTHORIZATION - SSO_PROVIDER PIPELINE - ORGANIZATION_MEMBER } """Context for an audit event created during a web request""" @@ -1018,9 +1020,18 @@ type Cluster { first: Int last: Int ): ClusterAgentTokenConnection +"""Color hex code for the cluster""" + color: String +"""User who created the cluster""" createdBy: User +"""The default queue that agents connecting to the cluster without specifying a queue will accept jobs from""" + defaultQueue: ClusterQueue +"""Description of the cluster""" description: String +"""Emoji for the cluster using Buildkite emoji syntax""" + emoji: String id: ID! +"""Name of the cluster""" name: String! organization: Organization queues( @@ -1045,17 +1056,118 @@ type ClusterAgentTokenConnection implements Connection{ pageInfo: PageInfo } +"""Autogenerated input type of ClusterAgentTokenCreateMutation""" +input ClusterAgentTokenCreateMutationInput { +"""Autogenerated input type of ClusterAgentTokenCreateMutation""" + clientMutationId: String +"""Autogenerated input type of ClusterAgentTokenCreateMutation""" + organizationId: ID! +"""Autogenerated input type of ClusterAgentTokenCreateMutation""" + description: String! +"""Autogenerated input type of ClusterAgentTokenCreateMutation""" + clusterId: ID! +"""Autogenerated input type of ClusterAgentTokenCreateMutation""" + jobTokensEnabled: Boolean +} + +"""Autogenerated return type of ClusterAgentTokenCreateMutation.""" +type ClusterAgentTokenCreateMutationPayload { +"""A unique identifier for the client performing the mutation.""" + clientMutationId: String + clusterAgentToken: ClusterToken! +"""The token value used to register a new agent to this tokens cluster""" + tokenValue: String! +} + type ClusterAgentTokenEdge { cursor: String! node: ClusterToken } +"""Autogenerated input type of ClusterAgentTokenRevokeMutation""" +input ClusterAgentTokenRevokeMutationInput { +"""Autogenerated input type of ClusterAgentTokenRevokeMutation""" + clientMutationId: String +"""Autogenerated input type of ClusterAgentTokenRevokeMutation""" + id: ID! +"""Autogenerated input type of ClusterAgentTokenRevokeMutation""" + organizationId: ID! +} + +"""Autogenerated return type of ClusterAgentTokenRevokeMutation.""" +type ClusterAgentTokenRevokeMutationPayload { +"""A unique identifier for the client performing the mutation.""" + clientMutationId: String + deletedClusterAgentTokenId: ID! +} + +"""Autogenerated input type of ClusterAgentTokenUpdateMutation""" +input ClusterAgentTokenUpdateMutationInput { +"""Autogenerated input type of ClusterAgentTokenUpdateMutation""" + clientMutationId: String +"""Autogenerated input type of ClusterAgentTokenUpdateMutation""" + id: ID! +"""Autogenerated input type of ClusterAgentTokenUpdateMutation""" + organizationId: ID! +"""Autogenerated input type of ClusterAgentTokenUpdateMutation""" + description: String! +"""Autogenerated input type of ClusterAgentTokenUpdateMutation""" + jobTokensEnabled: Boolean +} + +"""Autogenerated return type of ClusterAgentTokenUpdateMutation.""" +type ClusterAgentTokenUpdateMutationPayload { +"""A unique identifier for the client performing the mutation.""" + clientMutationId: String + clusterAgentToken: ClusterToken! +} + type ClusterConnection implements Connection{ count: Int! edges: [ClusterEdge] pageInfo: PageInfo } +"""Autogenerated input type of ClusterCreate""" +input ClusterCreateInput { +"""Autogenerated input type of ClusterCreate""" + clientMutationId: String +"""Autogenerated input type of ClusterCreate""" + organizationId: ID! +"""Autogenerated input type of ClusterCreate""" + name: String! +"""Autogenerated input type of ClusterCreate""" + description: String +"""Autogenerated input type of ClusterCreate""" + emoji: String +"""Autogenerated input type of ClusterCreate""" + color: String +} + +"""Autogenerated return type of ClusterCreate.""" +type ClusterCreatePayload { +"""A unique identifier for the client performing the mutation.""" + clientMutationId: String + cluster: Cluster! +} + +"""Autogenerated input type of ClusterDelete""" +input ClusterDeleteInput { +"""Autogenerated input type of ClusterDelete""" + clientMutationId: String +"""Autogenerated input type of ClusterDelete""" + organizationId: ID! +"""Autogenerated input type of ClusterDelete""" + id: ID! +} + +"""Autogenerated return type of ClusterDelete.""" +type ClusterDeletePayload { +"""A unique identifier for the client performing the mutation.""" + clientMutationId: String + deletedClusterId: ID! +} + type ClusterEdge { cursor: String! node: Cluster @@ -1162,12 +1274,41 @@ type ClusterToken implements Node{ """A description about what this cluster agent token is used for""" description: String id: ID! -"""The content of this cluster token""" +"""Agents registered with this token will use a unique token for each job. Please note that this feature is not yet available to all organizations""" + jobTokensEnabled: Boolean +"""The token value used to register a new agent to this tokens cluster. This will soon return an empty string before we finally remove this field.""" token: String! """The public UUID for this cluster token""" uuid: ID! } +"""Autogenerated input type of ClusterUpdate""" +input ClusterUpdateInput { +"""Autogenerated input type of ClusterUpdate""" + clientMutationId: String +"""Autogenerated input type of ClusterUpdate""" + organizationId: ID! +"""Autogenerated input type of ClusterUpdate""" + id: ID! +"""Autogenerated input type of ClusterUpdate""" + name: String +"""Autogenerated input type of ClusterUpdate""" + description: String +"""Autogenerated input type of ClusterUpdate""" + emoji: String +"""Autogenerated input type of ClusterUpdate""" + color: String +"""Autogenerated input type of ClusterUpdate""" + defaultQueueId: ID +} + +"""Autogenerated return type of ClusterUpdate.""" +type ClusterUpdatePayload { +"""A unique identifier for the client performing the mutation.""" + clientMutationId: String + cluster: Cluster! +} + interface Connection { count: Int! pageInfo: PageInfo @@ -1953,6 +2094,31 @@ type Mutation { """Parameters for BuildRebuild""" input: BuildRebuildInput! ): BuildRebuildPayload +"""Create a new cluster agent token""" + clusterAgentTokenCreate( +"""Parameters for ClusterAgentTokenCreateMutation""" + input: ClusterAgentTokenCreateMutationInput! + ): ClusterAgentTokenCreateMutationPayload +"""Revokes a cluster agent token""" + clusterAgentTokenRevoke( +"""Parameters for ClusterAgentTokenRevokeMutation""" + input: ClusterAgentTokenRevokeMutationInput! + ): ClusterAgentTokenRevokeMutationPayload +"""Updates a cluster agent token""" + clusterAgentTokenUpdate( +"""Parameters for ClusterAgentTokenUpdateMutation""" + input: ClusterAgentTokenUpdateMutationInput! + ): ClusterAgentTokenUpdateMutationPayload +"""Create a cluster.""" + clusterCreate( +"""Parameters for ClusterCreate""" + input: ClusterCreateInput! + ): ClusterCreatePayload +"""Delete a cluster.""" + clusterDelete( +"""Parameters for ClusterDelete""" + input: ClusterDeleteInput! + ): ClusterDeletePayload """This will prevent dispatch of jobs to agents on this queue. You can add an optional note describing the reason for pausing.""" clusterQueuePauseDispatch( """Parameters for ClusterQueuePauseDispatch""" @@ -1963,6 +2129,11 @@ type Mutation { """Parameters for ClusterQueueResumeDispatch""" input: ClusterQueueResumeDispatchInput! ): ClusterQueueResumeDispatchPayload +"""Updates a cluster.""" + clusterUpdate( +"""Parameters for ClusterUpdate""" + input: ClusterUpdateInput! + ): ClusterUpdatePayload """Add a new email address for the current user""" emailCreate( """Parameters for EmailCreate"""