Skip to content
This repository was archived by the owner on Nov 27, 2023. It is now read-only.

Commit 184c2b8

Browse files
authored
Merge pull request #1503 from docker/metrics-failures
2 parents 0623688 + 073d8e5 commit 184c2b8

File tree

15 files changed

+287
-31
lines changed

15 files changed

+287
-31
lines changed

cli/cmd/compose/compose.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030

3131
"github.com/docker/compose-cli/api/context/store"
3232
"github.com/docker/compose-cli/cli/formatter"
33+
"github.com/docker/compose-cli/cli/metrics"
3334
)
3435

3536
// Warning is a global warning to be displayed to user on command failure
@@ -74,7 +75,7 @@ func (o *projectOptions) toProject(services []string, po ...cli.ProjectOptionsFn
7475

7576
project, err := cli.ProjectFromOptions(options)
7677
if err != nil {
77-
return nil, err
78+
return nil, metrics.WrapComposeError(err)
7879
}
7980

8081
s, err := project.GetServices(services...)
@@ -112,6 +113,14 @@ func Command(contextType string) *cobra.Command {
112113
Short: "Docker Compose",
113114
Use: "compose",
114115
TraverseChildren: true,
116+
// By default (no Run/RunE in parent command) for typos in subcommands, cobra displays the help of parent command but exit(0) !
117+
RunE: func(cmd *cobra.Command, args []string) error {
118+
if len(args) == 0 {
119+
return cmd.Help()
120+
}
121+
_ = cmd.Help()
122+
return fmt.Errorf("unknown docker command: %q", "compose "+args[0])
123+
},
115124
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
116125
if noAnsi {
117126
if ansi != "auto" {

cli/main.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ var (
7070
"version": {},
7171
"backend-metadata": {},
7272
}
73-
unknownCommandRegexp = regexp.MustCompile(`unknown command "([^"]*)"`)
73+
unknownCommandRegexp = regexp.MustCompile(`unknown docker command: "([^"]*)"`)
7474
)
7575

7676
func init() {
@@ -122,7 +122,7 @@ func main() {
122122
if len(args) == 0 {
123123
return cmd.Help()
124124
}
125-
return fmt.Errorf("unknown command %q", args[0])
125+
return fmt.Errorf("unknown docker command: %q", args[0])
126126
},
127127
}
128128

@@ -292,7 +292,18 @@ func exit(ctx string, err error, ctype string) {
292292
os.Exit(exit.StatusCode)
293293
}
294294

295-
metrics.Track(ctype, os.Args[1:], metrics.FailureStatus)
295+
var composeErr metrics.ComposeError
296+
metricsStatus := metrics.FailureStatus
297+
exitCode := 1
298+
if errors.As(err, &composeErr) {
299+
metricsStatus = composeErr.GetMetricsFailureCategory().MetricsStatus
300+
exitCode = composeErr.GetMetricsFailureCategory().ExitCode
301+
}
302+
if strings.HasPrefix(err.Error(), "unknown shorthand flag:") || strings.HasPrefix(err.Error(), "unknown flag:") || strings.HasPrefix(err.Error(), "unknown docker command:") {
303+
metricsStatus = metrics.CommandSyntaxFailure.MetricsStatus
304+
exitCode = metrics.CommandSyntaxFailure.ExitCode
305+
}
306+
metrics.Track(ctype, os.Args[1:], metricsStatus)
296307

297308
if errors.Is(err, errdefs.ErrLoginRequired) {
298309
fmt.Fprintln(os.Stderr, err)
@@ -310,7 +321,8 @@ func exit(ctx string, err error, ctype string) {
310321
os.Exit(1)
311322
}
312323

313-
fatal(err)
324+
fmt.Fprintln(os.Stderr, err)
325+
os.Exit(exitCode)
314326
}
315327

316328
func fatal(err error) {

cli/metrics/client.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,6 @@ func init() {
4747
}
4848
}
4949

50-
const (
51-
// APISource is sent for API metrics
52-
APISource = "api"
53-
// SuccessStatus is sent for API metrics
54-
SuccessStatus = "success"
55-
// FailureStatus is sent for API metrics
56-
FailureStatus = "failure"
57-
// CanceledStatus is sent for API metrics
58-
CanceledStatus = "canceled"
59-
)
60-
6150
// Client sends metrics to Docker Desktopn
6251
type Client interface {
6352
// Send sends the command to Docker Desktop. Note that the function doesn't

cli/metrics/compose_errors.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
Copyright 2020 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package metrics
18+
19+
import (
20+
"io/fs"
21+
22+
"github.com/pkg/errors"
23+
24+
composeerrdefs "github.com/compose-spec/compose-go/errdefs"
25+
)
26+
27+
// ComposeError error to categorize failures and extract metrics info
28+
type ComposeError struct {
29+
Err error
30+
Category *FailureCategory
31+
}
32+
33+
// WrapComposeError wraps the error if not nil, otherwise returns nil
34+
func WrapComposeError(err error) error {
35+
if err == nil {
36+
return nil
37+
}
38+
return ComposeError{
39+
Err: err,
40+
}
41+
}
42+
43+
// WrapCategorisedComposeError wraps the error if not nil, otherwise returns nil
44+
func WrapCategorisedComposeError(err error, failure FailureCategory) error {
45+
if err == nil {
46+
return nil
47+
}
48+
return ComposeError{
49+
Err: err,
50+
Category: &failure,
51+
}
52+
}
53+
54+
// Unwrap get underlying error
55+
func (e ComposeError) Unwrap() error { return e.Err }
56+
57+
func (e ComposeError) Error() string { return e.Err.Error() }
58+
59+
// GetMetricsFailureCategory get metrics status and error code corresponding to this error
60+
func (e ComposeError) GetMetricsFailureCategory() FailureCategory {
61+
if e.Category != nil {
62+
return *e.Category
63+
}
64+
var pathError *fs.PathError
65+
if errors.As(e.Err, &pathError) {
66+
return FileNotFoundFailure
67+
}
68+
if composeerrdefs.IsNotFoundError(e.Err) {
69+
return FileNotFoundFailure
70+
}
71+
return ComposeParseFailure
72+
}

cli/metrics/definitions.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
Copyright 2020 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package metrics
18+
19+
// FailureCategory sruct regrouping metrics failure status and specific exit code
20+
type FailureCategory struct {
21+
MetricsStatus string
22+
ExitCode int
23+
}
24+
25+
const (
26+
// APISource is sent for API metrics
27+
APISource = "api"
28+
// SuccessStatus command success
29+
SuccessStatus = "success"
30+
// FailureStatus command failure
31+
FailureStatus = "failure"
32+
// ComposeParseFailureStatus failure while parsing compose file
33+
ComposeParseFailureStatus = "failure-compose-parse"
34+
// FileNotFoundFailureStatus failure getting compose file
35+
FileNotFoundFailureStatus = "failure-file-not-found"
36+
// CommandSyntaxFailureStatus failure reading command
37+
CommandSyntaxFailureStatus = "failure-cmd-syntax"
38+
// BuildFailureStatus failure building imge
39+
BuildFailureStatus = "failure-build"
40+
// PullFailureStatus failure pulling imge
41+
PullFailureStatus = "failure-pull"
42+
// CanceledStatus command canceled
43+
CanceledStatus = "canceled"
44+
)
45+
46+
var (
47+
// FileNotFoundFailure failure for compose file not found
48+
FileNotFoundFailure = FailureCategory{MetricsStatus: FileNotFoundFailureStatus, ExitCode: 14}
49+
// ComposeParseFailure failure for composefile parse error
50+
ComposeParseFailure = FailureCategory{MetricsStatus: ComposeParseFailureStatus, ExitCode: 15}
51+
// CommandSyntaxFailure failure for command line syntax
52+
CommandSyntaxFailure = FailureCategory{MetricsStatus: CommandSyntaxFailureStatus, ExitCode: 16}
53+
//BuildFailure failure while building images.
54+
BuildFailure = FailureCategory{MetricsStatus: BuildFailureStatus, ExitCode: 17}
55+
// PullFailure failure while pulling image
56+
PullFailure = FailureCategory{MetricsStatus: PullFailureStatus, ExitCode: 18}
57+
)

local/compose/build.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,21 @@ import (
2424
"path"
2525
"strings"
2626

27-
moby "github.com/docker/docker/api/types"
28-
29-
"github.com/docker/compose-cli/api/compose"
30-
composeprogress "github.com/docker/compose-cli/api/progress"
31-
"github.com/docker/compose-cli/utils"
32-
3327
"github.com/compose-spec/compose-go/types"
3428
"github.com/containerd/containerd/platforms"
3529
"github.com/docker/buildx/build"
3630
"github.com/docker/buildx/driver"
3731
_ "github.com/docker/buildx/driver/docker" // required to get default driver registered
3832
"github.com/docker/buildx/util/progress"
33+
moby "github.com/docker/docker/api/types"
3934
"github.com/docker/docker/errdefs"
4035
bclient "github.com/moby/buildkit/client"
4136
specs "github.com/opencontainers/image-spec/specs-go/v1"
37+
38+
"github.com/docker/compose-cli/api/compose"
39+
composeprogress "github.com/docker/compose-cli/api/progress"
40+
"github.com/docker/compose-cli/cli/metrics"
41+
"github.com/docker/compose-cli/utils"
4242
)
4343

4444
func (s *composeService) Build(ctx context.Context, project *types.Project, options compose.BuildOptions) error {
@@ -160,7 +160,8 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opts
160160
if info.OSType == "windows" {
161161
// no support yet for Windows container builds in Buildkit
162162
// https://docs.docker.com/develop/develop-images/build_enhancements/#limitations
163-
return s.windowsBuild(opts, mode)
163+
err := s.windowsBuild(opts, mode)
164+
return metrics.WrapCategorisedComposeError(err, metrics.BuildFailure)
164165
}
165166
if len(opts) == 0 {
166167
return nil
@@ -190,6 +191,9 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opts
190191
if err == nil {
191192
err = errW
192193
}
194+
if err != nil {
195+
return metrics.WrapCategorisedComposeError(err, metrics.BuildFailure)
196+
}
193197

194198
cw := composeprogress.ContextWriter(ctx)
195199
for _, c := range observedState {

local/compose/pull.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"github.com/docker/compose-cli/api/compose"
3737
"github.com/docker/compose-cli/api/config"
3838
"github.com/docker/compose-cli/api/progress"
39+
"github.com/docker/compose-cli/cli/metrics"
3940
)
4041

4142
func (s *composeService) Pull(ctx context.Context, project *types.Project, opts compose.PullOptions) error {
@@ -121,7 +122,7 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
121122
Status: progress.Error,
122123
Text: "Error",
123124
})
124-
return err
125+
return metrics.WrapCategorisedComposeError(err, metrics.PullFailure)
125126
}
126127

127128
dec := json.NewDecoder(stream)
@@ -131,10 +132,10 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
131132
if err == io.EOF {
132133
break
133134
}
134-
return err
135+
return metrics.WrapCategorisedComposeError(err, metrics.PullFailure)
135136
}
136137
if jm.Error != nil {
137-
return errors.New(jm.Error.Message)
138+
return metrics.WrapCategorisedComposeError(errors.New(jm.Error.Message), metrics.PullFailure)
138139
}
139140
toPullProgressEvent(service.Name, jm, w)
140141
}

local/e2e/cli-only/e2e_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ func TestContextMetrics(t *testing.T) {
182182
assert.DeepEqual(t, []string{
183183
`{"command":"ps","context":"moby","source":"cli","status":"success"}`,
184184
`{"command":"version","context":"moby","source":"cli","status":"success"}`,
185-
`{"command":"version","context":"moby","source":"cli","status":"failure"}`,
185+
`{"command":"version","context":"moby","source":"cli","status":"failure-cmd-syntax"}`,
186186
}, usage)
187187
})
188188

@@ -224,7 +224,6 @@ func TestContextDuplicateACI(t *testing.T) {
224224
}
225225

226226
func TestContextRemove(t *testing.T) {
227-
228227
t.Run("remove current", func(t *testing.T) {
229228
c := NewParallelE2eCLI(t, binDir)
230229

local/e2e/compose/compose_run_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,9 @@ func TestLocalComposeRun(t *testing.T) {
9595
})
9696

9797
t.Run("compose run --publish", func(t *testing.T) {
98-
c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yml", "run", "--publish", "8080:80", "-d", "back", "/bin/sh", "-c", "sleep 1")
98+
c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yml", "run", "--publish", "8081:80", "-d", "back", "/bin/sh", "-c", "sleep 1")
9999
res := c.RunDockerCmd("ps")
100-
assert.Assert(t, strings.Contains(res.Stdout(), "8080->80/tcp"), res.Stdout())
100+
assert.Assert(t, strings.Contains(res.Stdout(), "8081->80/tcp"), res.Stdout())
101101
})
102102

103103
t.Run("down", func(t *testing.T) {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
services:
2+
simple:
3+
build: service1

0 commit comments

Comments
 (0)