Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/local-app/cmd/root.go
2497 views
1
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package cmd
6
7
import (
8
"context"
9
"errors"
10
"fmt"
11
"io"
12
"log/slog"
13
"net/http"
14
"os"
15
"time"
16
17
"github.com/gitpod-io/gitpod/components/public-api/go/client"
18
"github.com/gitpod-io/local-app/pkg/auth"
19
"github.com/gitpod-io/local-app/pkg/config"
20
"github.com/gitpod-io/local-app/pkg/constants"
21
"github.com/gitpod-io/local-app/pkg/prettyprint"
22
"github.com/gitpod-io/local-app/pkg/selfupdate"
23
"github.com/gitpod-io/local-app/pkg/telemetry"
24
"github.com/gookit/color"
25
"github.com/lmittmann/tint"
26
"github.com/mattn/go-isatty"
27
"github.com/spf13/cobra"
28
)
29
30
var rootOpts struct {
31
ConfigLocation string
32
Verbose bool
33
}
34
35
var rootCmd = &cobra.Command{
36
Use: "gitpod",
37
Short: "Gitpod: Always ready to code.",
38
Long: color.Sprint(`
39
<fg=ff971d> .-+*#+ </> <b>Gitpod: Always ready to code.</>
40
<fg=ff971d> :=*#####*. </> Try the following commands to get started:
41
<fg=ff971d> .=*####*+-. .--: </>
42
<fg=ff971d> +****=: :=*####+ </> gitpod login <lightgray>Login to Gitpod</>
43
<fg=ff971d> ****: .-+*########.</> gitpod whoami <lightgray>Show information about the currently logged in user</>
44
<fg=ff971d> +***: *****+--####.</>
45
<fg=ff971d> +***: .-=:. .#*##.</> gitpod workspace list <lightgray>List your workspaces</>
46
<fg=ff971d> +***+-. .-+**** </> gitpod workspace create <lightgray>Create a new workspace</>
47
<fg=ff971d> .=*****+=::-+*****+: </> gitpod workspace open <lightgray>Open a running workspace</>
48
<fg=ff971d> .:=+*********=-. </> gitpod workspace stop <lightgray>Stop a running workspace</>
49
<fg=ff971d> .-++++=: </>
50
51
`),
52
SilenceErrors: true,
53
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
54
level := slog.LevelInfo
55
if rootOpts.Verbose {
56
level = slog.LevelDebug
57
}
58
var noColor bool
59
if !isatty.IsTerminal(os.Stdout.Fd()) {
60
noColor = true
61
color.Disable()
62
}
63
slog.SetDefault(slog.New(tint.NewHandler(os.Stdout, &tint.Options{
64
Level: level,
65
NoColor: noColor,
66
TimeFormat: time.StampMilli,
67
ReplaceAttr: func(groups []string, attr slog.Attr) slog.Attr {
68
if attr.Key != "level" {
69
return attr
70
}
71
72
switch attr.Value.String() {
73
case slog.LevelDebug.String():
74
attr.Value = slog.StringValue(color.Gray.Render("[DEBUG]"))
75
case slog.LevelInfo.String():
76
attr.Value = slog.StringValue(color.Green.Render("[INFO ]"))
77
case slog.LevelWarn.String():
78
attr.Value = slog.StringValue(color.Yellow.Render("[WARN ]"))
79
case slog.LevelError.String():
80
attr.Value = slog.StringValue(color.Red.Render("[ERROR]"))
81
}
82
return attr
83
},
84
})))
85
86
cfg, err := config.LoadConfig(rootOpts.ConfigLocation)
87
if errors.Is(err, os.ErrNotExist) {
88
err = nil
89
}
90
if err != nil {
91
return err
92
}
93
cmd.SetContext(config.ToContext(context.Background(), cfg))
94
95
host := "https://gitpod.io"
96
telemetryEnabled := !telemetry.DoNotTrack()
97
telemetryEnabled = telemetryEnabled && cfg.Telemetry.Enabled
98
gpctx, err := cfg.GetActiveContext()
99
if err == nil && gpctx != nil {
100
host = gpctx.Host.String()
101
}
102
telemetry.Init(telemetryEnabled, cfg.Telemetry.Identity, constants.Version.String(), level, host)
103
telemetry.RecordCommand(cmd)
104
105
if !isVersionCommand(cmd) {
106
waitForUpdate := selfupdate.Autoupdate(cmd.Context(), cfg)
107
cmd.PostRunE = func(cmd *cobra.Command, args []string) error {
108
waitForUpdate()
109
return nil
110
}
111
}
112
113
return nil
114
},
115
}
116
117
func Execute() {
118
err := rootCmd.Execute()
119
120
var exitCode int
121
if err != nil {
122
exitCode = 1
123
prettyprint.PrintError(os.Stderr, os.Args[0], err)
124
125
telemetry.RecordError(err)
126
}
127
128
telemetry.Close()
129
os.Exit(exitCode)
130
}
131
132
func init() {
133
if !isatty.IsTerminal(os.Stdout.Fd()) || !isatty.IsTerminal(os.Stderr.Fd()) {
134
color.Disable()
135
}
136
137
configLocation := config.DEFAULT_LOCATION
138
if fn := os.Getenv("GITPOD_CONFIG"); fn != "" {
139
configLocation = fn
140
}
141
rootCmd.PersistentFlags().StringVar(&rootOpts.ConfigLocation, "config", configLocation, "Location of the configuration file")
142
rootCmd.PersistentFlags().BoolVarP(&rootOpts.Verbose, "verbose", "v", false, "Display verbose output for more detailed logging")
143
}
144
145
var rootTestingOpts struct {
146
Client *client.Gitpod
147
WriterOut io.Writer
148
}
149
150
var clientCache *client.Gitpod
151
152
func getGitpodClient(ctx context.Context) (*client.Gitpod, error) {
153
// There will be only one client in a command context right now
154
if clientCache != nil {
155
return clientCache, nil
156
}
157
cfg := config.FromContext(ctx)
158
gpctx, err := cfg.GetActiveContext()
159
if err != nil {
160
return nil, err
161
}
162
163
host := gpctx.Host
164
if host == nil {
165
return nil, prettyprint.AddResolution(fmt.Errorf("active context has no host configured"),
166
"set a host using `gitpod config set-context --current --host <host>`",
167
"login again using `gitpod login`",
168
"change to a different context using `gitpod config use-context <context>`",
169
)
170
}
171
172
if rootTestingOpts.Client != nil {
173
return rootTestingOpts.Client, nil
174
}
175
176
token := gpctx.Token
177
if token == "" {
178
token = os.Getenv("GITPOD_TOKEN")
179
}
180
if token == "" {
181
var err error
182
token, err = auth.GetToken(host.String())
183
if err != nil {
184
return nil, err
185
}
186
}
187
if token == "" {
188
return nil, prettyprint.AddResolution(fmt.Errorf("no token found for active context"),
189
"provide a token by setting the GITPOD_TOKEN environment variable",
190
"login again using `gitpod login`",
191
"change to a different context using `gitpod config use-context <context>`",
192
"set a token explicitly using `gitpod config set-context --current --token <token>`",
193
)
194
}
195
196
var apiHost = *gpctx.Host.URL
197
apiHost.Host = "api." + apiHost.Host
198
slog.Debug("establishing connection to Gitpod", "host", apiHost.String())
199
res, err := client.New(
200
client.WithCredentials(token),
201
client.WithURL(apiHost.String()),
202
client.WithHTTPClient(&http.Client{
203
Transport: &auth.AuthenticatedTransport{Token: token, T: http.DefaultTransport},
204
}),
205
)
206
if err != nil {
207
return nil, err
208
}
209
clientCache = res
210
211
return res, nil
212
}
213
214
type formatOpts struct {
215
Field string
216
}
217
218
// WriteTabular writes the given tabular data to the writer
219
func WriteTabular[T any](v []T, opts formatOpts, format prettyprint.WriterFormat) error {
220
var out io.Writer = os.Stdout
221
if rootTestingOpts.WriterOut != nil {
222
out = rootTestingOpts.WriterOut
223
}
224
w := &prettyprint.Writer[T]{
225
Field: opts.Field,
226
Format: format,
227
Out: out,
228
}
229
return w.Write(v)
230
}
231
232
func addFormatFlags(cmd *cobra.Command, opts *formatOpts) {
233
cmd.Flags().StringVarP(&opts.Field, "field", "f", "", "Only print the specified field")
234
}
235
236