Skip to content
Open
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
5 changes: 2 additions & 3 deletions docs/data-sources/flagship_flag.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Accepted Permissions
data "cloudflare_flagship_flag" "example_flagship_flag" {
account_id = "account_id"
app_id = "app_id"
flag_key = "flag_key"
key = "example-flag"
}
```

Expand All @@ -29,14 +29,13 @@ data "cloudflare_flagship_flag" "example_flagship_flag" {

- `account_id` (String) Cloudflare account ID.
- `app_id` (String) App identifier.
- `flag_key` (String) Flag key (slug).
- `key` (String) Unique identifier for the flag within an app. Used in all evaluation and SDK calls.

### Read-Only

- `default_variation` (String) Variation served when no rule matches or the flag is disabled. Must be a key in `variations`.
- `description` (String)
- `enabled` (Boolean) When false, the flag bypasses all rules and always serves `default_variation`.
- `key` (String) Unique identifier for the flag within an app. Used in all evaluation and SDK calls.
- `rules` (Attributes List) Targeting rules evaluated in ascending `priority`; the first matching rule wins. An empty array means the flag always serves `default_variation`. (see [below for nested schema](#nestedatt--rules))
- `type` (String) Value type of the flag's variations. Inferred from the variation values on write, so it may be omitted in requests.
Available values: "boolean", "string", "number", "json".
Expand Down
3 changes: 1 addition & 2 deletions docs/resources/flagship_flag.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ resource "cloudflare_flagship_flag" "example_flagship_flag" {
app_id = "app_id"
default_variation = "x"
enabled = true
key = "x"
key = "example-flag"
rules = [{
conditions = [{
attribute = "x"
Expand Down Expand Up @@ -61,7 +61,6 @@ resource "cloudflare_flagship_flag" "example_flagship_flag" {
### Optional

- `description` (String)
- `flag_key` (String) Flag key (slug).
- `type` (String) Value type of the flag's variations. Inferred from the variation values on write, so it may be omitted in requests.
Available values: "boolean", "string", "number", "json".

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
data "cloudflare_flagship_flag" "example_flagship_flag" {
account_id = "account_id"
app_id = "app_id"
flag_key = "flag_key"
key = "example-flag"
}
2 changes: 1 addition & 1 deletion examples/resources/cloudflare_flagship_flag/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ resource "cloudflare_flagship_flag" "example_flagship_flag" {
app_id = "app_id"
default_variation = "x"
enabled = true
key = "x"
key = "example-flag"
rules = [{
conditions = [{
attribute = "x"
Expand Down
2 changes: 1 addition & 1 deletion internal/services/flagship_flag/data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (d *FlagshipFlagDataSource) Read(ctx context.Context, req datasource.ReadRe
_, err := d.client.Flagship.Apps.Flags.Get(
ctx,
data.AppID.ValueString(),
data.FlagKey.ValueString(),
data.Key.ValueString(),
params,
option.WithResponseBodyInto(&res),
option.WithMiddleware(logging.Middleware(ctx)),
Expand Down
3 changes: 1 addition & 2 deletions internal/services/flagship_flag/data_source_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,10 @@ type FlagshipFlagResultDataSourceEnvelope struct {
type FlagshipFlagDataSourceModel struct {
AccountID types.String `tfsdk:"account_id" path:"account_id,required"`
AppID types.String `tfsdk:"app_id" path:"app_id,required"`
FlagKey types.String `tfsdk:"flag_key" path:"flag_key,required"`
Key types.String `tfsdk:"key" json:"key,required"`
DefaultVariation types.String `tfsdk:"default_variation" json:"default_variation,computed"`
Description types.String `tfsdk:"description" json:"description,computed"`
Enabled types.Bool `tfsdk:"enabled" json:"enabled,computed"`
Key types.String `tfsdk:"key" json:"key,computed"`
Type types.String `tfsdk:"type" json:"type,computed"`
UpdatedAt types.String `tfsdk:"updated_at" json:"updated_at,computed"`
UpdatedBy types.String `tfsdk:"updated_by" json:"updated_by,computed"`
Expand Down
8 changes: 2 additions & 6 deletions internal/services/flagship_flag/data_source_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ func DataSourceSchema(ctx context.Context) schema.Schema {
Description: "App identifier.",
Required: true,
},
"flag_key": schema.StringAttribute{
Description: "Flag key (slug).",
"key": schema.StringAttribute{
Description: "Unique identifier for the flag within an app. Used in all evaluation and SDK calls.",
Required: true,
},
"default_variation": schema.StringAttribute{
Expand All @@ -50,10 +50,6 @@ func DataSourceSchema(ctx context.Context) schema.Schema {
Description: "When false, the flag bypasses all rules and always serves `default_variation`.",
Computed: true,
},
"key": schema.StringAttribute{
Description: "Unique identifier for the flag within an app. Used in all evaluation and SDK calls.",
Computed: true,
},
"type": schema.StringAttribute{
Description: "Value type of the flag's variations. Inferred from the variation values on write, so it may be omitted in requests.\nAvailable values: \"boolean\", \"string\", \"number\", \"json\".",
Computed: true,
Expand Down
3 changes: 3 additions & 0 deletions internal/services/flagship_flag/data_source_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ func TestFlagshipFlagDataSourceModelSchemaParity(t *testing.T) {
schema := flagship_flag.DataSourceSchema(context.TODO())
errs := test_helpers.ValidateDataSourceModelSchemaIntegrity(model, schema)
errs.Report(t)
if _, ok := schema.Attributes["flag_key"]; ok {
t.Fatal("flag_key remains in the data source schema")
}
}
82 changes: 81 additions & 1 deletion internal/services/flagship_flag/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,90 @@ import (
"context"

"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)

var _ resource.ResourceWithUpgradeState = (*FlagshipFlagResource)(nil)

func (r *FlagshipFlagResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader {
return map[int64]resource.StateUpgrader{}
priorSchema := resourceSchemaV500(ctx)

return map[int64]resource.StateUpgrader{
500: {
PriorSchema: &priorSchema,
StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
var prior flagshipFlagModelV500
resp.Diagnostics.Append(req.State.Get(ctx, &prior)...)
if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, upgradeFlagshipFlagV500ToV501(prior))...)
if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.AddWarning(
"Flagship flag state upgraded",
"The obsolete `flag_key` attribute was removed, please change any references to it to use `key` instead.",
)
},
},
}
}

// flagshipFlagModelV500 mirrors state written by provider versions 5.20 through
// 5.23. Those versions incorrectly modeled the flag's API key twice: key was
// the JSON field while flag_key was the URL path parameter.
type flagshipFlagModelV500 struct {
AccountID types.String `tfsdk:"account_id"`
AppID types.String `tfsdk:"app_id"`
FlagKey types.String `tfsdk:"flag_key"`
DefaultVariation types.String `tfsdk:"default_variation"`
Enabled types.Bool `tfsdk:"enabled"`
Key types.String `tfsdk:"key"`
Variations *map[string]types.String `tfsdk:"variations"`
Rules *[]*FlagshipFlagRulesModel `tfsdk:"rules"`
Description types.String `tfsdk:"description"`
Type types.String `tfsdk:"type"`
UpdatedAt types.String `tfsdk:"updated_at"`
UpdatedBy types.String `tfsdk:"updated_by"`
}

func upgradeFlagshipFlagV500ToV501(prior flagshipFlagModelV500) FlagshipFlagModel {
key := prior.Key
if (key.IsNull() || key.IsUnknown() || key.ValueString() == "") &&
!prior.FlagKey.IsNull() && !prior.FlagKey.IsUnknown() && prior.FlagKey.ValueString() != "" {
key = prior.FlagKey
}

return FlagshipFlagModel{
AccountID: prior.AccountID,
AppID: prior.AppID,
DefaultVariation: prior.DefaultVariation,
Enabled: prior.Enabled,
Key: key,
Variations: prior.Variations,
Rules: prior.Rules,
Description: prior.Description,
Type: prior.Type,
UpdatedAt: prior.UpdatedAt,
UpdatedBy: prior.UpdatedBy,
}
}

// resourceSchemaV500 reconstructs the pre-fix schema so Terraform can decode
// old state before dropping the redundant flag_key attribute.
func resourceSchemaV500(ctx context.Context) schema.Schema {
s := ResourceSchema(ctx)
s.Version = 500
s.Attributes["flag_key"] = schema.StringAttribute{
Description: "Flag key (slug).",
Optional: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
}
return s
}
83 changes: 83 additions & 0 deletions internal/services/flagship_flag/migrations_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package flagship_flag

import (
"context"
"testing"

"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
)

func TestUpgradeFlagshipFlagV500ToV501FrameworkDecode(t *testing.T) {
ctx := context.Background()
priorSchema := resourceSchemaV500(ctx)
rawState := &tfprotov6.RawState{JSON: []byte(`{
"account_id":"acct",
"app_id":"app",
"flag_key":null,
"default_variation":"disabled",
"enabled":true,
"key":"example-flag",
"variations":{"enabled":"true","disabled":"false"},
"rules":[],
"description":"",
"type":"boolean",
"updated_at":"2026-08-13T00:00:00Z",
"updated_by":"test@example.com"
}`)}
rawValue, err := rawState.Unmarshal(priorSchema.Type().TerraformType(ctx))
if err != nil {
t.Fatalf("decode prior raw state: %v", err)
}
priorState := tfsdk.State{Raw: rawValue, Schema: priorSchema}

r := &FlagshipFlagResource{}
upgrader, ok := r.UpgradeState(ctx)[500]
if !ok {
t.Fatal("no upgrader registered for version 500")
}
resp := resource.UpgradeStateResponse{State: tfsdk.State{Schema: ResourceSchema(ctx)}}
upgrader.StateUpgrader(ctx, resource.UpgradeStateRequest{RawState: rawState, State: &priorState}, &resp)
if resp.Diagnostics.HasError() {
t.Fatalf("upgrade failed: %v", resp.Diagnostics)
}
if len(resp.Diagnostics) != 1 {
t.Fatalf("diagnostics = %v, want one migration warning", resp.Diagnostics)
}
if got := resp.Diagnostics[0].Summary(); got != "Flagship flag state upgraded" {
t.Fatalf("warning summary = %q", got)
}

var got FlagshipFlagModel
if diags := resp.State.Get(ctx, &got); diags.HasError() {
t.Fatalf("decode upgraded state: %v", diags)
}
if got.Key.ValueString() != "example-flag" {
t.Fatalf("key = %q", got.Key.ValueString())
}
if _, exists := ResourceSchema(ctx).Attributes["flag_key"]; exists {
t.Fatal("flag_key remains in the v501 schema")
}
}

func TestUpgradeFlagshipFlagV500FallsBackToLegacyFlagKey(t *testing.T) {
upgraded := upgradeFlagshipFlagV500ToV501(flagshipFlagModelV500{
Key: types.StringNull(),
FlagKey: types.StringValue("legacy-key"),
})
if upgraded.Key.ValueString() != "legacy-key" {
t.Fatalf("key = %q, want legacy-key", upgraded.Key.ValueString())
}
}

func TestUpgradeFlagshipFlagV500PrefersCanonicalKey(t *testing.T) {
upgraded := upgradeFlagshipFlagV500ToV501(flagshipFlagModelV500{
Key: types.StringValue("example-flag"),
FlagKey: types.StringValue("wrong-flag"),
})
if upgraded.Key.ValueString() != "example-flag" {
t.Fatalf("key = %q, want example-flag", upgraded.Key.ValueString())
}
}
1 change: 0 additions & 1 deletion internal/services/flagship_flag/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ type FlagshipFlagResultEnvelope struct {
type FlagshipFlagModel struct {
AccountID types.String `tfsdk:"account_id" path:"account_id,required"`
AppID types.String `tfsdk:"app_id" path:"app_id,required"`
FlagKey types.String `tfsdk:"flag_key" path:"flag_key,optional"`
DefaultVariation types.String `tfsdk:"default_variation" json:"default_variation,required"`
Enabled types.Bool `tfsdk:"enabled" json:"enabled,required"`
Key types.String `tfsdk:"key" json:"key,required"`
Expand Down
6 changes: 3 additions & 3 deletions internal/services/flagship_flag/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (r *FlagshipFlagResource) Update(ctx context.Context, req resource.UpdateRe
_, err = r.client.Flagship.Apps.Flags.Update(
ctx,
data.AppID.ValueString(),
data.FlagKey.ValueString(),
state.Key.ValueString(),
flagship.AppFlagUpdateParams{
AccountID: cloudflare.F(data.AccountID.ValueString()),
},
Expand Down Expand Up @@ -157,7 +157,7 @@ func (r *FlagshipFlagResource) Read(ctx context.Context, req resource.ReadReques
_, err := r.client.Flagship.Apps.Flags.Get(
ctx,
data.AppID.ValueString(),
data.FlagKey.ValueString(),
data.Key.ValueString(),
flagship.AppFlagGetParams{
AccountID: cloudflare.F(data.AccountID.ValueString()),
},
Expand Down Expand Up @@ -196,7 +196,7 @@ func (r *FlagshipFlagResource) Delete(ctx context.Context, req resource.DeleteRe
_, err := r.client.Flagship.Apps.Flags.Delete(
ctx,
data.AppID.ValueString(),
data.FlagKey.ValueString(),
data.Key.ValueString(),
flagship.AppFlagDeleteParams{
AccountID: cloudflare.F(data.AccountID.ValueString()),
},
Expand Down
Loading
Loading