Skip to content

Commit 077df32

Browse files
authored
fix(railpack): use ShellQuote to preserve env vars in command (#1428)
1 parent ea15b25 commit 077df32

File tree

11 files changed

+58
-53
lines changed

11 files changed

+58
-53
lines changed

pkgs/defang/cli.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ buildGoModule {
77
pname = "defang-cli";
88
version = "git";
99
src = ../../src;
10-
vendorHash = "sha256-8caEfevVUKXsYA5YyVDuPZTqMnYXIZnC4kTTGfPmuqA="; # TODO: use fetchFromGitHub
10+
vendorHash = "sha256-2BQght2PFjXOwV7K7C74hc3yFvIS46N15qoUmkxZAe8="; # TODO: use fetchFromGitHub
1111

1212
subPackages = [ "cmd/cli" ];
1313

src/go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ require (
125125
)
126126

127127
require (
128-
al.essio.dev/pkg/shellescape v1.6.0
129128
github.com/Microsoft/go-winio v0.6.2 // indirect
130129
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect
131130
github.com/aws/aws-sdk-go-v2/credentials v1.16.16

src/go.sum

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA=
2-
al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
31
cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI=
42
cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
53
cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
@@ -187,8 +185,6 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE
187185
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
188186
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
189187
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
190-
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
191-
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
192188
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
193189
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
194190
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
@@ -223,9 +219,9 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
223219
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
224220
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
225221
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
226-
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
227222
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
228223
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
224+
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
229225
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
230226
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
231227
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=

src/pkg/cli/compose/fixup.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"strconv"
1010
"strings"
1111

12-
"al.essio.dev/pkg/shellescape"
12+
"github.com/DefangLabs/defang/src/pkg"
1313
"github.com/DefangLabs/defang/src/pkg/cli/client"
1414
"github.com/DefangLabs/defang/src/pkg/term"
1515
defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1"
@@ -108,7 +108,7 @@ func FixupServices(ctx context.Context, provider client.Provider, project *compo
108108
// command is run as intended by replacing `command: [ "npm", "start" ]`
109109
// with `command: [ "npm start" ]`.
110110
if len(svccfg.Command) > 1 {
111-
svccfg.Command = []string{shellescape.QuoteCommand(svccfg.Command)}
111+
svccfg.Command = []string{pkg.ShellQuote(svccfg.Command...)}
112112
}
113113
}
114114
}

src/pkg/clouds/do/appPlatform/setup.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"os"
88
"path"
99
"regexp"
10-
"strconv"
1110
"strings"
1211
"time"
1312

@@ -89,14 +88,6 @@ func (d *DoApp) SetUpBucket(ctx context.Context) error {
8988
return err
9089
}
9190

92-
func shellQuote(args ...string) string {
93-
quoted := make([]string, len(args))
94-
for i, arg := range args {
95-
quoted[i] = strconv.Quote(arg)
96-
}
97-
return strings.Join(quoted, " ")
98-
}
99-
10091
func getImageSourceSpec(cdImagePath string) (*godo.ImageSourceSpec, error) {
10192
term.Debugf("Using CD image: %q", cdImagePath)
10293
image, err := ParseImage(cdImagePath)
@@ -143,7 +134,7 @@ func (d DoApp) Run(ctx context.Context, env []*godo.AppVariableDefinition, cdIma
143134
Image: image,
144135
InstanceCount: 1,
145136
InstanceSizeSlug: "basic-xs", // TODO: this is legacy and we should use new slugs
146-
RunCommand: shellQuote(cmd...),
137+
RunCommand: pkg.ShellQuote(cmd...),
147138
Termination: &godo.AppJobSpecTermination{
148139
GracePeriodSeconds: 600, // max 10mins to avoid killing the job while it's still running
149140
},

src/pkg/clouds/do/appPlatform/setup_test.go

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,6 @@ import (
66
"github.com/digitalocean/godo"
77
)
88

9-
func TestShellQuote(t *testing.T) {
10-
// Given
11-
tests := []struct {
12-
input []string
13-
expected string
14-
}{
15-
{
16-
input: []string{"true"},
17-
expected: `"true"`,
18-
},
19-
{
20-
input: []string{"echo", "hello world"},
21-
expected: `"echo" "hello world"`,
22-
},
23-
{
24-
input: []string{"echo", "hello", "world"},
25-
expected: `"echo" "hello" "world"`,
26-
},
27-
{
28-
input: []string{"echo", `hello"world`},
29-
expected: `"echo" "hello\"world"`,
30-
},
31-
}
32-
33-
for _, test := range tests {
34-
actual := shellQuote(test.input...)
35-
if actual != test.expected {
36-
t.Errorf("Expected `%s` but got: `%s`", test.expected, actual)
37-
}
38-
}
39-
}
40-
419
func Test_getImageSourceSpec(t *testing.T) {
4210
// Given
4311
tests := []struct {

src/pkg/utils.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,18 @@ func Diff(actualRaw, goldenRaw string) error {
175175
diff := fmt.Sprint(gotextdiff.ToUnified("expected", "actual", goldenRaw, edits))
176176
return fmt.Errorf("mismatch:\n%s", diff)
177177
}
178+
179+
var shellSpecialChars = regexp.MustCompile(`[^\w@%+=:,./-]`) // copied from al.essio.dev/pkg/shellescape
180+
181+
// ShellQuote returns a shell-quoted string of the given arguments.
182+
// When needed, arguments are quoted with double quotes, so that spaces and env vars are preserved.
183+
func ShellQuote(args ...string) string {
184+
quoted := make([]string, len(args))
185+
for i, arg := range args {
186+
if shellSpecialChars.MatchString(arg) {
187+
arg = strconv.Quote(arg)
188+
}
189+
quoted[i] = arg
190+
}
191+
return strings.Join(quoted, " ")
192+
}

src/pkg/utils_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,38 @@ func TestGetCurrentUser(t *testing.T) {
158158
t.Errorf("GetCurrentUser() returned an empty string")
159159
}
160160
}
161+
162+
func TestShellQuote(t *testing.T) {
163+
tests := []struct {
164+
input []string
165+
expected string
166+
}{
167+
{
168+
input: []string{"true"},
169+
expected: `true`,
170+
},
171+
{
172+
input: []string{"echo", "hello world"},
173+
expected: `echo "hello world"`,
174+
},
175+
{
176+
input: []string{"echo", "hello", "world"},
177+
expected: `echo hello world`,
178+
},
179+
{
180+
input: []string{"echo", `hello"world`},
181+
expected: `echo "hello\"world"`,
182+
},
183+
{
184+
input: []string{"bash", "-c", "start.sh $PORT"},
185+
expected: `bash -c "start.sh $PORT"`,
186+
},
187+
}
188+
189+
for _, test := range tests {
190+
actual := ShellQuote(test.input...)
191+
if actual != test.expected {
192+
t.Errorf("Expected `%s` but got: `%s`", test.expected, actual)
193+
}
194+
}
195+
}

src/testdata/railpack/compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ services:
1010
build:
1111
context: .
1212
dockerfile: Dockerfile
13-
command: something "with space"
13+
command: something "with space" $PORT

src/testdata/railpack/compose.yaml.fixup

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"dockerfile": "*Railpack"
3232
},
3333
"command": [
34-
"something 'with space'"
34+
"something \"with space\" \"${PORT}\""
3535
],
3636
"entrypoint": null,
3737
"networks": {

0 commit comments

Comments
 (0)