Repository: superfly/flyctl (Go)

Issue:
fly deploy panics when mount and release command are configured 
with this fly.toml:
```toml
app = "tvd-cfg-repro-2023-02-21"

[mount]
destination = "/mydata"

[deploy]
release_command = "/release_cmd.sh foo bar baz 123 --six"
```

`fly deploy` will panic with these commands:

```
% fly version
fly v0.0.463 darwin/arm64 Commit: be3d531e BuildDate: 2023-02-21T16:42:54Z

% fly apps create tvd-cfg-repro-2023-02-21
New app created: tvd-cfg-repro-2023-02-21

% fly vol create -r sea --size 1 tvd_cfg_repro_2023_02_21_vol
        ID: vol_1q85vg72yz1vzdxe
...

% curl -vs -X POST "https://api.machines.dev/v1/apps/tvd-cfg-repro-2023-02-21/machines" \
    -H "Authorization: Bearer ${FLY_API_TOKEN}" \
    -H "content-type: application/json" \
    --data-binary '{
    "region": "sea",
    "config": {
        "image": "nginx",
        "mounts": [{
            "volume": "vol_1q85vg72yz1vzdxe",
            "path": "/testdata"
        }]
    }
}'

% fly deploy --image nginx
==> Verifying app config
--> Verified app config
==> Building image
Searching for image 'nginx' remotely...
image found: img_19gm46k36kzvx0jk
WARN Found 1 machines that are not part of the Fly Apps Platform:
tvd-cfg-repro-2023-02-21 machines
ID              NAME                    STATE   REGION  IMAGE                   IP ADDRESS                      VOLUME                      CREATED                 LAST UPDATED
39080e6e2c3487  aged-glitter-9049       started sea     library/nginx:latest    fdaa:0:bfcd:a7b:f9:fb09:1c2a:2  vol_1q85vg72yz1vzdxe        2023-02-22T06:44:21Z    2023-02-22T06:44:27Z

? Migrate 1 existing machines into Fly Apps Platform? Yes
INFO Migrating 1 machines to the Fly Apps Platform
Running tvd-cfg-repro-2023-02-21 release_command: /release_cmd.sh foo bar baz 123 --six
Oops, something went wrong! Could you try that again?
```

if I run with a dev build, i get a panic.

This seems to be happening because we try to attach a volume to the release command machine and there is no volume to attach. We shouldn't attach volumes to release commands, at least for now to fix this panic.



Discussion: Couldn't the volume still be attached though? I might be misunderstanding, but when we migrate the machine, shouldn't we just make sure that the volume is properly attached to its app v1 equivalent?
@billyb2 the **release command** machine we provision just for the deployment should not have a volume. That's the behavior in apps v1, too.

from https://fly.io/docs/reference/configuration/#run-one-off-commands-before-releasing-a-deployment
> The temporary VM has full access to the network, environment variables and secrets, but not to persistent volumes.
I had to revert the fix because it broke apps with volumes https://github.com/superfly/flyctl/issues/1808 (revert https://github.com/superfly/flyctl/pull/1807) 



