Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- `trunk_merge_queue`: support for `extension_enabled`, `enqueueing_label`, `label_commands_enabled`, and `state_labels_enabled` attributes
- `trunk_merge_queue` resource for managing Trunk merge queues
- Full CRUD operations (create, read, update, delete)
- Import support via `terraform import`
Expand Down
4 changes: 4 additions & 0 deletions docs/resources/merge_queue.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,16 @@ Manages a Trunk merge queue.
- `concurrency` (Number) Number of concurrent test slots.
- `create_prs_for_testing_branches` (Boolean) Create PRs for testing branches.
- `direct_merge_mode` (String) Direct merge mode: "off" or "always".
- `enqueueing_label` (String) The GitHub label whose application enqueues a PR onto the merge queue.
- `extension_enabled` (Boolean) Whether the Trunk Merge Queue browser extension is enabled (shown) for this repository.
- `label_commands_enabled` (Boolean) Whether label-based commands (e.g. enqueue/dequeue via labels) are enabled on the merge queue.
- `merge_method` (String) Merge method: "merge_commit", "squash", or "rebase".
- `mode` (String) Queue mode: "single" or "parallel".
- `optimization_mode` (String) Optimization mode: "off" or "bisection_skip_redundant_tests".
- `pending_failure_depth` (Number) Number of PRs below a failure to wait for before eviction.
- `required_statuses` (List of String) Override required status checks. Set to null to revert to branch protection or trunk.yaml defaults; set to [] to explicitly require no statuses.
- `state` (String) Queue state: "running", "paused", or "draining".
- `state_labels_enabled` (Boolean) Whether the merge queue applies labels to PRs reflecting their merge queue state.
- `status_check_enabled` (Boolean) Post GitHub status checks.
- `testing_timeout_minutes` (Number) Maximum minutes to wait for tests.

Expand Down
4 changes: 4 additions & 0 deletions docs/trd/terraform-provider-trunk.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ All optional attributes are also `Computed: true` because the API always returns
| `direct_merge_mode` | string | `"off"` | `"off"` or `"always"` |
| `optimization_mode` | string | `"off"` | `"off"` or `"bisection_skip_redundant_tests"` |
| `bisection_concurrency` | int | -- | Concurrent tests during bisection |
| `extension_enabled` | bool | -- | Whether the browser extension is enabled |
| `enqueueing_label` | string | -- | GitHub label whose application enqueues a PR |
| `label_commands_enabled` | bool | -- | Whether label-based commands are enabled |
| `state_labels_enabled` | bool | -- | Whether MQ applies state labels to PRs |
| `required_statuses` | list | -- | Override required status checks |

**Note on `required_statuses`:** This field distinguishes three states:
Expand Down
53 changes: 47 additions & 6 deletions internal/client/merge_queue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ func testQueue() Queue {
DirectMergeMode: "off",
OptimizationMode: "off",
BisectionConcurrency: 1,
ExtensionEnabled: false,
EnqueueingLabel: "",
LabelCommandsEnabled: false,
StateLabelsEnabled: false,
}
}

