Repository navigation
Expand file tree
/
Copy pathmain.go
More file actions
143 lines (131 loc) · 5.53 KB
/
Copy pathmain.go
File metadata and controls
143 lines (131 loc) · 5.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//
// Copyright 2023-2026 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"os"
"github.com/chainloop-dev/chainloop/app/cli/cmd"
"github.com/chainloop-dev/chainloop/app/cli/pkg/action"
v1 "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
"github.com/go-kratos/kratos/v2/errors"
"github.com/rs/zerolog"
_ "github.com/sigstore/sigstore/pkg/signature/kms/aws"
_ "github.com/sigstore/sigstore/pkg/signature/kms/azure"
_ "github.com/sigstore/sigstore/pkg/signature/kms/gcp"
_ "github.com/sigstore/sigstore/pkg/signature/kms/hashivault"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func main() {
// Couldn't find an easier way to disable the timestamp.
// Enable color on interactive terminals and CI systems that render ANSI,
// and disable it for ANSI-incapable consumers (e.g. a Jenkins console
// without the AnsiColor plugin, piped output) to avoid leaking raw escape
// codes. See cmd.LogColorDisabled.
logger := zerolog.New(zerolog.ConsoleWriter{
Out: os.Stderr,
NoColor: cmd.LogColorDisabled(),
FormatTimestamp: func(interface{}) string { return "" },
})
rootCmd := cmd.NewRootCmd(logger)
exitCode := 0
if err := cmd.Execute(rootCmd); err != nil {
// cmd.Logger() rather than the local logger: a hook command may have
// swapped the root logger for a colorless one that also writes to the
// trace log file, and this final line belongs in both.
hookLogger := cmd.Logger()
var msg string
msg, exitCode = errorInfo(err, hookLogger)
hookLogger.Error().Msg(msg)
}
// After the final error line, and before os.Exit, which skips defers.
cmd.CloseHookLog()
if exitCode != 0 {
os.Exit(exitCode)
}
}
// handle predefined errors and handle types so we can tailor the experience
func errorInfo(err error, logger zerolog.Logger) (string, int) {
var msg string
exitCode := 1
// Extract message from grpc message if applicable
st, ok := status.FromError(err)
// It's a regular error
if !ok {
msg = err.Error()
} else {
msg = st.Message()
// Sanitize error message for validation and other known errors
// by default status.fromError(err).Message() returns the whole error chain
// i.e "creating API token: creating API token: rpc error: code = AlreadyExists desc = duplicated: name already taken"
// We do not want to show that in some specific error codes, we just want to show the part of the gRPC response
// i.e "duplicated: name already taken"
// To do what we perform an additional parsing of the error similar to
// https://github.com/grpc/grpc-go/blob/ced812e3287e15a009eab5b271c25750050a2f82/status/status.go#L123
type grpcstatus interface{ GRPCStatus() *status.Status }
var gs grpcstatus
if errors.As(err, &gs) {
knownCodes := []codes.Code{
codes.AlreadyExists, codes.InvalidArgument, codes.NotFound, codes.PermissionDenied,
codes.FailedPrecondition,
}
grpcStatus := gs.GRPCStatus()
for _, code := range knownCodes {
if st.Code() == code {
msg = grpcStatus.Message()
break
}
}
}
}
var gateErr *cmd.GateError
var subprocessErr *action.SubprocessExitError
var repositoryOrganizationErr *cmd.RepositoryOrganizationError
// The same helper the commands that only log a credential failure use, so
// the CLI says the same thing wherever one surfaces.
authMsg, authMatched := action.AuthErrorMessage(err)
// Make overrides
switch {
case errors.As(err, &subprocessErr):
// `chainloop trace run` — surface the wrapped command's exit code so
// the parent shell sees the same status it would have seen running the
// command directly.
return subprocessErr.Error(), subprocessErr.ExitCode
case v1.IsCasBackendErrorReasonRequired(err):
msg = "you need to enable a CAS backend first. Refer to `chainloop cas-backend` command or contact your administrator."
case v1.IsCasBackendErrorReasonInvalid(err):
msg = "the CAS backend you provided is invalid. Refer to `chainloop cas-backend update` command or contact your administrator."
case errors.As(err, &repositoryOrganizationErr):
msg = repositoryOrganizationErr.Error()
case v1.IsUserNotMemberOfOrgErrorNotInOrg(err):
msg = "the organization you are trying to access does not exist or you are not part of it, please run \"chainloop auth login\""
case v1.IsUserWithNoMembershipErrorNotInOrg(err):
msg = "you are not part of any organization, please run \"chainloop organization create --name ORG_NAME\" to create one"
case authMatched:
// Ordered after the org-membership cases, which use gRPC code 7
// (PermissionDenied) rather than 16 (Unauthenticated), so shadowing
// them is structurally impossible either way.
msg = authMsg
case errors.As(err, &cmd.GracefulError{}):
// Graceful recovery if the flag is set and the received error is marked as recoverable
if cmd.GracefulExit {
logger.Debug().Msg("GracefulErrorExit enabled (exitCode 0). If you want to disable it set --graceful-exit=false")
exitCode = 0
}
case errors.As(err, &gateErr):
// exit code for gate errors
exitCode = 4
}
return msg, exitCode
}