Skip to content

Commit 6c061de

Browse files
authored
resource(vpc_firewall_rules): add schema upgrade (#501)
1 parent 185b9f3 commit 6c061de

File tree

6 files changed

+1145
-2
lines changed

6 files changed

+1145
-2
lines changed

internal/provider/resource_vpc_firewall_rules.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ import (
2929

3030
// Ensure the implementation satisfies the expected interfaces.
3131
var (
32-
_ resource.Resource = (*vpcFirewallRulesResource)(nil)
33-
_ resource.ResourceWithConfigure = (*vpcFirewallRulesResource)(nil)
32+
_ resource.Resource = (*vpcFirewallRulesResource)(nil)
33+
_ resource.ResourceWithConfigure = (*vpcFirewallRulesResource)(nil)
34+
_ resource.ResourceWithUpgradeState = (*vpcFirewallRulesResource)(nil)
3435
)
3536

3637
// NewVPCFirewallRulesResource is a helper function to simplify the provider implementation.
@@ -112,10 +113,24 @@ func (r *vpcFirewallRulesResource) ImportState(ctx context.Context, req resource
112113
resource.ImportStatePassthroughID(ctx, path.Root("vpc_id"), req, resp)
113114
}
114115

116+
// UpgradeState upgrades the Terraform state for the oxide_vpc_firewall_rules
117+
// resource from a previous schema version to the current version.
118+
//
119+
// Schema upgrades are not expected to be applied sequentially, since users are
120+
// allowed to jump to whatever new version they choose. When adding a new
121+
// version, you must ensure that each of the existing StateUpgrader functions
122+
// are also updated to handle the new schema.
123+
func (r *vpcFirewallRulesResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader {
124+
return map[int64]resource.StateUpgrader{
125+
0: {StateUpgrader: r.stateUpgraderV0},
126+
}
127+
}
128+
115129
// Schema defines the schema for the resource.
116130
func (r *vpcFirewallRulesResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
117131
// TODO: Make sure users can define a single block per VPC ID, not many, is this even possible?
118132
resp.Schema = schema.Schema{
133+
Version: 1,
119134
Attributes: map[string]schema.Attribute{
120135
"vpc_id": schema.StringAttribute{
121136
Required: true,

internal/provider/resource_vpc_firewall_rules_test.go

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ package provider
77
import (
88
"context"
99
"fmt"
10+
"net/http"
11+
"net/http/httptest"
12+
"path/filepath"
13+
"sync/atomic"
1014
"testing"
1115
"time"
1216

@@ -611,3 +615,225 @@ func testAccFirewallRulesDestroy(s *terraform.State) error {
611615

612616
return nil
613617
}
618+
619+
func TestFirewallRules_v0_upgrade(t *testing.T) {
620+
var version atomic.Int32
621+
version.Store(15)
622+
623+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
624+
var respFile string
625+
switch version.Load() {
626+
case 15:
627+
respFile = "r15_get_vpc_firewall_rules.json"
628+
case 16:
629+
respFile = "r16_get_vpc_firewall_rules.json"
630+
}
631+
632+
respPath := filepath.Join("test-fixtures", "resource_vpc_firewall_rules", respFile)
633+
f, err := testFixtures.ReadFile(respPath)
634+
if err != nil {
635+
t.Errorf("failed to read file %q: %v", respPath, err)
636+
http.Error(w, "failed to read file", http.StatusInternalServerError)
637+
return
638+
}
639+
640+
w.WriteHeader(http.StatusOK)
641+
_, _ = w.Write(f)
642+
}))
643+
t.Cleanup(func() {
644+
ts.Close()
645+
})
646+
647+
//lintignore:AT004 // Provider must connect to test server.
648+
configV0 := fmt.Sprintf(`
649+
provider "oxide" {
650+
host = "%s"
651+
token = "fake"
652+
}
653+
654+
resource "oxide_vpc_firewall_rules" "test" {
655+
vpc_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
656+
rules = [
657+
{
658+
action = "allow"
659+
name = "single-protocol"
660+
description = "Single protocol"
661+
direction = "inbound"
662+
priority = 65535
663+
status = "enabled"
664+
filters = {
665+
ports = ["22"]
666+
protocols = ["TCP"]
667+
}
668+
targets = [
669+
{
670+
type = "subnet"
671+
value = "default"
672+
}
673+
]
674+
},
675+
{
676+
action = "allow"
677+
name = "multiple-protocols"
678+
description = "Multiple protocols"
679+
direction = "inbound"
680+
priority = 65535
681+
status = "enabled"
682+
filters = {
683+
ports = ["22"]
684+
protocols = ["TCP", "UDP"]
685+
}
686+
targets = [
687+
{
688+
type = "subnet"
689+
value = "default"
690+
}
691+
]
692+
},
693+
{
694+
action = "allow"
695+
name = "no-protocol"
696+
description = "No protocol"
697+
direction = "inbound"
698+
priority = 65535
699+
status = "enabled"
700+
filters = {
701+
ports = ["22"]
702+
}
703+
targets = [
704+
{
705+
type = "subnet"
706+
value = "default"
707+
}
708+
]
709+
}
710+
]
711+
}
712+
`, ts.URL)
713+
714+
//lintignore:AT004 // Provider must connect to test server.
715+
configV1 := fmt.Sprintf(`
716+
provider "oxide" {
717+
host = "%s"
718+
token = "fake"
719+
}
720+
721+
resource "oxide_vpc_firewall_rules" "test" {
722+
vpc_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
723+
rules = [
724+
{
725+
action = "allow"
726+
name = "single-protocol"
727+
description = "Single protocol"
728+
direction = "inbound"
729+
priority = 65535
730+
status = "enabled"
731+
filters = {
732+
ports = ["22"]
733+
protocols = [
734+
{ type : "tcp" }
735+
]
736+
}
737+
targets = [
738+
{
739+
type = "subnet"
740+
value = "default"
741+
}
742+
]
743+
},
744+
{
745+
action = "allow"
746+
name = "multiple-protocols"
747+
description = "Multiple protocols"
748+
direction = "inbound"
749+
priority = 65535
750+
status = "enabled"
751+
filters = {
752+
ports = ["22"]
753+
protocols = [
754+
{ type : "tcp" },
755+
{ type : "udp" }
756+
]
757+
}
758+
targets = [
759+
{
760+
type = "subnet"
761+
value = "default"
762+
}
763+
]
764+
},
765+
{
766+
action = "allow"
767+
name = "no-protocol"
768+
description = "No protocol"
769+
direction = "inbound"
770+
priority = 65535
771+
status = "enabled"
772+
filters = {
773+
ports = ["22"]
774+
}
775+
targets = [
776+
{
777+
type = "subnet"
778+
value = "default"
779+
}
780+
]
781+
}
782+
]
783+
}
784+
`, ts.URL)
785+
786+
resource.UnitTest(t, resource.TestCase{
787+
Steps: []resource.TestStep{
788+
// Initial state with v0.12.0 and R15.
789+
{
790+
ExternalProviders: map[string]resource.ExternalProvider{
791+
"oxide": {
792+
Source: "oxidecomputer/oxide",
793+
VersionConstraint: "0.12.0",
794+
},
795+
},
796+
PreConfig: func() {
797+
version.Store(15)
798+
},
799+
Config: configV0,
800+
},
801+
// Upgrade to current version and R16.
802+
{
803+
ExternalProviders: map[string]resource.ExternalProvider{},
804+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(),
805+
PreConfig: func() {
806+
version.Store(16)
807+
},
808+
Config: configV1,
809+
},
810+
},
811+
})
812+
813+
resource.UnitTest(t, resource.TestCase{
814+
Steps: []resource.TestStep{
815+
// Initial state with v0.13.0 and R16.
816+
{
817+
ExternalProviders: map[string]resource.ExternalProvider{
818+
"oxide": {
819+
Source: "oxidecomputer/oxide",
820+
VersionConstraint: "0.13.0",
821+
},
822+
},
823+
PreConfig: func() {
824+
version.Store(16)
825+
},
826+
Config: configV1,
827+
},
828+
// Upgrade to current version.
829+
{
830+
ExternalProviders: map[string]resource.ExternalProvider{},
831+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(),
832+
PreConfig: func() {
833+
version.Store(16)
834+
},
835+
Config: configV1,
836+
},
837+
},
838+
})
839+
}

0 commit comments

Comments
 (0)