Fix (patch):
diff --git a/internal/command/deploy/machines.go b/internal/command/deploy/machines.go
index be862ff6c6..e66995af10 100644
--- a/internal/command/deploy/machines.go
+++ b/internal/command/deploy/machines.go
@@ -233,7 +233,7 @@ func (md *machineDeployment) DeployMachinesApp(ctx context.Context) error {
 
 	fmt.Fprintf(md.io.Out, "Deploying %s app with %s strategy\n", md.colorize.Bold(md.app.Name), md.strategy)
 	for _, m := range md.machineSet.GetMachines() {
-		launchInput := md.resolveUpdatedMachineConfig(m.Machine())
+		launchInput := md.resolveUpdatedMachineConfig(m.Machine(), false)
 
 		fmt.Fprintf(md.io.ErrOut, "  Updating %s\n", md.colorize.Bold(m.FormattedMachineId()))
 		err := m.Update(ctx, *launchInput)
@@ -273,7 +273,7 @@ func (md *machineDeployment) DeployMachinesApp(ctx context.Context) error {
 
 func (md *machineDeployment) createOneMachine(ctx context.Context) error {
 	fmt.Fprintf(md.io.Out, "No machines in %s app, launching one new machine\n", md.colorize.Bold(md.app.Name))
-	launchInput := md.resolveUpdatedMachineConfig(nil)
+	launchInput := md.resolveUpdatedMachineConfig(nil, false)
 	newMachineRaw, err := md.flapsClient.Launch(ctx, *launchInput)
 	newMachine := machine.NewLeasableMachine(md.flapsClient, md.io, newMachineRaw)
 	if err != nil {
@@ -366,7 +366,7 @@ func (md *machineDeployment) createReleaseCommandMachine(ctx context.Context) er
 	if len(md.releaseCommand) == 0 || !md.releaseCommandMachine.IsEmpty() {
 		return nil
 	}
-	launchInput := md.resolveUpdatedMachineConfig(nil)
+	launchInput := md.resolveUpdatedMachineConfig(nil, true)
 	launchInput = md.configureLaunchInputForReleaseCommand(launchInput)
 	releaseCmdMachine, err := md.flapsClient.Launch(ctx, *launchInput)
 	if err != nil {
@@ -390,7 +390,7 @@ func (md *machineDeployment) updateReleaseCommandMachine(ctx context.Context) er
 	if err != nil {
 		return err
 	}
-	updatedConfig := md.resolveUpdatedMachineConfig(releaseCmdMachine.Machine())
+	updatedConfig := md.resolveUpdatedMachineConfig(releaseCmdMachine.Machine(), true)
 	updatedConfig = md.configureLaunchInputForReleaseCommand(updatedConfig)
 	err = md.releaseCommandMachine.AcquireLeases(ctx, md.leaseTimeout)
 	defer func() {
@@ -532,7 +532,7 @@ func (md *machineDeployment) createReleaseInBackend(ctx context.Context) error {
 	return nil
 }
 
-func (md *machineDeployment) resolveUpdatedMachineConfig(origMachineRaw *api.Machine) *api.LaunchMachineInput {
+func (md *machineDeployment) resolveUpdatedMachineConfig(origMachineRaw *api.Machine, forReleaseCommand bool) *api.LaunchMachineInput {
 	if origMachineRaw == nil {
 		origMachineRaw = &api.Machine{
 			Region: md.appConfig.PrimaryRegion,
@@ -550,6 +550,7 @@ func (md *machineDeployment) resolveUpdatedMachineConfig(origMachineRaw *api.Mac
 		Config:  machineConf,
 		Region:  origMachineRaw.Region,
 	}
+
 	launchInput.Config.Metadata = md.defaultMachineMetadata()
 	if origMachineRaw.Config.Metadata != nil {
 		for k, v := range origMachineRaw.Config.Metadata {
@@ -563,22 +564,22 @@ func (md *machineDeployment) resolveUpdatedMachineConfig(origMachineRaw *api.Mac
 	}
 
 	launchInput.Config.Image = md.img.Tag
-	launchInput.Config.Metrics = md.appConfig.Metrics
 	launchInput.Config.Restart = origMachineRaw.Config.Restart
-	for _, s := range md.appConfig.Statics {
-		launchInput.Config.Statics = append(launchInput.Config.Statics, &api.Static{
-			GuestPath: s.GuestPath,
-			UrlPrefix: s.UrlPrefix,
-		})
-	}
-	launchInput.Config.Env = make(map[string]string)
-	for k, v := range md.appConfig.Env {
-		launchInput.Config.Env[k] = v
-	}
+	launchInput.Config.Guest = origMachineRaw.Config.Guest
+	launchInput.Config.Init = origMachineRaw.Config.Init
+	launchInput.Config.Env = lo.Assign(md.appConfig.Env)
+
 	if launchInput.Config.Env["PRIMARY_REGION"] == "" && origMachineRaw.Config.Env["PRIMARY_REGION"] != "" {
 		launchInput.Config.Env["PRIMARY_REGION"] = origMachineRaw.Config.Env["PRIMARY_REGION"]
 	}
 
+	// Anything below this point doesn't apply to machines created to run ReleaseCommand
+	if forReleaseCommand {
+		return launchInput
+	}
+
+	launchInput.Config.Metrics = md.appConfig.Metrics
+
 	if origMachineRaw.Config.Mounts != nil {
 		launchInput.Config.Mounts = origMachineRaw.Config.Mounts
 	} else if md.appConfig.Mounts != nil {
@@ -593,19 +594,28 @@ func (md *machineDeployment) resolveUpdatedMachineConfig(origMachineRaw *api.Mac
 		terminal.Warnf("Updating the mount path for volume %s on machine %s from %s to %s due to fly.toml [mounts] destination value\n", currentMount.Volume, origMachineRaw.ID, currentMount.Path, md.volumeDestination)
 		launchInput.Config.Mounts[0].Path = md.volumeDestination
 	}
-	if origMachineRaw.Config.Guest != nil {
-		launchInput.Config.Guest = origMachineRaw.Config.Guest
-	}
-	launchInput.Config.Init = origMachineRaw.Config.Init
+
 	processGroup := origMachineRaw.Config.Metadata[api.MachineConfigMetadataKeyFlyProcessGroup]
 	if processGroup == "" {
 		processGroup = api.MachineProcessGroupApp
 	}
 	if processConfig, ok := md.processConfigs[processGroup]; ok {
 		launchInput.Config.Services = processConfig.Services
-		launchInput.Config.Init.Cmd = processConfig.Cmd
 		launchInput.Config.Checks = processConfig.Checks
+		if len(processConfig.Cmd) > 0 {
+			launchInput.Config.Init.Cmd = processConfig.Cmd
+		} else {
+			launchInput.Config.Init.Cmd = nil
+		}
+	}
+
+	for _, s := range md.appConfig.Statics {
+		launchInput.Config.Statics = append(launchInput.Config.Statics, &api.Static{
+			GuestPath: s.GuestPath,
+			UrlPrefix: s.UrlPrefix,
+		})
 	}
+
 	return launchInput
 }