Commit edec6ed
Add opslevel_campaign resource with full CRUD and check management (#643)
* feat: add opslevel_campaign resource with full CRUD
Implements the Terraform resource for managing OpsLevel campaigns:
- Create: creates a draft campaign, optionally schedules it
- Read: fetches campaign by ID from the API
- Update: updates campaign fields and manages schedule lifecycle
- Delete: deletes the campaign
- ImportState: supports terraform import by campaign ID
Managed attributes: name, owner_id, filter_id, project_brief,
start_date, target_date. Computed: id, status, html_url.
Setting both start_date and target_date triggers scheduling;
removing them unschedules the campaign back to draft.
Made-with: Cursor
* feat: register opslevel_campaign in provider and enable local SDK
Wire up NewCampaignResource in the provider's resource list and
enable the local replace directive for opslevel-go development.
Made-with: Cursor
* docs: add campaign resource documentation and examples
Add resource documentation with usage examples for both draft and
scheduled campaigns, schema reference, and import instructions.
Made-with: Cursor
* feat: add opslevel_campaign and opslevel_campaigns data sources
Add single-campaign lookup by ID (opslevel_campaign) and list-all
with optional status filter (opslevel_campaigns). Follows the same
pattern as opslevel_scorecard / opslevel_scorecards.
Made-with: Cursor
* feat: add start_date/target_date pair validation
Add ValidateConfig to opslevel_campaign that ensures both start_date
and target_date are set together or both omitted. Prevents confusing
API errors from setting only one schedule field.
Made-with: Cursor
* test: add mock terraform tests for campaign resource
Add mock provider tests that validate schema attributes, defaults,
and plan-time behavior for both full (scheduled) and minimal (draft)
campaign configurations.
Made-with: Cursor
* test: add remote integration test for campaign resource
Add end-to-end test module that creates, updates, and destroys a
campaign against a live OpsLevel environment. Also exercises the
opslevel_campaigns data source.
Made-with: Cursor
* chore: add changie changelog entry for campaign resource
Made-with: Cursor
* chore: update opslevel-go submodule with campaign CRUD methods
Points submodule to jamescarr/add-campaign-crud branch which adds
GetCampaign, CreateCampaign, UpdateCampaign, DeleteCampaign,
ScheduleCampaign, and UnscheduleCampaign.
Made-with: Cursor
* fix: pass dates directly in campaign create/update
campaignSchedule mutation doesn't exist in the OpsLevel API.
Dates are passed in campaignCreate and campaignUpdate inputs
instead. Unschedule via campaignUnschedule remains unchanged.
Made-with: Cursor
* fix: create then schedule campaign in two API calls
OpsLevel API requires separate mutations: campaignCreate (no dates)
followed by campaignScheduleUpdate (sets start/target dates). Same
two-step flow for updates. Unschedule still via campaignUnschedule.
Made-with: Cursor
* feat: add check_ids field to campaign resource
Passes check IDs via checkIdsToCopy on campaign creation to
associate existing checks with the campaign.
Made-with: Cursor
* fix: set CheckIds type in model to prevent DynamicPseudoType panic
Made-with: Cursor
* feat: use checksCopyToCampaign for associating checks with campaigns
Replace the broken CheckIdsToCopy field on CampaignCreateInput with
the dedicated checksCopyToCampaign GraphQL mutation. Checks are now
copied to the campaign after creation (and optional scheduling),
ensuring they actually appear in the campaign.
Changes:
- Call CopyChecksToCampaign after create if check_ids is set
- Call CopyChecksToCampaign on update when check_ids changes
- Add extractCheckIds helper for type conversion
- Update local mock tests to assert on check_ids
- Update SDK submodule to v2026.3.7-campaign.2
Made-with: Cursor
* fix: update SDK submodule with deletedId fix for campaign delete
Made-with: Cursor
* fix: make check_ids create-only (OpsLevel API has no check removal)
The checksCopyToCampaign mutation is additive — there is no API to
remove checks from a campaign. Calling it on update causes duplicates.
Remove the update-path copy and document check_ids as create-only.
Made-with: Cursor
* feat: reconcile campaign check_ids on update (add + remove)
The Update function now diffs plan vs state check_ids and:
- Copies newly added rubric checks to the campaign
- Deletes campaign checks matching removed rubric checks (by name)
Requires opslevel-go v2026.3.7-campaign.4 for ListCampaignChecks.
Made-with: Cursor
* fix: read campaign checks from API for proper state tracking
Read now queries the campaign's actual checks via ListCampaignChecks
and verifies each rubric check ID in state still has a corresponding
campaign check. This enables drift detection (UI deletions are noticed)
and proper reconciliation on update (adds and removes work correctly).
NewCampaignResourceModel no longer handles CheckIds -- each CRUD path
sets it explicitly: Create/Update from plan, Read from API verification.
Made-with: Cursor
* extract DiffCheckIds, add unit tests, bump opslevel-go
Extracts the check ID diffing logic into a pure, exported function
for testability. Adds 9 unit tests covering all edge cases (no
change, add-only, remove-only, mixed, empty/nil inputs).
Made-with: Cursor
* code review fixes: conventions, docs, tests, and safety
- Add Computed:true to check_ids schema for proper drift handling
- Fix provider registration ordering (alphabetical)
- Use ComputedStringValue in data source model (convention)
- Collapse dead-code date branches in NewCampaignResourceModel
- Add check_ids to resource docs with name-matching caveat
- Add duplicate-name detection warnings in read and reconcile
- Add data source mock test (campaign.tfmock.hcl + tftest)
- Expand remote tests to cover schedule/unschedule lifecycle
- Add TODO comment on go.mod replace directive for upstream merge
- Bump opslevel-go submodule (GetCampaign guard + recursive pagination)
Made-with: Cursor
* fix: use nullableID for filter_id to support unsetting
Sends GraphQL null instead of an empty string when filter_id is
removed from config. Aligns with how all check resources handle
nullable filter IDs.
Made-with: Cursor
* Apply suggestions from code review
Co-authored-by: andrewstillv15 <andrew.still@opslevel.com>
* bump opslevel go
* update opslevel-go
* fix: gofumpt formatting and project_brief clear on update
Always send projectBrief on update so removing it from config sends ""
to the API, which clears the field (null is ignored by the API).
Also fixes gofumpt struct tag alignment in CampaignResourceModel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* get pipeline going
---------
Co-authored-by: andrewstillv15 <andrew.still@opslevel.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>1 parent 337ef62 commit edec6ed
24 files changed
Lines changed: 1673 additions & 140 deletions
File tree
- .changes/unreleased
- docs
- data-sources
- resources
- examples/resources/opslevel_campaign
- opslevel
- submodules
- tests
- local
- mock_datasource
- mock_resource
- remote
- campaign
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
1 | 2 | | |
2 | 3 | | |
3 | 4 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
0 commit comments