Skip to content

Commit 6bcf876

Browse files
authored
Add pg-major-version flag to mpg create (#4701)
1 parent e330d0a commit 6bcf876

File tree

3 files changed

+299
-0
lines changed

3 files changed

+299
-0
lines changed

internal/command/mpg/create.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"sort"
7+
"strconv"
78
"strings"
89
"time"
910

@@ -27,6 +28,7 @@ type CreateClusterParams struct {
2728
Plan string
2829
VolumeSizeGB int
2930
PostGISEnabled bool
31+
PGMajorVersion int
3032
}
3133

3234
func newCreate() *cobra.Command {
@@ -62,6 +64,11 @@ func newCreate() *cobra.Command {
6264
Description: "Enable PostGIS for the Postgres cluster",
6365
Default: false,
6466
},
67+
flag.Int{
68+
Name: "pg-major-version",
69+
Description: "The major version of Postgres to use for the Postgres cluster. Supported versions are 16 and 17.",
70+
Default: 16,
71+
},
6572
)
6673

6774
return cmd
@@ -109,6 +116,11 @@ func runCreate(ctx context.Context) error {
109116
return fmt.Errorf("no valid regions found for Managed Postgres")
110117
}
111118

119+
pgMajorVersion := flag.GetInt(ctx, "pg-major-version")
120+
if pgMajorVersion != 16 && pgMajorVersion != 17 {
121+
return fmt.Errorf("invalid Postgres major version: %d. Supported versions are 16 and 17", pgMajorVersion)
122+
}
123+
112124
// Check if region was specified via flag
113125
regionCode := flag.GetString(ctx, "region")
114126
var selectedRegion *fly.Region
@@ -197,6 +209,7 @@ func runCreate(ctx context.Context) error {
197209
Plan: plan,
198210
VolumeSizeGB: flag.GetInt(ctx, "volume-size"),
199211
PostGISEnabled: flag.GetBool(ctx, "enable-postgis-support"),
212+
PGMajorVersion: pgMajorVersion,
200213
}
201214

202215
uiexClient := uiexutil.ClientFromContext(ctx)
@@ -208,6 +221,7 @@ func runCreate(ctx context.Context) error {
208221
OrgSlug: params.OrgSlug,
209222
Disk: params.VolumeSizeGB,
210223
PostGISEnabled: params.PostGISEnabled,
224+
PGMajorVersion: strconv.Itoa(params.PGMajorVersion),
211225
}
212226

213227
response, err := uiexClient.CreateCluster(ctx, input)

internal/command/mpg/mpg_test.go

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"strconv"
78
"testing"
89

910
"github.com/spf13/cobra"
@@ -921,3 +922,286 @@ func TestBackupList(t *testing.T) {
921922
assert.Equal(t, "backup-1", backups[0].Id)
922923
assert.Equal(t, "backup-2", backups[1].Id)
923924
}
925+
926+
// Test PG major version validation logic
927+
func TestPGMajorVersionValidation(t *testing.T) {
928+
tests := []struct {
929+
name string
930+
version int
931+
expectError bool
932+
errorMsg string
933+
}{
934+
{
935+
name: "valid version 16",
936+
version: 16,
937+
expectError: false,
938+
},
939+
{
940+
name: "valid version 17",
941+
version: 17,
942+
expectError: false,
943+
},
944+
{
945+
name: "invalid version 15",
946+
version: 15,
947+
expectError: true,
948+
errorMsg: "invalid Postgres major version: 15. Supported versions are 16 and 17",
949+
},
950+
{
951+
name: "invalid version 18",
952+
version: 18,
953+
expectError: true,
954+
errorMsg: "invalid Postgres major version: 18. Supported versions are 16 and 17",
955+
},
956+
{
957+
name: "invalid version 14",
958+
version: 14,
959+
expectError: true,
960+
errorMsg: "invalid Postgres major version: 14. Supported versions are 16 and 17",
961+
},
962+
{
963+
name: "invalid version 0",
964+
version: 0,
965+
expectError: true,
966+
errorMsg: "invalid Postgres major version: 0. Supported versions are 16 and 17",
967+
},
968+
}
969+
970+
for _, tt := range tests {
971+
t.Run(tt.name, func(t *testing.T) {
972+
// Test the validation logic directly (matching lines 119-122 in create.go)
973+
if tt.version != 16 && tt.version != 17 {
974+
if !tt.expectError {
975+
t.Errorf("expected error for version %d", tt.version)
976+
return
977+
}
978+
err := fmt.Errorf("invalid Postgres major version: %d. Supported versions are 16 and 17", tt.version)
979+
if tt.errorMsg != "" && err.Error() != tt.errorMsg {
980+
t.Errorf("expected error message '%s', got '%s'", tt.errorMsg, err.Error())
981+
}
982+
} else {
983+
if tt.expectError {
984+
t.Errorf("did not expect error for version %d", tt.version)
985+
}
986+
}
987+
})
988+
}
989+
}
990+
991+
// Test that PG major version is correctly passed to CreateClusterParams
992+
func TestCreateClusterParams_PGMajorVersion(t *testing.T) {
993+
tests := []struct {
994+
name string
995+
pgMajorVersion int
996+
expectedVersion int
997+
}{
998+
{
999+
name: "version 16",
1000+
pgMajorVersion: 16,
1001+
expectedVersion: 16,
1002+
},
1003+
{
1004+
name: "version 17",
1005+
pgMajorVersion: 17,
1006+
expectedVersion: 17,
1007+
},
1008+
}
1009+
1010+
for _, tt := range tests {
1011+
t.Run(tt.name, func(t *testing.T) {
1012+
params := &CreateClusterParams{
1013+
Name: "test-db",
1014+
OrgSlug: "test-org",
1015+
Region: "ord",
1016+
Plan: "basic",
1017+
VolumeSizeGB: 10,
1018+
PostGISEnabled: false,
1019+
PGMajorVersion: tt.pgMajorVersion,
1020+
}
1021+
1022+
assert.Equal(t, tt.expectedVersion, params.PGMajorVersion)
1023+
})
1024+
}
1025+
}
1026+
1027+
// Test that PG major version is correctly converted to string in CreateClusterInput
1028+
func TestCreateClusterInput_PGMajorVersion(t *testing.T) {
1029+
tests := []struct {
1030+
name string
1031+
pgMajorVersion int
1032+
expectedVersion string
1033+
}{
1034+
{
1035+
name: "version 16 as string",
1036+
pgMajorVersion: 16,
1037+
expectedVersion: "16",
1038+
},
1039+
{
1040+
name: "version 17 as string",
1041+
pgMajorVersion: 17,
1042+
expectedVersion: "17",
1043+
},
1044+
}
1045+
1046+
for _, tt := range tests {
1047+
t.Run(tt.name, func(t *testing.T) {
1048+
params := &CreateClusterParams{
1049+
PGMajorVersion: tt.pgMajorVersion,
1050+
}
1051+
1052+
// Simulate the conversion that happens in create.go line 224
1053+
input := uiex.CreateClusterInput{
1054+
PGMajorVersion: strconv.Itoa(params.PGMajorVersion),
1055+
}
1056+
1057+
assert.Equal(t, tt.expectedVersion, input.PGMajorVersion)
1058+
})
1059+
}
1060+
}
1061+
1062+
// Test CreateCluster command with pg-major-version flag
1063+
func TestCreateCommand_WithPGMajorVersion(t *testing.T) {
1064+
tests := []struct {
1065+
name string
1066+
pgMajorVersion int
1067+
expectError bool
1068+
expectedVersion string
1069+
}{
1070+
{
1071+
name: "default version 16",
1072+
pgMajorVersion: 16,
1073+
expectError: false,
1074+
expectedVersion: "16",
1075+
},
1076+
{
1077+
name: "explicit version 16",
1078+
pgMajorVersion: 16,
1079+
expectError: false,
1080+
expectedVersion: "16",
1081+
},
1082+
{
1083+
name: "version 17",
1084+
pgMajorVersion: 17,
1085+
expectError: false,
1086+
expectedVersion: "17",
1087+
},
1088+
}
1089+
1090+
for _, tt := range tests {
1091+
t.Run(tt.name, func(t *testing.T) {
1092+
ctx := setupTestContext()
1093+
1094+
// Add pg-major-version flag to the flag set
1095+
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
1096+
flagSet.Int("pg-major-version", tt.pgMajorVersion, "PG major version")
1097+
flagSet.String("name", "test-db", "Cluster name")
1098+
flagSet.String("region", "ord", "Region")
1099+
flagSet.String("plan", "basic", "Plan")
1100+
flagSet.Int("volume-size", 10, "Volume size")
1101+
flagSet.Bool("enable-postgis-support", false, "PostGIS")
1102+
ctx = flagctx.NewContext(ctx, flagSet)
1103+
1104+
// Add macaroon tokens for MPG compatibility
1105+
macaroonTokens := tokens.Parse("fm1r_macaroon_token")
1106+
configWithMacaroonTokens := &config.Config{
1107+
Tokens: macaroonTokens,
1108+
}
1109+
ctx = config.NewContext(ctx, configWithMacaroonTokens)
1110+
1111+
mpgRegions := []uiex.MPGRegion{
1112+
{Code: "ord", Available: true},
1113+
}
1114+
1115+
var capturedInput uiex.CreateClusterInput
1116+
mockUiex := &mock.UiexClient{
1117+
ListMPGRegionsFunc: func(ctx context.Context, orgSlug string) (uiex.ListMPGRegionsResponse, error) {
1118+
return uiex.ListMPGRegionsResponse{
1119+
Data: mpgRegions,
1120+
}, nil
1121+
},
1122+
CreateClusterFunc: func(ctx context.Context, input uiex.CreateClusterInput) (uiex.CreateClusterResponse, error) {
1123+
capturedInput = input
1124+
return uiex.CreateClusterResponse{
1125+
Data: struct {
1126+
Id string `json:"id"`
1127+
Name string `json:"name"`
1128+
Status *string `json:"status"`
1129+
Plan string `json:"plan"`
1130+
Environment *string `json:"environment"`
1131+
Region string `json:"region"`
1132+
Organization fly.Organization `json:"organization"`
1133+
Replicas int `json:"replicas"`
1134+
Disk int `json:"disk"`
1135+
IpAssignments uiex.ManagedClusterIpAssignments `json:"ip_assignments"`
1136+
PostGISEnabled bool `json:"postgis_enabled"`
1137+
}{
1138+
Id: "test-cluster-123",
1139+
Name: "test-db",
1140+
Region: "ord",
1141+
Plan: "basic",
1142+
PostGISEnabled: false,
1143+
},
1144+
}, nil
1145+
},
1146+
GetManagedClusterByIdFunc: func(ctx context.Context, id string) (uiex.GetManagedClusterResponse, error) {
1147+
status := "ready"
1148+
return uiex.GetManagedClusterResponse{
1149+
Data: uiex.ManagedCluster{
1150+
Id: id,
1151+
Status: status,
1152+
},
1153+
Credentials: uiex.GetManagedClusterCredentialsResponse{
1154+
ConnectionUri: "postgresql://test",
1155+
},
1156+
}, nil
1157+
},
1158+
}
1159+
1160+
ctx = uiexutil.NewContextWithClient(ctx, mockUiex)
1161+
1162+
// Test the validation logic
1163+
pgMajorVersion := tt.pgMajorVersion
1164+
if pgMajorVersion != 16 && pgMajorVersion != 17 {
1165+
if !tt.expectError {
1166+
t.Errorf("expected error for version %d", pgMajorVersion)
1167+
}
1168+
return
1169+
}
1170+
1171+
// Test that the version is correctly passed to CreateClusterInput
1172+
params := &CreateClusterParams{
1173+
PGMajorVersion: pgMajorVersion,
1174+
}
1175+
1176+
input := uiex.CreateClusterInput{
1177+
PGMajorVersion: strconv.Itoa(params.PGMajorVersion),
1178+
}
1179+
1180+
assert.Equal(t, tt.expectedVersion, input.PGMajorVersion, "PG major version should be correctly converted to string")
1181+
1182+
// Verify the version would be passed correctly in actual CreateCluster call
1183+
_, err := mockUiex.CreateCluster(ctx, input)
1184+
if tt.expectError {
1185+
assert.Error(t, err)
1186+
} else {
1187+
require.NoError(t, err)
1188+
assert.Equal(t, tt.expectedVersion, capturedInput.PGMajorVersion, "PG major version should be correctly passed to CreateCluster")
1189+
}
1190+
})
1191+
}
1192+
}
1193+
1194+
// Test invalid PG major version error message
1195+
func TestInvalidPGMajorVersion_Error(t *testing.T) {
1196+
invalidVersions := []int{15, 18, 14, 13, 19, 0, -1}
1197+
1198+
for _, version := range invalidVersions {
1199+
t.Run(fmt.Sprintf("version_%d", version), func(t *testing.T) {
1200+
err := fmt.Errorf("invalid Postgres major version: %d. Supported versions are 16 and 17", version)
1201+
assert.Error(t, err)
1202+
assert.Contains(t, err.Error(), "invalid Postgres major version")
1203+
assert.Contains(t, err.Error(), "Supported versions are 16 and 17")
1204+
assert.Contains(t, err.Error(), fmt.Sprintf("%d", version))
1205+
})
1206+
}
1207+
}

internal/uiex/managed_postgres.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,7 @@ type CreateClusterInput struct {
603603
OrgSlug string `json:"org_slug"`
604604
Disk int `json:"disk"`
605605
PostGISEnabled bool `json:"postgis_enabled"`
606+
PGMajorVersion string `json:"pg_major_version"`
606607
}
607608

608609
type CreateClusterResponse struct {

0 commit comments

Comments
 (0)