Skip to content

Commit 36f7261

Browse files
committed
fix: remove double printing of errors and supress usage text when errors are present after parsing
Signed-off-by: Ben Meier <[email protected]>
1 parent c507418 commit 36f7261

File tree

9 files changed

+181
-41
lines changed

9 files changed

+181
-41
lines changed

README.md

+19-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,25 @@ score-compose run -f /tmp/score.yaml -o /tmp/compose.yaml
2323
- `-f` is the path to the Score file.
2424
- `-o` specifies the path to the output file.
2525

26-
If you're just getting started, follow [this guide](https://docs.score.dev/docs/get-started/score-compose-hello-world/) to run your first Hello World program with `score-compose`.
26+
If you're just getting started, follow [this guide](https://docs.score.dev/docs/get-started/score-compose-hello-world/) to run your first Hello World program with `score-compose`. The full usage of the `run` command is:
27+
28+
```
29+
Translate the SCORE file to docker-compose configuration
30+
31+
Usage:
32+
score-compose run [--file=score.yaml] [--output=compose.yaml] [flags]
33+
34+
Flags:
35+
--build string Replaces 'image' name with compose 'build' instruction
36+
--env-file string Location to store sample .env file
37+
-f, --file string Source SCORE file (default "./score.yaml")
38+
-h, --help help for run
39+
-o, --output string Output file
40+
--overrides string Overrides SCORE file (default "./overrides.score.yaml")
41+
-p, --property stringArray Overrides selected property value
42+
--skip-validation DEPRECATED: Disables Score file schema validation
43+
--verbose Enable diagnostic messages (written to STDERR)
44+
```
2745

2846
## ![Get involved](docs/images/get-involved.svg) Get involved
2947

cmd/score-compose/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616

1717
func main() {
1818
if err := command.Execute(); err != nil {
19-
fmt.Fprintln(os.Stderr, err)
19+
_, _ = fmt.Fprintln(os.Stderr, "Error: "+err.Error())
2020
os.Exit(1)
2121
}
2222
}

e2e-tests/resources/outputs/run --help-output.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Translate the SCORE file to docker-compose configuration
22

33
Usage:
4-
score-compose run [flags]
4+
score-compose run [--file=score.yaml] [--output=compose.yaml] [flags]
55

66
Flags:
77
--build string Replaces 'image' name with compose 'build' instruction
Original file line numberDiff line numberDiff line change
@@ -1,17 +1 @@
1-
'./score.yaml'...
2-
Error: open ./score.yaml: no such file or directory
3-
Usage:
4-
score-compose run [flags]
5-
6-
Flags:
7-
--build string Replaces 'image' name with compose 'build' instruction
8-
--env-file string Location to store sample .env file
9-
-f, --file string Source SCORE file (default "./score.yaml")
10-
-h, --help help for run
11-
-o, --output string Output file
12-
--overrides string Overrides SCORE file (default "./overrides.score.yaml")
13-
-p, --property stringArray Overrides selected property value
14-
--skip-validation DEPRECATED: Disables Score file schema validation
15-
--verbose Enable diagnostic messages (written to STDERR)
16-
17-
open ./score.yaml: no such file or directory
1+
Error: open ./score.yaml: no such file or directory
+1-16
Original file line numberDiff line numberDiff line change
@@ -1,16 +1 @@
1-
Error: open ./score.yaml: no such file or directory
2-
Usage:
3-
score-compose run [flags]
4-
5-
Flags:
6-
--build string Replaces 'image' name with compose 'build' instruction
7-
--env-file string Location to store sample .env file
8-
-f, --file string Source SCORE file (default "./score.yaml")
9-
-h, --help help for run
10-
-o, --output string Output file
11-
--overrides string Overrides SCORE file (default "./overrides.score.yaml")
12-
-p, --property stringArray Overrides selected property value
13-
--skip-validation DEPRECATED: Disables Score file schema validation
14-
--verbose Enable diagnostic messages (written to STDERR)
15-
16-
open ./score.yaml: no such file or directory
1+
Error: open ./score.yaml: no such file or directory
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
Error: unknown command "unknown" for "score-compose"
2-
Run 'score-compose --help' for usage.
3-
unknown command "unknown" for "score-compose"
1+
Error: unknown command "unknown" for "score-compose"

