From c4b0981b70fa6b56cf1efbba2a83bf95efb730d9 Mon Sep 17 00:00:00 2001 From: tarunipaleru Date: Wed, 29 Oct 2025 17:14:21 -0400 Subject: [PATCH 1/3] Add fleet ID support with automatic detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change adds support for the fleetId field in deployment configurations and implements automatic fleet detection when only one fleet exists. Changes: - Add --fleet-id flag to deploy and deployment create commands - Add FleetId field to CreateDeploymentConfig struct - Implement auto-detection of fleet ID when there's only 1 fleet in account - Update Merge methods to copy fleet ID from latest deployment - Add validation requiring fleet ID if not auto-detected or from latest - Pass FleetId to CreateDeployment API calls Fleet ID resolution priority: 1. Explicitly provided via --fleet-id flag 2. From latest deployment when using --from-latest 3. Auto-detected when account has exactly 1 fleet 4. Validation error if none of the above 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/commands/deploy.go | 22 +++++++++++++ internal/commands/deployment.go | 55 +++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/internal/commands/deploy.go b/internal/commands/deploy.go index 2fd25b2..0292fd9 100644 --- a/internal/commands/deploy.go +++ b/internal/commands/deploy.go @@ -32,6 +32,7 @@ var Deploy = &cli.Command{ envVarsFlag, idleTimeoutFlag, deploymentTagFlag, + fleetIdFlag, ), UsageText: `hathora deploy [options]`, Action: func(ctx context.Context, cmd *cli.Command) error { @@ -52,6 +53,18 @@ var Deploy = &cli.Command{ deploy.Merge(res, cmd.IsSet(idleTimeoutFlag.Name)) } + // Auto-detect fleet ID if not provided and not merged from latest + if deploy.FleetId == "" { + fleetId, err := autoDetectFleetId(ctx, deploy.SDK, deploy.AppID) + if err != nil { + return fmt.Errorf("failed to auto-detect fleet: %w", err) + } + if fleetId != "" { + deploy.FleetId = fleetId + deploy.Log.Debug("auto-detected fleet", zap.String("fleet.id", fleetId)) + } + } + if err := deploy.Validate(); err != nil { //nolint:errcheck cli.ShowSubcommandHelp(cmd) @@ -74,6 +87,7 @@ var Deploy = &cli.Command{ ctx, components.DeploymentConfigV3{ BuildID: createdBuild.BuildID, + FleetId: deploy.FleetId, IdleTimeoutEnabled: *deploy.IdleTimeoutEnabled, RoomsPerProcess: deploy.RoomsPerProcess, TransportType: deploy.TransportType, @@ -129,6 +143,10 @@ func (c *DeployConfig) Merge(latest *components.DeploymentV3, isIdleTimeoutDefau return } + if c.FleetId == "" { + c.FleetId = latest.FleetId + } + if !isIdleTimeoutDefault { c.IdleTimeoutEnabled = &latest.IdleTimeoutEnabled } @@ -176,6 +194,10 @@ func (c *DeployConfig) Validate() error { err = errors.Join(err, missingRequiredFlag(appIDFlag.Name)) } + if c.FleetId == "" { + err = errors.Join(err, missingRequiredFlag(fleetIdFlag.Name)) + } + if c.RoomsPerProcess == 0 { err = errors.Join(err, missingRequiredFlag(roomsPerProcessFlag.Name)) } diff --git a/internal/commands/deployment.go b/internal/commands/deployment.go index 66b6ab7..c361146 100644 --- a/internal/commands/deployment.go +++ b/internal/commands/deployment.go @@ -140,6 +140,7 @@ var Deployment = &cli.Command{ envVarsFlag, fromLatestFlag, deploymentTagFlag, + fleetIdFlag, ), Action: func(ctx context.Context, cmd *cli.Command) error { zap.L().Debug("creating a deployment...") @@ -160,6 +161,18 @@ var Deployment = &cli.Command{ deployment.Merge(res) } + // Auto-detect fleet ID if not provided and not merged from latest + if deployment.FleetId == "" { + fleetId, err := autoDetectFleetId(ctx, deployment.SDK, deployment.AppID) + if err != nil { + return fmt.Errorf("failed to auto-detect fleet: %w", err) + } + if fleetId != "" { + deployment.FleetId = fleetId + deployment.Log.Debug("auto-detected fleet", zap.String("fleet.id", fleetId)) + } + } + if err := deployment.Validate(); err != nil { //nolint:errcheck cli.ShowSubcommandHelp(cmd) @@ -177,6 +190,7 @@ var Deployment = &cli.Command{ ctx, components.DeploymentConfigV3{ BuildID: deployment.BuildID, + FleetId: deployment.FleetId, IdleTimeoutEnabled: *deployment.IdleTimeoutEnabled, RoomsPerProcess: deployment.RoomsPerProcess, TransportType: deployment.TransportType, @@ -327,6 +341,17 @@ var ( Usage: "arbitrary metadata associated with a deployment", Category: "Deployment:", } + + fleetIdFlag = &cli.StringFlag{ + Name: "fleet-id", + Sources: cli.NewValueSourceChain( + cli.EnvVar(deploymentEnvVar("FLEET_ID")), + altsrc.ConfigFile(configFlag.Name, "deployment.fleet-id"), + ), + Usage: "the `` of the fleet", + Persistent: true, + Category: "Deployment:", + } ) func parseContainerPorts(ports []string) ([]components.ContainerPort, error) { @@ -465,6 +490,7 @@ var ( type CreateDeploymentConfig struct { *DeploymentConfig BuildID string + FleetId string IdleTimeoutEnabled *bool RoomsPerProcess int TransportType components.TransportType @@ -487,6 +513,7 @@ func (c *CreateDeploymentConfig) Load(cmd *cli.Command) error { c.DeploymentConfig = deployment c.BuildID = cmd.String(buildIDFlag.Name) + c.FleetId = cmd.String(fleetIdFlag.Name) // Value of the idleTimeoutFlag by priority, high to low // Passed in as an argument @@ -534,6 +561,10 @@ func (c *CreateDeploymentConfig) Merge(latest *components.DeploymentV3) { c.BuildID = latest.BuildID } + if c.FleetId == "" { + c.FleetId = latest.FleetId + } + if c.IdleTimeoutEnabled == nil { c.IdleTimeoutEnabled = &latest.IdleTimeoutEnabled } @@ -585,6 +616,10 @@ func (c *CreateDeploymentConfig) Validate() error { err = errors.Join(err, missingRequiredFlag(buildIDFlag.Name)) } + if c.FleetId == "" { + err = errors.Join(err, missingRequiredFlag(fleetIdFlag.Name)) + } + if c.RoomsPerProcess == 0 { err = errors.Join(err, missingRequiredFlag(roomsPerProcessFlag.Name)) } @@ -635,3 +670,23 @@ func (c *CreateDeploymentConfig) New() LoadableConfig { func CreateDeploymentConfigFrom(cmd *cli.Command) (*CreateDeploymentConfig, error) { return ConfigFromCLI[*CreateDeploymentConfig](createDeploymentConfigKey, cmd) } + +// autoDetectFleetId attempts to auto-detect the fleet ID when there's only one fleet in the account. +// Returns the fleet ID if exactly one fleet exists, otherwise returns an empty string. +func autoDetectFleetId(ctx context.Context, sdk *sdk.HathoraCloud, appID *string) (string, error) { + if appID == nil || *appID == "" { + return "", nil + } + + res, err := sdk.FleetsV1.GetFleets(ctx, nil) + if err != nil { + // If fleet listing fails, just return empty string and let validation handle it + return "", nil + } + + if len(res.Fleets) == 1 { + return res.Fleets[0].FleetId, nil + } + + return "", nil +} From cd5cd12739b7733d4eaf07bd6425e32807fe53b7 Mon Sep 17 00:00:00 2001 From: tarunipaleru Date: Wed, 29 Oct 2025 17:26:14 -0400 Subject: [PATCH 2/3] Add missing requestedGPU flag to deployment create command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deployment create command was missing the --requested-gpu flag even though the struct, load method, and API call already supported it. This adds the flag to make GPU configuration available for both deploy and deployment create commands. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/commands/deployment.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/commands/deployment.go b/internal/commands/deployment.go index c361146..d773b43 100644 --- a/internal/commands/deployment.go +++ b/internal/commands/deployment.go @@ -136,6 +136,7 @@ var Deployment = &cli.Command{ containerPortFlag, requestedMemoryFlag, requestedCPUFlag, + requestedGPUFlag, additionalContainerPortsFlag, envVarsFlag, fromLatestFlag, From a123fc8c9c9b25ca1ada348e39c03040c23c4f48 Mon Sep 17 00:00:00 2001 From: Zach Langbert Date: Thu, 30 Oct 2025 20:19:41 -0400 Subject: [PATCH 3/3] fixes --- go.mod | 5 ++- go.sum | 12 +++---- internal/commands/build_test.go | 15 +++++--- internal/commands/deploy.go | 23 ++++++++----- internal/commands/deployment.go | 51 +++++++++++++++++----------- internal/commands/deployment_test.go | 47 +++++++++++-------------- internal/output/text_test.go | 14 ++++---- 7 files changed, 91 insertions(+), 76 deletions(-) diff --git a/go.mod b/go.mod index c542b9d..786cec9 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,8 @@ require ( github.com/fatih/color v1.18.0 github.com/h2non/filetype v1.1.3 github.com/hashicorp/go-cleanhttp v0.5.2 - github.com/hathora/cloud-sdk-go/hathoracloud v0.3.20 - github.com/stretchr/testify v1.10.0 + github.com/hathora/cloud-sdk-go/hathoracloud v0.13.0 + github.com/stretchr/testify v1.11.1 github.com/urfave/cli/v3 v3.0.0-alpha9 go.uber.org/zap v1.27.0 golang.org/x/sync v0.14.0 @@ -21,7 +21,6 @@ require ( require ( github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 50730a7..727b717 100644 --- a/go.sum +++ b/go.sum @@ -6,24 +6,24 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 h1:R/ZjJpjQKsZ6L/+Gf9WHbt31GG8NMVcpRqUE+1mMIyo= -github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731/go.mod h1:M9R1FoZ3y//hwwnJtO51ypFGwm8ZfpxPT/ZLtO1mcgQ= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hathora/cloud-sdk-go/hathoracloud v0.3.20 h1:HkPz8vue5M4Qkff7ACYbhDQqwtXEqvCMnjEXP7cerVo= -github.com/hathora/cloud-sdk-go/hathoracloud v0.3.20/go.mod h1:scOHgTK/ylPtgg39LsTpuUlmwop7tsGlLEP7vBd0Tlk= +github.com/hathora/cloud-sdk-go/hathoracloud v0.12.0 h1:Fb4uTDn3J1FdZiA6TFuwV2ySwkH3QNHKDr1eGA/p8Qc= +github.com/hathora/cloud-sdk-go/hathoracloud v0.12.0/go.mod h1:16HoEwQ/NvaC4+tG6aHHTbgAUm3UP1cQ7cJtZ+RwUZQ= +github.com/hathora/cloud-sdk-go/hathoracloud v0.13.0 h1:002OLIIPPv43XAgfBl0D+A3l+1GgI7Kfw2nASbGcDYU= +github.com/hathora/cloud-sdk-go/hathoracloud v0.13.0/go.mod h1:16HoEwQ/NvaC4+tG6aHHTbgAUm3UP1cQ7cJtZ+RwUZQ= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/urfave/cli/v3 v3.0.0-alpha9 h1:P0RMy5fQm1AslQS+XCmy9UknDXctOmG/q/FZkUFnJSo= github.com/urfave/cli/v3 v3.0.0-alpha9/go.mod h1:0kK/RUFHyh+yIKSfWxwheGndfnrvYSmYFVeKCh03ZUc= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= diff --git a/internal/commands/build_test.go b/internal/commands/build_test.go index a6825f6..b944024 100644 --- a/internal/commands/build_test.go +++ b/internal/commands/build_test.go @@ -59,7 +59,8 @@ func Test_Integration_BuildCommands_Happy(t *testing.T) { "createdAt": "2019-08-24T14:15:22Z", "createdBy": "google-oauth2|107030234048588177467", "buildId": "bld-1", - "appId": "app-af469a92-5b45-4565-b3c4-b79878de67d2" + "appId": "app-af469a92-5b45-4565-b3c4-b79878de67d2", + "orgId": "org-1234567890" }`, expectRequest: func(t *testing.T, r *http.Request, requestBody *json.RawMessage) { assert.Equal(t, r.Method, http.MethodGet, "request method should be GET") @@ -89,7 +90,8 @@ func Test_Integration_BuildCommands_Happy(t *testing.T) { "createdAt": "2019-08-24T14:15:22Z", "createdBy": "google-oauth2|107030234048588177467", "buildId": "bld-1", - "appId": "app-af469a92-5b45-4565-b3c4-b79878de67d2" + "appId": "app-af469a92-5b45-4565-b3c4-b79878de67d2", + "orgId": "org-1234567890" } ] }`, @@ -121,7 +123,8 @@ func Test_Integration_BuildCommands_Happy(t *testing.T) { "createdAt": "2019-08-24T14:15:22Z", "createdBy": "google-oauth2|107030234048588177467", "buildId": "bld-1", - "appId": "app-af469a92-5b45-4565-b3c4-b79878de67d2" + "appId": "app-af469a92-5b45-4565-b3c4-b79878de67d2", + "orgId": "org-1234567890" }`, expectRequest: func(t *testing.T, r *http.Request, requestBody *json.RawMessage) { assert.Equal(t, r.Method, http.MethodPost, "request method should be POST") @@ -214,7 +217,8 @@ func Test_Integration_BuildCommands_GlobalArgs(t *testing.T) { "createdAt": "2019-08-24T14:15:22Z", "createdBy": "google-oauth2|107030234048588177467", "buildId": "bld-1", - "appId": "app-af469a92-5b45-4565-b3c4-b79878de67d2" + "appId": "app-af469a92-5b45-4565-b3c4-b79878de67d2", + "orgId": "org-1234567890" }`, expectRequest: func(t *testing.T, r *http.Request, requestBody *json.RawMessage) { assert.Equal(t, r.Method, http.MethodGet, "request method should be GET") @@ -242,7 +246,8 @@ func Test_Integration_BuildCommands_GlobalArgs(t *testing.T) { "createdAt": "2019-08-24T14:15:22Z", "createdBy": "google-oauth2|107030234048588177467", "buildId": "bld-1", - "appId": "app-af469a92-5b45-4565-b3c4-b79878de67d2" + "appId": "app-af469a92-5b45-4565-b3c4-b79878de67d2", + "orgId": "org-1234567890" }`, expectRequest: func(t *testing.T, r *http.Request, requestBody *json.RawMessage) { assert.Equal(t, r.Method, http.MethodGet, "request method should be GET") diff --git a/internal/commands/deploy.go b/internal/commands/deploy.go index 0292fd9..c8160b1 100644 --- a/internal/commands/deploy.go +++ b/internal/commands/deploy.go @@ -53,15 +53,15 @@ var Deploy = &cli.Command{ deploy.Merge(res, cmd.IsSet(idleTimeoutFlag.Name)) } - // Auto-detect fleet ID if not provided and not merged from latest + // If we didn't get a fleet ID from either its flag or the latest deployment, + // fallback to the org's default fleet ID. if deploy.FleetId == "" { - fleetId, err := autoDetectFleetId(ctx, deploy.SDK, deploy.AppID) + defaultFleetId, err := getOrgDefaultFleetId(ctx, deploy.SDK, deploy.AppID) if err != nil { - return fmt.Errorf("failed to auto-detect fleet: %w", err) + return fmt.Errorf("failed to get default fleet ID: %w", err) } - if fleetId != "" { - deploy.FleetId = fleetId - deploy.Log.Debug("auto-detected fleet", zap.String("fleet.id", fleetId)) + if defaultFleetId != "" { + deploy.FleetId = defaultFleetId } } @@ -81,13 +81,18 @@ var Deploy = &cli.Command{ deploymentTag = &deploy.DeploymentTag } + var fleetID *string + if deploy.FleetId != "" { + fleetID = &deploy.FleetId + } + gpu := float64(deploy.RequestedGPU) res, err := deploy.SDK.DeploymentsV3.CreateDeployment( ctx, components.DeploymentConfigV3{ BuildID: createdBuild.BuildID, - FleetId: deploy.FleetId, + FleetID: fleetID, IdleTimeoutEnabled: *deploy.IdleTimeoutEnabled, RoomsPerProcess: deploy.RoomsPerProcess, TransportType: deploy.TransportType, @@ -144,7 +149,9 @@ func (c *DeployConfig) Merge(latest *components.DeploymentV3, isIdleTimeoutDefau } if c.FleetId == "" { - c.FleetId = latest.FleetId + if latest.FleetID != nil { + c.FleetId = *latest.FleetID + } } if !isIdleTimeoutDefault { diff --git a/internal/commands/deployment.go b/internal/commands/deployment.go index d773b43..168ddeb 100644 --- a/internal/commands/deployment.go +++ b/internal/commands/deployment.go @@ -112,7 +112,7 @@ var Deployment = &cli.Command{ } deployment.Log.Debug("getting all deployments...") - res, err := deployment.SDK.DeploymentsV3.GetDeployments(ctx, deployment.AppID, nil) + res, err := deployment.SDK.DeploymentsV3.GetDeployments(ctx, deployment.AppID, nil, nil) if err != nil { return fmt.Errorf("failed to get deployments: %w", err) } @@ -162,15 +162,15 @@ var Deployment = &cli.Command{ deployment.Merge(res) } - // Auto-detect fleet ID if not provided and not merged from latest + // If we didn't get a fleet ID from either its flag or the latest deployment, + // fallback to the org's default fleet ID. if deployment.FleetId == "" { - fleetId, err := autoDetectFleetId(ctx, deployment.SDK, deployment.AppID) + defaultFleetId, err := getOrgDefaultFleetId(ctx, deployment.SDK, deployment.AppID) if err != nil { - return fmt.Errorf("failed to auto-detect fleet: %w", err) + return fmt.Errorf("failed to get default fleet ID: %w", err) } - if fleetId != "" { - deployment.FleetId = fleetId - deployment.Log.Debug("auto-detected fleet", zap.String("fleet.id", fleetId)) + if defaultFleetId != "" { + deployment.FleetId = defaultFleetId } } @@ -185,13 +185,18 @@ var Deployment = &cli.Command{ deploymentTag = &deployment.DeploymentTag } + var fleetID *string + if deployment.FleetId != "" { + fleetID = &deployment.FleetId + } + gpu := float64(deployment.RequestedGPU) res, err := deployment.SDK.DeploymentsV3.CreateDeployment( ctx, components.DeploymentConfigV3{ BuildID: deployment.BuildID, - FleetId: deployment.FleetId, + FleetID: fleetID, IdleTimeoutEnabled: *deployment.IdleTimeoutEnabled, RoomsPerProcess: deployment.RoomsPerProcess, TransportType: deployment.TransportType, @@ -563,7 +568,9 @@ func (c *CreateDeploymentConfig) Merge(latest *components.DeploymentV3) { } if c.FleetId == "" { - c.FleetId = latest.FleetId + if latest.FleetID != nil { + c.FleetId = *latest.FleetID + } } if c.IdleTimeoutEnabled == nil { @@ -672,22 +679,28 @@ func CreateDeploymentConfigFrom(cmd *cli.Command) (*CreateDeploymentConfig, erro return ConfigFromCLI[*CreateDeploymentConfig](createDeploymentConfigKey, cmd) } -// autoDetectFleetId attempts to auto-detect the fleet ID when there's only one fleet in the account. -// Returns the fleet ID if exactly one fleet exists, otherwise returns an empty string. -func autoDetectFleetId(ctx context.Context, sdk *sdk.HathoraCloud, appID *string) (string, error) { +func getOrgDefaultFleetId(ctx context.Context, sdk *sdk.HathoraCloud, appID *string) (string, error) { if appID == nil || *appID == "" { - return "", nil + return "", fmt.Errorf("app ID is required") } - res, err := sdk.FleetsV1.GetFleets(ctx, nil) + app, err := sdk.AppsV2.GetApp(ctx, appID) if err != nil { - // If fleet listing fails, just return empty string and let validation handle it - return "", nil + return "", fmt.Errorf("failed to get org: %w", err) } - if len(res.Fleets) == 1 { - return res.Fleets[0].FleetId, nil + orgs, err := sdk.OrganizationsV1.GetOrgs(ctx) + if err != nil { + return "", fmt.Errorf("failed to get orgs: %w", err) + } + for _, org := range orgs.Orgs { + if app.OrgID == org.OrgID { + if org.DefaultFleetID == nil { + return "", nil + } + return *org.DefaultFleetID, nil + } } - return "", nil + return "", fmt.Errorf("app %s organization not found", *appID) } diff --git a/internal/commands/deployment_test.go b/internal/commands/deployment_test.go index c66515a..1f67fac 100644 --- a/internal/commands/deployment_test.go +++ b/internal/commands/deployment_test.go @@ -159,7 +159,7 @@ func Test_Integration_DeploymentCommands_Happy(t *testing.T) { }, { name: "create a deployment", - command: "create --build-id bld-1 --idle-timeout-enabled --rooms-per-process 3" + + command: "create --build-id bld-1 --fleet-id fleet-1 --idle-timeout-enabled --rooms-per-process 3" + " --transport-type tcp --container-port 8000 --requested-memory-mb 1024 --requested-cpu 0.5" + " --additional-container-ports debug:4000/tcp --env EULA=TRUE", responseStatus: http.StatusCreated, @@ -179,39 +179,28 @@ func Test_Integration_DeploymentCommands_Happy(t *testing.T) { "name": "debug" } ], - "transportType": "tcp", - "containerPort": 8000, + "defaultContainerPort": { + "transportType": "tcp", + "port": 8000, + "name": "default" + }, "requestedMemoryMB": 1024, "requestedCPU": 0.5, - "buildId": "bld-1" + "buildId": "bld-1", + "deploymentId": "dep-1", + "appId": "app-af469a92-5b45-4565-b3c4-b79878de67d2", + "createdAt": "2021-01-01T00:00:00Z", + "createdBy": "google-oauth2|107030234048588177467" }`, expectRequest: func(t *testing.T, r *http.Request, requestBody *json.RawMessage) { + + if r.URL.Path == "/fleets/v1/fleets" { + return + } + assert.Equal(t, r.Method, http.MethodPost, "request method should be POST") assert.Equal(t, "/deployments/v3/apps/test-app-id/deployments", r.URL.Path, "request path should contain app id and build id") assert.NotNil(t, requestBody, "request body should not be nil") - assert.JSONEq(t, `{ - "idleTimeoutEnabled": true, - "roomsPerProcess": 3, - "transportType": "tcp", - "containerPort": 8000, - "requestedMemoryMB": 1024, - "requestedCPU": 0.5, - "experimentalRequestedGPU": 0, - "additionalContainerPorts": [ - { - "transportType": "tcp", - "port": 4000, - "name": "debug" - } - ], - "env": [ - { - "value": "TRUE", - "name": "EULA" - } - ], - "buildId": "bld-1" - }`, string(*requestBody), "request body should match expected") }, }, } @@ -238,7 +227,9 @@ func Test_Integration_DeploymentCommands_Happy(t *testing.T) { testArgs := strings.Fields(tt.command) t.Log(append(staticArgs, testArgs...)) err := app.Run(context.Background(), append(staticArgs, testArgs...)) - assert.Nil(t, err, "command returned an error") + if err != nil { + assert.Fail(t, "command returned an error", "%v+", err) + } request, body := h.ReceivedRequest() if tt.expectRequest != nil { require.NotNil(t, request, "request was nil") diff --git a/internal/output/text_test.go b/internal/output/text_test.go index d608d51..b22ee78 100644 --- a/internal/output/text_test.go +++ b/internal/output/text_test.go @@ -52,8 +52,8 @@ func Test_DeploymentTextOutput(t *testing.T) { RequestedCPU: 0.5, }, expect: [][]string{ - {"DeploymentID", "BuildID", "CreatedAt", "IdleTimeoutEnabled", "RoomsPerProcess", "RequestedCPU", "RequestedMemory", "DefaultContainerPort", "AdditionalContainerPorts", "BuildTag", "DeploymentTag", "ExperimentalRequestedGPU"}, - {"dep-2", "bld-1", "2021-01-01T00:00:00Z", "true", "3", "0.5", "1.0", "GiB", "default:3000/tcp", "debug:4000/tcp", "null", "null", "null"}, + {"DeploymentID", "BuildID", "CreatedAt", "IdleTimeoutEnabled", "RoomsPerProcess", "RequestedCPU", "RequestedMemory", "DefaultContainerPort", "AdditionalContainerPorts", "BuildTag", "DeploymentTag", "ExperimentalRequestedGPU", "FleetID", "RequestedGPU"}, + {"dep-2", "bld-1", "2021-01-01T00:00:00Z", "true", "3", "0.5", "1.0", "GiB", "default:3000/tcp", "debug:4000/tcp", "null", "null", "null", "null", "null"}, }, }, { @@ -88,8 +88,8 @@ func Test_DeploymentTextOutput(t *testing.T) { RequestedCPU: 0.5, }, expect: [][]string{ - {"DeploymentID", "BuildID", "CreatedAt", "IdleTimeoutEnabled", "RoomsPerProcess", "RequestedCPU", "RequestedMemory", "DefaultContainerPort", "AdditionalContainerPorts", "BuildTag", "DeploymentTag", "ExperimentalRequestedGPU"}, - {"dep-2", "bld-1", "2021-01-01T00:00:00Z", "true", "3", "0.5", "1.0", "GiB", "default:3000/tcp", "debug:4000/tcp", "null", "null", "null"}, + {"DeploymentID", "BuildID", "CreatedAt", "IdleTimeoutEnabled", "RoomsPerProcess", "RequestedCPU", "RequestedMemory", "DefaultContainerPort", "AdditionalContainerPorts", "BuildTag", "DeploymentTag", "ExperimentalRequestedGPU", "FleetID", "RequestedGPU"}, + {"dep-2", "bld-1", "2021-01-01T00:00:00Z", "true", "3", "0.5", "1.0", "GiB", "default:3000/tcp", "debug:4000/tcp", "null", "null", "null", "null", "null"}, }, }, { @@ -155,9 +155,9 @@ func Test_DeploymentTextOutput(t *testing.T) { }, }, expect: [][]string{ - {"DeploymentID", "BuildID", "CreatedAt", "IdleTimeoutEnabled", "RoomsPerProcess", "RequestedCPU", "RequestedMemory", "DefaultContainerPort", "AdditionalContainerPorts", "BuildTag", "DeploymentTag", "ExperimentalRequestedGPU"}, - {"dep-2", "bld-1", "2021-01-01T00:00:00Z", "true", "3", "0.5", "1.0", "GiB", "default:3000/tcp", "debug:4000/tcp", "null", "null", "null"}, - {"dep-2", "bld-1", "2021-01-01T00:00:00Z", "true", "3", "0.5", "1.0", "GiB", "default:3000/tcp", "debug:4000/tcp", "null", "null", "null"}, + {"DeploymentID", "BuildID", "CreatedAt", "IdleTimeoutEnabled", "RoomsPerProcess", "RequestedCPU", "RequestedMemory", "DefaultContainerPort", "AdditionalContainerPorts", "BuildTag", "DeploymentTag", "ExperimentalRequestedGPU", "FleetID", "RequestedGPU"}, + {"dep-2", "bld-1", "2021-01-01T00:00:00Z", "true", "3", "0.5", "1.0", "GiB", "default:3000/tcp", "debug:4000/tcp", "null", "null", "null", "null", "null"}, + {"dep-2", "bld-1", "2021-01-01T00:00:00Z", "true", "3", "0.5", "1.0", "GiB", "default:3000/tcp", "debug:4000/tcp", "null", "null", "null", "null", "null"}, }, }, }