Expand Down Expand Up @@ -147,6 +151,10 @@ func TestGetQueue_AllFields(t *testing.T) {
DirectMergeMode: "off",
OptimizationMode: "bisection_skip_redundant_tests",
BisectionConcurrency: 5,
ExtensionEnabled: true,
EnqueueingLabel: "merge-queue",
LabelCommandsEnabled: true,
StateLabelsEnabled: true,
RequiredStatuses: &statuses,
}
_ = json.NewEncoder(w).Encode(q)
Expand Down Expand Up @@ -186,6 +194,18 @@ func TestGetQueue_AllFields(t *testing.T) {
if queue.BisectionConcurrency != 5 {
t.Errorf("BisectionConcurrency = %d, want 5", queue.BisectionConcurrency)
}
if !queue.ExtensionEnabled {
t.Error("ExtensionEnabled = false, want true")
}
if queue.EnqueueingLabel != "merge-queue" {
t.Errorf("EnqueueingLabel = %q, want %q", queue.EnqueueingLabel, "merge-queue")
}
if !queue.LabelCommandsEnabled {
t.Error("LabelCommandsEnabled = false, want true")
}
if !queue.StateLabelsEnabled {
t.Error("StateLabelsEnabled = false, want true")
}
if queue.RequiredStatuses == nil || len(*queue.RequiredStatuses) != 2 {
t.Fatalf("RequiredStatuses = %v, want 2 elements", queue.RequiredStatuses)
}
Expand Down Expand Up @@ -254,6 +274,7 @@ func TestUpdateQueue_OmitsNilFields(t *testing.T) {
for _, field := range []string{
"mode", "concurrency", "state", "mergeMethod", "batch",
"deleteRequiredStatuses", "testingTimeoutMinutes", "requiredStatuses",
"extensionEnabled", "enqueueingLabel", "labelCommandsEnabled", "stateLabelsEnabled",
} {
if _, present := rawBody[field]; present {
t.Errorf("field %q should be absent when nil, but was present in request body", field)
Expand All @@ -274,14 +295,22 @@ func TestUpdateQueue_IncludesNonNilFields(t *testing.T) {
concurrency := 3
mergeMethod := "squash"
batch := true
extensionEnabled := true
enqueueingLabel := "merge-queue"
labelCommandsEnabled := true
stateLabelsEnabled := true
c := newTestClient("key", server.URL)
if _, err := c.UpdateQueue(context.Background(), UpdateQueueRequest{
Repo: Repo{Host: "github.com", Owner: "my-org", Name: "my-repo"},
TargetBranch: "main",
Mode: &mode,
Concurrency: &concurrency,
MergeMethod: &mergeMethod,
Batch: &batch,
Repo: Repo{Host: "github.com", Owner: "my-org", Name: "my-repo"},
TargetBranch: "main",
Mode: &mode,
Concurrency: &concurrency,
MergeMethod: &mergeMethod,
Batch: &batch,
ExtensionEnabled: &extensionEnabled,
EnqueueingLabel: &enqueueingLabel,
LabelCommandsEnabled: &labelCommandsEnabled,
StateLabelsEnabled: &stateLabelsEnabled,
}); err != nil {
t.Fatalf("UpdateQueue error: %v", err)
}
Expand All @@ -297,6 +326,18 @@ func TestUpdateQueue_IncludesNonNilFields(t *testing.T) {
if rawBody["batch"] != true {
t.Errorf("batch = %v, want true", rawBody["batch"])
}
if rawBody["extensionEnabled"] != true {
t.Errorf("extensionEnabled = %v, want true", rawBody["extensionEnabled"])
}
if rawBody["enqueueingLabel"] != "merge-queue" {
t.Errorf("enqueueingLabel = %v, want %q", rawBody["enqueueingLabel"], "merge-queue")
}
if rawBody["labelCommandsEnabled"] != true {
t.Errorf("labelCommandsEnabled = %v, want true", rawBody["labelCommandsEnabled"])
}
if rawBody["stateLabelsEnabled"] != true {
t.Errorf("stateLabelsEnabled = %v, want true", rawBody["stateLabelsEnabled"])
}
}

func TestUpdateQueue_ReturnsQueue(t *testing.T) {
Expand Down
8 changes: 8 additions & 0 deletions internal/client/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ type Queue struct {
DirectMergeMode string `json:"directMergeMode"`
OptimizationMode string `json:"optimizationMode"`
BisectionConcurrency int `json:"bisectionConcurrency"`
ExtensionEnabled bool `json:"extensionEnabled"`
EnqueueingLabel string `json:"enqueueingLabel"`
LabelCommandsEnabled bool `json:"labelCommandsEnabled"`
StateLabelsEnabled bool `json:"stateLabelsEnabled"`

// RequiredStatuses is null when no manual override is set (uses branch protection / trunk.yaml defaults).
RequiredStatuses *[]string `json:"requiredStatuses"`
Expand Down Expand Up @@ -88,6 +92,10 @@ type UpdateQueueRequest struct {
DirectMergeMode *string `json:"directMergeMode,omitempty"`
OptimizationMode *string `json:"optimizationMode,omitempty"`
BisectionConcurrency *int `json:"bisectionConcurrency,omitempty"`
ExtensionEnabled *bool `json:"extensionEnabled,omitempty"`
EnqueueingLabel *string `json:"enqueueingLabel,omitempty"`
LabelCommandsEnabled *bool `json:"labelCommandsEnabled,omitempty"`
StateLabelsEnabled *bool `json:"stateLabelsEnabled,omitempty"`
RequiredStatuses *[]string `json:"requiredStatuses,omitempty"`

// DeleteRequiredStatuses reverts required statuses to branch protection / trunk.yaml defaults
Expand Down
39 changes: 39 additions & 0 deletions internal/provider/merge_queue_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,41 @@ func (r *mergeQueueResource) Schema(_ context.Context, _ resource.SchemaRequest,
int64planmodifier.UseStateForUnknown(),
},
},
"extension_enabled": schema.BoolAttribute{
Description: "Whether the Trunk Merge Queue browser extension is enabled (shown) for this repository.",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Bool{
boolplanmodifier.UseStateForUnknown(),
},
},
"enqueueing_label": schema.StringAttribute{
Description: "The GitHub label whose application enqueues a PR onto the merge queue.",
Optional: true,
Computed: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"label_commands_enabled": schema.BoolAttribute{
Description: "Whether label-based commands (e.g. enqueue/dequeue via labels) are enabled on the merge queue.",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Bool{
boolplanmodifier.UseStateForUnknown(),
},
},
"state_labels_enabled": schema.BoolAttribute{
Description: "Whether the merge queue applies labels to PRs reflecting their merge queue state.",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Bool{
boolplanmodifier.UseStateForUnknown(),
},
},
"required_statuses": schema.ListAttribute{
Description: "Override required status checks. Set to null to revert to branch protection or trunk.yaml defaults; set to [] to explicitly require no statuses.",
Optional: true,
Expand Down Expand Up @@ -462,6 +497,10 @@ func (r *mergeQueueResource) ImportState(ctx context.Context, req resource.Impor
DirectMergeMode: types.StringNull(),
OptimizationMode: types.StringNull(),
BisectionConcurrency: types.Int64Null(),
ExtensionEnabled: types.BoolNull(),
EnqueueingLabel: types.StringNull(),
LabelCommandsEnabled: types.BoolNull(),
StateLabelsEnabled: types.BoolNull(),
RequiredStatuses: types.ListNull(types.StringType),
}

Expand Down
24 changes: 24 additions & 0 deletions internal/provider/merge_queue_resource_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ type mergeQueueResourceModel struct {
DirectMergeMode types.String `tfsdk:"direct_merge_mode"`
OptimizationMode types.String `tfsdk:"optimization_mode"`
BisectionConcurrency types.Int64 `tfsdk:"bisection_concurrency"`
ExtensionEnabled types.Bool `tfsdk:"extension_enabled"`
EnqueueingLabel types.String `tfsdk:"enqueueing_label"`
LabelCommandsEnabled types.Bool `tfsdk:"label_commands_enabled"`
StateLabelsEnabled types.Bool `tfsdk:"state_labels_enabled"`
RequiredStatuses types.List `tfsdk:"required_statuses"`
}

Expand Down Expand Up @@ -135,6 +139,22 @@ func (m *mergeQueueResourceModel) toUpdateRequest(config *mergeQueueResourceMode
v := int(m.BisectionConcurrency.ValueInt64())
req.BisectionConcurrency = &v
}
if !config.ExtensionEnabled.IsNull() {
v := m.ExtensionEnabled.ValueBool()
req.ExtensionEnabled = &v
}
if !config.EnqueueingLabel.IsNull() {
v := m.EnqueueingLabel.ValueString()
req.EnqueueingLabel = &v
}
if !config.LabelCommandsEnabled.IsNull() {
v := m.LabelCommandsEnabled.ValueBool()
req.LabelCommandsEnabled = &v
}
if !config.StateLabelsEnabled.IsNull() {
v := m.StateLabelsEnabled.ValueBool()
req.StateLabelsEnabled = &v
}

// required_statuses: null in config means revert to branch protection / trunk.yaml defaults.
if config.RequiredStatuses.IsNull() {
Expand Down Expand Up @@ -184,6 +204,10 @@ func (m *mergeQueueResourceModel) fromQueue(q *client.Queue) {
m.DirectMergeMode = types.StringValue(q.DirectMergeMode)
m.OptimizationMode = types.StringValue(q.OptimizationMode)
m.BisectionConcurrency = types.Int64Value(int64(q.BisectionConcurrency))
m.ExtensionEnabled = types.BoolValue(q.ExtensionEnabled)
m.EnqueueingLabel = types.StringValue(q.EnqueueingLabel)
m.LabelCommandsEnabled = types.BoolValue(q.LabelCommandsEnabled)
m.StateLabelsEnabled = types.BoolValue(q.StateLabelsEnabled)

if q.RequiredStatuses != nil {
elems := make([]attr.Value, len(*q.RequiredStatuses))
Expand Down
Loading