internal/command/root.go

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ var (
2323
This tool produces a docker-compose configuration file from the SCORE specification.
2424
Complete documentation is available at https://score.dev`,
2525
Version: fmt.Sprintf("%s (build: %s; sha: %s)", version.Version, version.BuildTime, version.GitSHA),
26+
// don't print the errors - we print these ourselves in main()
27+
SilenceErrors: true,
2628
}
2729
)
2830

internal/command/run.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,17 @@ func init() {
6363
}
6464

6565
var runCmd = &cobra.Command{
66-
Use: "run",
66+
Use: "run [--file=score.yaml] [--output=compose.yaml]",
6767
Short: "Translate the SCORE file to docker-compose configuration",
6868
RunE: run,
69+
// don't print the errors - we print these ourselves in main()
70+
SilenceErrors: true,
6971
}
7072

7173
func run(cmd *cobra.Command, args []string) error {
74+
// Silence usage message if args are parsed correctly
75+
cmd.SilenceUsage = true
76+
7277
if !verbose {
7378
log.SetOutput(io.Discard)
7479
}
@@ -185,7 +190,7 @@ func run(cmd *cobra.Command, args []string) error {
185190

186191
// Open output file (optional)
187192
//
188-
var dest = io.Writer(os.Stdout)
193+
var dest = cmd.OutOrStdout()
189194
if outFile != "" {
190195
log.Printf("Creating '%s'...\n", outFile)
191196
destFile, err := os.Create(outFile)

internal/command/run_test.go

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package command
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"os"
7+
"path/filepath"
8+
"testing"
9+
10+
"github.com/spf13/cobra"
11+
"github.com/spf13/pflag"
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
"gopkg.in/yaml.v3"
15+
)
16+
17+
// executeAndResetCommand is a test helper that runs and then resets a command for executing in another test.
18+
func executeAndResetCommand(ctx context.Context, cmd *cobra.Command, args []string) (string, string, error) {
19+
beforeOut, beforeErr := cmd.OutOrStderr(), cmd.ErrOrStderr()
20+
defer func() {
21+
cmd.SetOut(beforeOut)
22+
cmd.SetErr(beforeErr)
23+
}()
24+
25+
nowOut, nowErr := new(bytes.Buffer), new(bytes.Buffer)
26+
cmd.SetOut(nowOut)
27+
cmd.SetErr(nowErr)
28+
cmd.SetArgs(args)
29+
subCmd, err := cmd.ExecuteContextC(ctx)
30+
if subCmd != nil {
31+
subCmd.SetContext(nil)
32+
subCmd.SilenceUsage = false
33+
subCmd.SilenceErrors = false
34+
subCmd.Flags().VisitAll(func(f *pflag.Flag) {
35+
_ = f.Value.Set(f.DefValue)
36+
})
37+
}
38+
return nowOut.String(), nowErr.String(), err
39+
}
40+
41+
func TestExample(t *testing.T) {
42+
td := t.TempDir()
43+
require.NoError(t, os.WriteFile(filepath.Join(td, "score.yaml"), []byte(`
44+
apiVersion: score.dev/v1b1
45+
metadata:
46+
name: example-workload-name123
47+
extra-key: extra-value
48+
service:
49+
ports:
50+
port-one:
51+
port: 1000
52+
protocol: TCP
53+
targetPort: 10000
54+
port-two2:
55+
port: 8000
56+
containers:
57+
container-one1:
58+
image: localhost:4000/repo/my-image:tag
59+
command: ["/bin/sh", "-c"]
60+
args: ["hello", "world"]
61+
resources:
62+
requests:
63+
cpu: 1000m
64+
memory: 10Gi
65+
limits:
66+
cpu: "0.24"
67+
memory: 128M
68+
variables:
69+
SOME_VAR: some content here
70+
volumes:
71+
- source: volume-name
72+
target: /mnt/something
73+
readOnly: false
74+
- source: volume-two
75+
target: /mnt/something-else
76+
livenessProbe:
77+
httpGet:
78+
port: 8080
79+
path: /livez
80+
readinessProbe:
81+
httpGet:
82+
host: 127.0.0.1
83+
port: 80
84+
scheme: HTTP
85+
path: /readyz
86+
httpHeaders:
87+
- name: SOME_HEADER
88+
value: some-value-here
89+
container-two2:
90+
image: localhost:4000/repo/my-image:tag
91+
resources:
92+
resource-one1:
93+
metadata:
94+
annotations:
95+
Default-Annotation: this is my annotation
96+
prefix.com/Another-Key_Annotation.2: something else
97+
extra-key: extra-value
98+
type: Resource-One
99+
class: default
100+
params:
101+
extra:
102+
data: here
103+
resource-two2:
104+
type: Resource-Two
105+
`), 0600))
106+
stdout, stderr, err := executeAndResetCommand(context.Background(), rootCmd, []string{"run", "--file", filepath.Join(td, "score.yaml"), "--output", filepath.Join(td, "compose.yaml")})
107+
assert.NoError(t, err)
108+
assert.NotEqual(t, "", stdout)
109+
assert.Equal(t, "", stderr)
110+
rawComposeContent, err := os.ReadFile(filepath.Join(td, "compose.yaml"))
111+
require.NoError(t, err)
112+
var actualComposeContent map[string]interface{}
113+
assert.NoError(t, yaml.Unmarshal(rawComposeContent, &actualComposeContent))
114+
assert.Equal(t, map[string]interface{}{
115+
"services": map[string]interface{}{
116+
"example-workload-name123-container-one1": map[string]interface{}{
117+
"image": "localhost:4000/repo/my-image:tag",
118+
"entrypoint": []interface{}{"/bin/sh", "-c"},
119+
"command": []interface{}{"hello", "world"},
120+
"environment": map[string]interface{}{
121+
"SOME_VAR": "some content here",
122+
},
123+
"ports": []interface{}{
124+
map[string]interface{}{"target": 10000, "published": "1000", "protocol": "TCP"},
125+
map[string]interface{}{"target": 8000, "published": "8000"},
126+
},
127+
"volumes": []interface{}{
128+
map[string]interface{}{"type": "volume", "source": "volume-name", "target": "/mnt/something"},
129+
map[string]interface{}{"type": "volume", "source": "volume-two", "target": "/mnt/something-else"},
130+
},
131+
},
132+
"example-workload-name123-container-two2": map[string]interface{}{
133+
"image": "localhost:4000/repo/my-image:tag",
134+
"network_mode": "service:example-workload-name123-container-one1",
135+
},
136+
},
137+
}, actualComposeContent)
138+
}
139+
140+
func TestExample_invalid_spec(t *testing.T) {
141+
td := t.TempDir()
142+
require.NoError(t, os.WriteFile(filepath.Join(td, "score.yaml"), []byte(`
143+
{}`), 0600))
144+
stdout, stderr, err := executeAndResetCommand(context.Background(), rootCmd, []string{"run", "--file", filepath.Join(td, "score.yaml"), "--output", filepath.Join(td, "compose.yaml")})
145+
assert.EqualError(t, err, "validating workload spec: jsonschema: '' does not validate with https://score.dev/schemas/score#/required: missing properties: 'apiVersion', 'metadata', 'containers'")
146+
assert.Equal(t, "", stdout)
147+
assert.Equal(t, "", stderr)
148+
}

0 commit comments

Comments
 (0)