634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | run-gp
6 |
7 |
8 | Run local workspaces using the .gitpod.yml
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | `run-gp` is a CLI tool for running workspaces based on a `.gitpod.yml` file locally on your machine. Using a local working copy it produces a workspace image, and starts that workspace. This provides an experience akin to a regular Gitpod workspace.
20 |
21 | > **Warning**
22 | > This is an experiment. When you find an issue, [please report it](https://github.com/gitpod-io/run-gp/issues/new?assignees=&labels=&template=bug_report.md&title=) so that we can improve this project.
23 |
24 | > **Note**
25 | > `run-gp` is **not the "real Gitpod experience"**. Gitpod offers remote development environments which sport many benefits compared to running things locally. Head over to https://www.gitpod.io to find out more.
26 |
27 | ## Features
28 | - ✅ **Image Build**: `run-gp` produces a workspace image based on the `image` section in the `.gitpod.yml`. If no such section exists, `gitpod/workspace-full:latest` is used.
29 | - ✅ **Browser Access**: by default we'll start [Open VS Code server](https://github.com/gitpod-io/openvscode-server) to provide an experience akin to a regular Gitpod workspace. This means that a `run-gp` workspace is accessible from your browser.
30 | - ✅ **SSH Access**: if your user has an SSH key (`~/.ssh/id_rsa.pub` file) present, the run-gp workspace will sport an SSH server with an appropriate entry in authorized_keys. This means that you can just SSH into the `run-gp` workspace, e.g. from a terminal or using VS Code.
31 | - ✅ VS Code extension installation: VS Code extensions specified in the `.gitpod.yml` will be installed when the workspace starts up. Those extensions are downloaded from [Open VSX](https://open-vsx.org), much like on gitpod.io.
32 | - ✅ **Tasks** configured in the `.gitpod.yml` will run automatically on startup.
33 | - ✅ **Ports** configured in the `.gitpod.yml` will be made available on startup. There is no dynamic port exposure you might expect from a Gitpod workspace.
34 | - ✅ **Airgapped startup** so that other the image that's configured for the workspace no external assets need to be downloaded. It's all in the `run-gp` binary.
35 | - ✅ **Auto-Update** which keeps `run-gp` up to date without you having to worry about it. This can be disabled - see the Config section below.
36 | - ⚠️ **Docker-in-Docker** depends on the environment you use `run-gp` in. It does not work yet on MacOS and when `run-gp` is used from within a Gitpod workspace.
37 | - ⚠️ **JetBrains Gateway support** also depends on the environment `run-gp` is used in. It is known NOT to work on arm64 MacOS.
38 | - ⏳ **`gp` CLI** is coming in a future release.
39 | - ❌ **Gitpod Prebuilds** are unsupported because this tool is completely disconnected from [gitpod.io](https://gitpod.io).
40 | - ❌ **Gitpod Backups** are unsupported because this tool is completely disconnected from [gitpod.io](https://gitpod.io).
41 |
42 | ## Getting Started
43 |
44 | -
45 | Download the [latest release](https://github.com/gitpod-io/run-gp/releases/latest).
46 |
47 | If you're on MacOS you'll need to jump through hoops because the run-gp releases are not signed.
48 | MacOS requires that binaries downloaded using a browser must be [signed and notarized](https://developer.apple.com/developer-id/). Otherwise you won't be able to just execute the `run-gp` command. If you download the release using `curl` in a terminal, MacOS will just let you execute the binary. Alternatively, you can head over to the `Security` system settings and allow the binary to run once MacOS denied this on the first attempt.
49 |
50 |
51 | -
52 |
53 | In a terminal navigate to a directory containing a `.gitpod.yml` file, e.g. a Git working copy of a repository, then execute `run-gp`.
54 |
55 | ```bash
56 | git clone https://github.com/gitpod-io/go-gin-app
57 | cd go-gin-app
58 | run-gp
59 | ```
60 |
61 | **Note:** The `run-gp` command will use the current working directory as context. To point it to a different directory, use the `-w` option.
62 |
63 | -
64 | Once the workspace is ready, open the URL displayed in the terminal.
65 |
66 |
67 |
68 | ## Configuration
69 | `run-gp` does not have a lot of configuration settings, as most thinsg are determined by the `.gitpod.yml`. You can find the location of the configuration file using
70 | ```bash
71 | run-gp config path
72 | ```
73 |
74 | ### Auto Update behaviour
75 | By default `run-gp` will automatically update itself to the latest version. It does that by checking the GitHub releases of the run-gp repository for a new release.
76 | To disable this behaviour run:
77 | ```bash
78 | run-gp config set autoUpdate.enabled false
79 | ```
80 |
81 | ### Telemetry
82 | By default `run-gp` will send anonymous telemetry. We never send identifiable details such as usernames, URLs or the like. You can review all data ever being transmitted [in the sources](https://github.com/gitpod-io/run-gp/blob/main/pkg/telemetry/telemetry.go#L84-L123). To disable telemetry run:
83 | ```bash
84 | run-gp config set telemetry.enabled false
85 | ```
86 |
87 | `run-gp` respects [Console Do Not Track](https://consoledonottrack.com/), i.e. `export DO_NOT_TRACK=1` will also disable telemetry.
88 |
89 | ## Frequently Asked Questions
90 |
91 | - **This readme refers to `run-gp` as experiment. What does that mean?**
92 |
93 | This means that `run-gp` is not a polished product. Instead it's an attempt to [ship a 🛹](https://www.gitpod.io/blog/gitpod-core-values#ship-skateboards), i.e. an MVP that explores how local Gitpod-like workspaces would look like. This repository is not backed by a regular product team, but instead a product exploration effort.
94 |
95 | - **The performacne on my M1 Mac is terrible, what can I do?**
96 |
97 | Until the release of [MacOS 13](https://developer.apple.com/documentation/virtualization/running_intel_binaries_in_linux_vms_with_rosetta), Docker Desktop (and any other Linux VM) will be rather slow on arm64 hardware. It's unlikely we'll produce an arm64 version of Gitpod before (if ever) MacOS 13 comes out. Your best chance is to find an x86 machine, or wait for the release of MacOS 13.
98 |
99 |
--------------------------------------------------------------------------------
/cmd/build.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020 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 | "time"
11 |
12 | "github.com/gitpod-io/gitpod/run-gp/pkg/console"
13 | "github.com/gitpod-io/gitpod/run-gp/pkg/update"
14 | "github.com/spf13/cobra"
15 | )
16 |
17 | // serveCmd represents the serve command
18 | var buildCmd = &cobra.Command{
19 | Use: "build ",
20 | Short: "Builds the workspace image",
21 | Args: cobra.ExactArgs(1),
22 | RunE: func(cmd *cobra.Command, args []string) error {
23 | uiMode := console.UIModeAuto
24 | if rootOpts.Verbose {
25 | uiMode = console.UIModeDaemon
26 | }
27 | log, done, err := console.NewBubbleTeaUI(console.BubbleUIOpts{
28 | UIMode: uiMode,
29 | Verbose: rootOpts.Verbose,
30 | })
31 | if err != nil {
32 | return err
33 | }
34 | console.Init(log)
35 |
36 | cfg, err := getGitpodYaml()
37 | if err != nil {
38 | return err
39 | }
40 | if cfg.WorkspaceLocation == "" {
41 | cfg.WorkspaceLocation = cfg.CheckoutLocation
42 | }
43 |
44 | runtime, err := getRuntime(rootOpts.Workdir)
45 | if err != nil {
46 | return err
47 | }
48 |
49 | ctx, cancel := context.WithCancel(context.Background())
50 | defer cancel()
51 |
52 | shutdown := make(chan struct{})
53 | go func() {
54 | defer close(shutdown)
55 |
56 | if rootOpts.cfg.AutoUpdate.Enabled {
57 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
58 | defer cancel()
59 | didUpdate, err := update.Update(ctx, version, update.NewGitHubReleaseDiscovery(ctx), rootOpts.cfg.Filename)
60 | if errors.Is(err, context.Canceled) {
61 | return
62 | } else if err != nil {
63 | log.Warnf("failed to auto-update: %v", err)
64 | } else if didUpdate {
65 | log.Warnf("Updated to new version - update comes into effect with the next start of run-gp")
66 | }
67 | }
68 |
69 | buildingPhase := log.StartPhase("[building]", "workspace image")
70 | ref := args[0]
71 | bldLog := log.Writer()
72 | err = runtime.BuildImage(ctx, bldLog, ref, cfg)
73 | if err != nil {
74 | buildingPhase.Failure(err.Error())
75 | return
76 | }
77 | bldLog.Discard()
78 | buildingPhase.Success()
79 | }()
80 |
81 | select {
82 | case <-done:
83 | cancel()
84 | <-shutdown
85 | case <-shutdown:
86 | log.Quit()
87 | }
88 |
89 | return nil
90 | },
91 | }
92 |
93 | func init() {
94 | rootCmd.AddCommand(buildCmd)
95 | }
96 |
--------------------------------------------------------------------------------
/cmd/config-path.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 | "fmt"
9 |
10 | "github.com/gitpod-io/gitpod/run-gp/pkg/config"
11 | "github.com/spf13/cobra"
12 | )
13 |
14 | var configPathCmd = &cobra.Command{
15 | Use: "path",
16 | Short: "prints the path to the config file",
17 | RunE: func(cmd *cobra.Command, args []string) error {
18 | cfg, err := config.ReadInConfig()
19 | if err != nil {
20 | return err
21 | }
22 |
23 | if cfg == nil {
24 | return fmt.Errorf("no config file found")
25 | }
26 |
27 | fmt.Println(cfg.Filename)
28 |
29 | return nil
30 | },
31 | }
32 |
33 | func init() {
34 | configCmd.AddCommand(configPathCmd)
35 | }
36 |
--------------------------------------------------------------------------------
/cmd/config-set.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 | "github.com/gitpod-io/gitpod/run-gp/pkg/config"
9 | "github.com/spf13/cobra"
10 | )
11 |
12 | var configSetCmd = &cobra.Command{
13 | Use: "set ",
14 | Short: "set a configuration value",
15 | Args: cobra.ExactArgs(2),
16 | RunE: func(cmd *cobra.Command, args []string) error {
17 | cfg, err := config.ReadInConfig()
18 | if err != nil {
19 | return err
20 | }
21 |
22 | err = cfg.Set(args[0], args[1])
23 | if err != nil {
24 | return err
25 | }
26 |
27 | err = cfg.Write()
28 | if err != nil {
29 | return err
30 | }
31 |
32 | return nil
33 | },
34 | }
35 |
36 | func init() {
37 | configCmd.AddCommand(configSetCmd)
38 | }
39 |
--------------------------------------------------------------------------------
/cmd/config.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 | "github.com/spf13/cobra"
9 | )
10 |
11 | // serveCmd represents the serve command
12 | var configCmd = &cobra.Command{
13 | Use: "config",
14 | Short: "helps to configure run-gp",
15 | // Hidden: true,
16 | }
17 |
18 | func init() {
19 | rootCmd.AddCommand(configCmd)
20 | }
21 |
--------------------------------------------------------------------------------
/cmd/debug-latest-release.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 | "encoding/json"
10 | "os"
11 |
12 | "github.com/gitpod-io/gitpod/run-gp/pkg/update"
13 | "github.com/spf13/cobra"
14 | )
15 |
16 | // serveCmd represents the serve command
17 | var debugLatestReleaseCmd = &cobra.Command{
18 | Use: "get-latest-release",
19 | RunE: func(cmd *cobra.Command, args []string) error {
20 | res, err := update.NewGitHubReleaseDiscovery(context.Background()).DiscoverLatest(context.Background())
21 | if err != nil {
22 | return err
23 | }
24 |
25 | enc := json.NewEncoder(os.Stdout)
26 | enc.SetIndent("", " ")
27 | return enc.Encode(res)
28 | },
29 | }
30 |
31 | func init() {
32 | debugCmd.AddCommand(debugLatestReleaseCmd)
33 | }
34 |
--------------------------------------------------------------------------------
/cmd/debug-ui.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 | "fmt"
9 | "time"
10 |
11 | "github.com/gitpod-io/gitpod/run-gp/pkg/console"
12 | "github.com/spf13/cobra"
13 | )
14 |
15 | // serveCmd represents the serve command
16 | var debugUICmd = &cobra.Command{
17 | Use: "ui",
18 | Short: "runs the bubble UI",
19 | RunE: func(cmd *cobra.Command, args []string) error {
20 | lg, _, err := console.NewBubbleTeaUI(console.BubbleUIOpts{
21 | UIMode: console.UIModeFancy,
22 | Verbose: false,
23 | })
24 | if err != nil {
25 | return err
26 | }
27 | defer lg.Quit()
28 |
29 | p := lg.StartPhase("", "doing something")
30 | time.Sleep(200 * time.Millisecond)
31 | p.Success()
32 | p = lg.StartPhase("", "doing some more")
33 | time.Sleep(1 * time.Second)
34 | p.Success()
35 |
36 | p = lg.StartPhase("", "yet more work")
37 | w := lg.Writer()
38 | for i := 0; i < 30; i++ {
39 | fmt.Fprintf(w, "line %03d\n", i)
40 | time.Sleep(100 * time.Millisecond)
41 | }
42 | w.Close()
43 | p.Failure("no good reason")
44 | w.Discard()
45 |
46 | time.Sleep(200 * time.Millisecond)
47 |
48 | return nil
49 | },
50 | }
51 |
52 | func init() {
53 | debugCmd.AddCommand(debugUICmd)
54 | }
55 |
--------------------------------------------------------------------------------
/cmd/debug.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 | "github.com/spf13/cobra"
9 | )
10 |
11 | // serveCmd represents the serve command
12 | var debugCmd = &cobra.Command{
13 | Use: "debug",
14 | Short: "helps develop run-gp",
15 | Hidden: true,
16 | }
17 |
18 | func init() {
19 | rootCmd.AddCommand(debugCmd)
20 | }
21 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 | "fmt"
9 | "io/ioutil"
10 | "os"
11 | "path/filepath"
12 |
13 | "github.com/spf13/cobra"
14 | "github.com/spf13/pflag"
15 | "gopkg.in/yaml.v3"
16 |
17 | gitpod "github.com/gitpod-io/gitpod/gitpod-protocol"
18 | "github.com/gitpod-io/gitpod/run-gp/pkg/config"
19 | "github.com/gitpod-io/gitpod/run-gp/pkg/console"
20 | "github.com/gitpod-io/gitpod/run-gp/pkg/runtime"
21 | "github.com/gitpod-io/gitpod/run-gp/pkg/telemetry"
22 | )
23 |
24 | // rootCmd represents the base command when called without any subcommands
25 | var rootCmd = &cobra.Command{
26 | Use: "run-gp",
27 | Short: "start a local dev-environment using a .gitpdod.yaml file",
28 | PersistentPreRun: func(cmd *cobra.Command, args []string) {
29 | cfg, err := config.ReadInConfig()
30 | if err != nil {
31 | console.Default.Warnf("%v", err)
32 | }
33 | if cfg == nil {
34 | cfg = &config.Config{
35 | AutoUpdate: config.AutoUpdateConfig{
36 | Enabled: true,
37 | },
38 | Telemetry: config.TelemtryConfig{
39 | Enabled: true,
40 | },
41 | }
42 | }
43 |
44 | telemetry.Init(cfg.Telemetry.Enabled && !rootOpts.DisableTelemetry, cfg.Telemetry.Identity, version)
45 | if cfg.Telemetry.Identity == "" {
46 | cfg.Telemetry.Identity = telemetry.Identity()
47 | err := cfg.Write()
48 | if err != nil {
49 | console.Default.Warnf("cannot write config file: %v", err)
50 | }
51 |
52 | console.Default.Debugf("produced new telemetry identity: %s", cfg.Telemetry.Identity)
53 | }
54 |
55 | rootOpts.cfg = cfg
56 | },
57 | PersistentPostRun: func(cmd *cobra.Command, args []string) {
58 | telemetry.Close()
59 | },
60 | }
61 |
62 | var rootOpts struct {
63 | Workdir string
64 | GitpodYamlFN string
65 | Verbose bool
66 | DisableTelemetry bool
67 | Runtime string
68 |
69 | cfg *config.Config
70 | }
71 |
72 | // Execute adds all child commands to the root command and sets flags appropriately.
73 | // This is called by main.main(). It only needs to happen once to the rootCmd.
74 | func Execute() {
75 | cmd, _, err := rootCmd.Find(os.Args[1:])
76 | // default cmd if no cmd is given
77 | if err == nil && cmd.Use == rootCmd.Use && cmd.Flags().Parse(os.Args[1:]) != pflag.ErrHelp {
78 | args := append([]string{runCmd.Use}, os.Args[1:]...)
79 | rootCmd.SetArgs(args)
80 | }
81 |
82 | if err := rootCmd.Execute(); err != nil {
83 | fmt.Println(err)
84 | os.Exit(1)
85 | }
86 | }
87 |
88 | func init() {
89 | wd, err := os.Getwd()
90 | if err != nil {
91 | panic(err)
92 | }
93 | rootCmd.PersistentFlags().StringVarP(&rootOpts.Workdir, "workdir", "w", wd, "Path to the working directory")
94 | rootCmd.PersistentFlags().StringVarP(&rootOpts.GitpodYamlFN, "gitpod-yaml", "f", ".gitpod.yml", "path to the .gitpod.yml file relative to the working directory")
95 | rootCmd.PersistentFlags().BoolVarP(&rootOpts.Verbose, "verbose", "v", false, "verbose output")
96 | rootCmd.PersistentFlags().BoolVar(&rootOpts.DisableTelemetry, "disable-telemetry", os.Getenv("DO_NOT_TRACK") == "1", "disable telemetry")
97 | rootCmd.PersistentFlags().StringVar(&rootOpts.Runtime, "runtime", "auto", "container runtime to use")
98 | }
99 |
100 | func getGitpodYaml() (*gitpod.GitpodConfig, error) {
101 | fn := filepath.Join(rootOpts.Workdir, rootOpts.GitpodYamlFN)
102 | fc, err := ioutil.ReadFile(fn)
103 | if os.IsNotExist(err) {
104 | return &gitpod.GitpodConfig{}, nil
105 | }
106 | if err != nil {
107 | return nil, err
108 | }
109 |
110 | var cfg gitpod.GitpodConfig
111 | err = yaml.Unmarshal(fc, &cfg)
112 | if err != nil {
113 | return nil, fmt.Errorf("unmarshal .gitpod.yml file failed: %v", err)
114 | }
115 |
116 | return &cfg, nil
117 | }
118 |
119 | func getRuntime(workdir string) (runtime.RuntimeBuilder, error) {
120 | var rt runtime.SupportedRuntime
121 | switch rootOpts.Runtime {
122 | case "auto":
123 | rt = runtime.AutodetectRuntime
124 | case "docker":
125 | rt = runtime.DockerRuntime
126 | case "nerdctl":
127 | rt = runtime.NerdctlRuntime
128 | default:
129 | return nil, fmt.Errorf("unsupported value for --runtime: %s. Only auto, docker and nerdctl are supported", rootOpts.Runtime)
130 | }
131 |
132 | return runtime.New(workdir, rt)
133 | }
134 |
--------------------------------------------------------------------------------
/cmd/run.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020 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 | "io/ioutil"
11 | "os"
12 | "path/filepath"
13 | "strings"
14 | "time"
15 |
16 | "github.com/gitpod-io/gitpod/run-gp/pkg/console"
17 | "github.com/gitpod-io/gitpod/run-gp/pkg/runtime"
18 | "github.com/gitpod-io/gitpod/run-gp/pkg/telemetry"
19 | "github.com/gitpod-io/gitpod/run-gp/pkg/update"
20 | "github.com/spf13/cobra"
21 | )
22 |
23 | // serveCmd represents the serve command
24 | var runCmd = &cobra.Command{
25 | Use: "run",
26 | Short: "Starts a workspace",
27 |
28 | RunE: func(cmd *cobra.Command, args []string) error {
29 | uiMode := console.UIModeAuto
30 | if rootOpts.Verbose {
31 | uiMode = console.UIModeDaemon
32 | }
33 | log, done, err := console.NewBubbleTeaUI(console.BubbleUIOpts{
34 | UIMode: uiMode,
35 | Verbose: rootOpts.Verbose,
36 | })
37 | if err != nil {
38 | return err
39 | }
40 | console.Init(log)
41 |
42 | cfg, err := getGitpodYaml()
43 | if err != nil {
44 | return err
45 | }
46 |
47 | if cfg.CheckoutLocation == "" {
48 | cfg.CheckoutLocation = filepath.Base(rootOpts.Workdir)
49 | }
50 | if cfg.WorkspaceLocation == "" {
51 | cfg.WorkspaceLocation = cfg.CheckoutLocation
52 | }
53 |
54 | runtime, err := getRuntime(rootOpts.Workdir)
55 | if err != nil {
56 | return err
57 | }
58 |
59 | ctx, cancel := context.WithCancel(context.Background())
60 | defer cancel()
61 |
62 | shutdown := make(chan struct{})
63 | go func() {
64 | defer close(shutdown)
65 |
66 | if rootOpts.cfg.AutoUpdate.Enabled {
67 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
68 | defer cancel()
69 | didUpdate, err := update.Update(ctx, version, update.NewGitHubReleaseDiscovery(ctx), rootOpts.cfg.Filename)
70 | if errors.Is(err, context.Canceled) {
71 | return
72 | } else if err != nil {
73 | log.Warnf("failed to auto-update: %v", err)
74 | } else if didUpdate {
75 | log.Warnf("Updated to new version - update comes into effect with the next start of run-gp")
76 | }
77 | }
78 |
79 | buildingPhase := log.StartPhase("[building]", "workspace image")
80 | ref := filepath.Join("workspace-image:latest")
81 | bldLog := log.Writer()
82 | err = runtime.BuildImage(ctx, bldLog, ref, cfg)
83 | if err != nil {
84 | buildingPhase.Failure(err.Error())
85 | return
86 | }
87 | bldLog.Discard()
88 | buildingPhase.Success()
89 |
90 | var (
91 | publicSSHKey string
92 | publicSSHKeyFN = runOpts.SSHPublicKeyPath
93 | )
94 | if strings.HasPrefix(publicSSHKeyFN, "~") {
95 | home, err := os.UserHomeDir()
96 | if err != nil {
97 | log.Warnf("cannot find user home directory: %v", err)
98 | return
99 | }
100 | publicSSHKeyFN = filepath.Join(home, strings.TrimPrefix(publicSSHKeyFN, "~"))
101 | }
102 |
103 | if fc, err := ioutil.ReadFile(publicSSHKeyFN); err == nil {
104 | publicSSHKey = string(fc)
105 | } else if rootOpts.Verbose {
106 | log.Warnf("cannot read public SSH key from %s: %v", publicSSHKeyFN, err)
107 | }
108 |
109 | recordFailure := func() {
110 | if !telemetry.Enabled() {
111 | return
112 | }
113 |
114 | telemetry.RecordWorkspaceFailure(telemetry.GetGitRemoteOriginURI(rootOpts.Workdir), "running", runtime.Name())
115 | }
116 |
117 | runLogs := console.Observe(log, console.WorkspaceAccessInfo{
118 | WorkspaceFolder: filepath.Join("/workspace", cfg.WorkspaceLocation),
119 | HTTPPort: runOpts.StartOpts.IDEPort,
120 | SSHPort: runOpts.StartOpts.SSHPort,
121 | }, recordFailure)
122 | opts := runOpts.StartOpts
123 | opts.Logs = runLogs
124 | opts.SSHPublicKey = publicSSHKey
125 | err := runtime.StartWorkspace(ctx, ref, cfg, opts)
126 | if err != nil {
127 | return
128 | }
129 | runLogs.Discard()
130 | }()
131 |
132 | select {
133 | case <-done:
134 | cancel()
135 | <-shutdown
136 | case <-shutdown:
137 | log.Quit()
138 | }
139 |
140 | return nil
141 | },
142 | }
143 |
144 | var runOpts struct {
145 | StartOpts runtime.StartOpts
146 | SSHPublicKeyPath string
147 | }
148 |
149 | func init() {
150 | rootCmd.AddCommand(runCmd)
151 | runCmd.Flags().BoolVar(&runOpts.StartOpts.NoPortForwarding, "no-port-forwarding", false, "disable port-forwarding for ports in the .gitpod.yml")
152 | runCmd.Flags().IntVar(&runOpts.StartOpts.PortOffset, "port-offset", 0, "shift exposed ports by this number")
153 | runCmd.Flags().IntVar(&runOpts.StartOpts.IDEPort, "ide-port", 8080, "port to expose open vs code server")
154 | runCmd.Flags().IntVar(&runOpts.StartOpts.SSHPort, "ssh-port", 8082, "port to expose SSH on (set to 0 to disable SSH)")
155 | runCmd.Flags().StringVar(&runOpts.SSHPublicKeyPath, "ssh-public-key-path", "~/.ssh/id_rsa.pub", "path to the user's public SSH key")
156 | }
157 |
--------------------------------------------------------------------------------
/cmd/version.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 | "fmt"
9 |
10 | "github.com/spf13/cobra"
11 | )
12 |
13 | var (
14 | version = "v0.0.0"
15 | commit = ""
16 | date = ""
17 | )
18 |
19 | var versionCmd = &cobra.Command{
20 | Use: "version",
21 | Short: "prints the version",
22 | Run: func(cmd *cobra.Command, args []string) {
23 | fmt.Printf("%s (commit: %s, built-on: %s)\n", version, commit, date)
24 | },
25 | }
26 |
27 | func init() {
28 | rootCmd.AddCommand(versionCmd)
29 | }
30 |
--------------------------------------------------------------------------------
/docs/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitpod-io/run-gp/c6d34075f85921e7f43cdc94c8889812ff9d00d5/docs/logo.png
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gitpod-io/gitpod/run-gp
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/Masterminds/semver v1.5.0
7 | github.com/charmbracelet/bubbles v0.11.0
8 | github.com/charmbracelet/bubbletea v0.21.0
9 | github.com/charmbracelet/lipgloss v0.5.0
10 | github.com/gitpod-io/gitpod/gitpod-protocol v0.0.0-00010101000000-000000000000
11 | github.com/google/go-github/v45 v45.1.0
12 | github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
13 | github.com/mattn/go-isatty v0.0.14
14 | github.com/muesli/reflow v0.3.0
15 | github.com/pterm/pterm v0.12.41
16 | github.com/segmentio/analytics-go/v3 v3.2.1
17 | github.com/sirupsen/logrus v1.8.1
18 | github.com/spf13/cobra v1.4.0
19 | github.com/spf13/pflag v1.0.5
20 | github.com/vmware-labs/yaml-jsonpath v0.3.2
21 | golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb
22 | gopkg.in/yaml.v3 v3.0.1
23 | )
24 |
25 | require (
26 | github.com/atomicgo/cursor v0.0.1 // indirect
27 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
28 | github.com/containerd/console v1.0.3 // indirect
29 | github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960 // indirect
30 | github.com/golang/mock v1.6.0 // indirect
31 | github.com/golang/protobuf v1.4.2 // indirect
32 | github.com/google/go-querystring v1.1.0 // indirect
33 | github.com/google/uuid v1.3.0 // indirect
34 | github.com/gookit/color v1.5.0 // indirect
35 | github.com/gorilla/websocket v1.5.0 // indirect
36 | github.com/inconshreveable/mousetrap v1.0.0 // indirect
37 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
38 | github.com/mattn/go-runewidth v0.0.13 // indirect
39 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
40 | github.com/muesli/cancelreader v0.2.0 // indirect
41 | github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 // indirect
42 | github.com/rivo/uniseg v0.2.0 // indirect
43 | github.com/segmentio/backo-go v1.0.0 // indirect
44 | github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 // indirect
45 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
46 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
47 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
48 | golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
49 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
50 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
51 | google.golang.org/appengine v1.6.7 // indirect
52 | google.golang.org/protobuf v1.25.0 // indirect
53 | )
54 |
55 | replace github.com/gitpod-io/gitpod/gitpod-protocol => github.com/gitpod-io/gitpod/components/gitpod-protocol/go v0.0.0-20220615132424-21a462d793da
56 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
34 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
35 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
36 | github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
37 | github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
38 | github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
39 | github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k=
40 | github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI=
41 | github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c=
42 | github.com/MarvinJWendt/testza v0.3.5 h1:g9krITRRlIsF1eO9sUKXtiTw670gZIIk6T08Keeo1nM=
43 | github.com/MarvinJWendt/testza v0.3.5/go.mod h1:ExbTpWmA1z2E9HSskvrNcwApoX4F9bID692s10nuHRY=
44 | github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
45 | github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
46 | github.com/atomicgo/cursor v0.0.1 h1:xdogsqa6YYlLfM+GyClC/Lchf7aiMerFiZQn7soTOoU=
47 | github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
48 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
49 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
50 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
51 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
52 | github.com/charmbracelet/bubbles v0.11.0 h1:fBLyY0PvJnd56Vlu5L84JJH6f4axhgIJ9P3NET78f0Q=
53 | github.com/charmbracelet/bubbles v0.11.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc=
54 | github.com/charmbracelet/bubbletea v0.21.0 h1:f3y+kanzgev5PA916qxmDybSHU3N804uOnKnhRPXTcI=
55 | github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4=
56 | github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
57 | github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8=
58 | github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
59 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
60 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
61 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
62 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
63 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
64 | github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
65 | github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
66 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
67 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
68 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
69 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
70 | github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960 h1:aRd8M7HJVZOqn/vhOzrGcQH0lNAMkqMn+pXUYkatmcA=
71 | github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
72 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
73 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
74 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
75 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
76 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
77 | github.com/gitpod-io/gitpod/components/gitpod-protocol/go v0.0.0-20220615132424-21a462d793da h1:Gu48fKfgY+QpIn75Ag9lfd79GIjTMno7hfLBHmVsT8k=
78 | github.com/gitpod-io/gitpod/components/gitpod-protocol/go v0.0.0-20220615132424-21a462d793da/go.mod h1:4Irs5aX2Ah7MUlrWqam2r8gBmpLEHXCQ40XwQvd5jWg=
79 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
80 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
81 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
82 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
83 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
84 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
85 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
86 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
87 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
88 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
89 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
90 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
91 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
92 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
93 | github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
94 | github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
95 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
96 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
97 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
98 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
99 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
100 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
101 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
102 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
103 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
104 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
105 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
106 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
107 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
108 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
109 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
110 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
111 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
112 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
113 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
114 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
115 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
116 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
117 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
118 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
119 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
120 | github.com/google/go-github/v45 v45.1.0 h1:SbUjHMRiCe9cHfu6Me4idWxLQEV8ZW9DLPz69zopyWo=
121 | github.com/google/go-github/v45 v45.1.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28=
122 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
123 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
124 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
125 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
126 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
127 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
128 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
129 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
130 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
131 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
132 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
133 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
134 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
135 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
136 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
137 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
138 | github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
139 | github.com/gookit/color v1.5.0 h1:1Opow3+BWDwqor78DcJkJCIwnkviFi+rrOANki9BUFw=
140 | github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
141 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
142 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
143 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
144 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
145 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
146 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
147 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
148 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
149 | github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
150 | github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
151 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
152 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
153 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
154 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
155 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
156 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
157 | github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
158 | github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
159 | github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
160 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
161 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
162 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
163 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
164 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
165 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
166 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
167 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
168 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
169 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
170 | github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
171 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
172 | github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
173 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
174 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
175 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
176 | github.com/muesli/cancelreader v0.2.0 h1:SOpr+CfyVNce341kKqvbhhzQhBPyJRXQaCtn03Pae1Q=
177 | github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
178 | github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
179 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
180 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
181 | github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
182 | github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI=
183 | github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
184 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
185 | github.com/onsi/ginkgo v1.10.2 h1:uqH7bpe+ERSiDa34FDOF7RikN6RzXgduUF8yarlZp94=
186 | github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
187 | github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
188 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
189 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
190 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
191 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
192 | github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
193 | github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
194 | github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
195 | github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=
196 | github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
197 | github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
198 | github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
199 | github.com/pterm/pterm v0.12.41 h1:e2BRfFo1H9nL8GY0S3ImbZqfZ/YimOk9XtkhoobKJVs=
200 | github.com/pterm/pterm v0.12.41/go.mod h1:LW/G4J2A42XlTaPTAGRPvbBfF4UXvHWhC6SN7ueU4jU=
201 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
202 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
203 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
204 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
205 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
206 | github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
207 | github.com/segmentio/analytics-go/v3 v3.2.1 h1:G+f90zxtc1p9G+WigVyTR0xNfOghOGs/PYAlljLOyeg=
208 | github.com/segmentio/analytics-go/v3 v3.2.1/go.mod h1:p8owAF8X+5o27jmvUognuXxdtqvSGtD0ZrfY2kcS9bE=
209 | github.com/segmentio/backo-go v1.0.0 h1:kbOAtGJY2DqOR0jfRkYEorx/b18RgtepGtY3+Cpe6qA=
210 | github.com/segmentio/backo-go v1.0.0/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M=
211 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
212 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
213 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
214 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
215 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
216 | github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 h1:marA1XQDC7N870zmSFIoHZpIUduK80USeY0Rkuflgp4=
217 | github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo=
218 | github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
219 | github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
220 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
221 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
222 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
223 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
224 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
225 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
226 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
227 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
228 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
229 | github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk=
230 | github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ=
231 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
232 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
233 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
234 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
235 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
236 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
237 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
238 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
239 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
240 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
241 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
242 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
243 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
244 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
245 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
246 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
247 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
248 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
249 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
250 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
251 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
252 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
253 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
254 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
255 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
256 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
257 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
258 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
259 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
260 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
261 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
262 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
263 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
264 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
265 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
266 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
267 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
268 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
269 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
270 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
271 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
272 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
273 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
274 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
275 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
276 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
277 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
278 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
279 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
280 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
281 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
282 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
283 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
284 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
285 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
286 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
287 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
288 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
289 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
290 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
291 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
292 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
293 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
294 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
295 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
296 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
297 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
298 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
299 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
300 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
301 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
302 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
303 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
304 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
305 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
306 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
307 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
308 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
309 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
310 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
311 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
312 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
313 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
314 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
315 | golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb h1:8tDJ3aechhddbdPAxpycgXHJRMLpk/Ab+aa4OgdN5/g=
316 | golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
317 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
318 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
319 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
320 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
321 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
322 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
323 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
324 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
325 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
326 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
327 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
328 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
329 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
330 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
331 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
332 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
333 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
334 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
335 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
336 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
337 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
338 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
339 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
340 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
341 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
342 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
343 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
344 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
345 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
346 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
347 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
348 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
349 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
350 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
351 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
352 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
353 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
354 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
355 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
356 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
357 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
358 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
359 | golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
360 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
361 | golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
362 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
363 | golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
364 | golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
365 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
366 | golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
367 | golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
368 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
369 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
370 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
371 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
372 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
373 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
374 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
375 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
376 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
377 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
378 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
379 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
380 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
381 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
382 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
383 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
384 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
385 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
386 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
387 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
388 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
389 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
390 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
391 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
392 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
393 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
394 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
395 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
396 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
397 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
398 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
399 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
400 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
401 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
402 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
403 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
404 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
405 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
406 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
407 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
408 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
409 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
410 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
411 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
412 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
413 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
414 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
415 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
416 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
417 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
418 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
419 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
420 | golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
421 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
422 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
423 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
424 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
425 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
426 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
427 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
428 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
429 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
430 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
431 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
432 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
433 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
434 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
435 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
436 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
437 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
438 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
439 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
440 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
441 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
442 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
443 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
444 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
445 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
446 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
447 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
448 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
449 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
450 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
451 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
452 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
453 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
454 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
455 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
456 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
457 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
458 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
459 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
460 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
461 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
462 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
463 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
464 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
465 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
466 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
467 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
468 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
469 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
470 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
471 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
472 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
473 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
474 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
475 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
476 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
477 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
478 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
479 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
480 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
481 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
482 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
483 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
484 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
485 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
486 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
487 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
488 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
489 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
490 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
491 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
492 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
493 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
494 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
495 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
496 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
497 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
498 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
499 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
500 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
501 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
502 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
503 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
504 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
505 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
506 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
507 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
508 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
509 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
510 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
511 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
512 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
513 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
514 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
515 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
516 | gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
517 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
518 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
519 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
520 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
521 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
522 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
523 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
524 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
525 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
526 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
527 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
528 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
529 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
530 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
531 |
--------------------------------------------------------------------------------
/hack/update-assets.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3 | # Licensed under the GNU Affero General Public License (AGPL).
4 | # See License-AGPL.txt in the project root for license information.
5 |
6 |
7 | tmpdir="$(mktemp -d)"
8 | bldname="assets$(date +%s)"
9 | base="$(dirname "$0")/.."
10 |
11 | SUPERVISOR="$(jq -r '.supervisor' "$base/pkg/runtime/assets/images.json")"
12 | WEBIDE="$(jq -r '."gitpod-code"' "$base/pkg/runtime/assets/images.json")"
13 | OPENVSCODE="$(jq -r '."open-vscode"' "$base/pkg/runtime/assets/images.json")"
14 |
15 | cat < "$tmpdir/Dockerfile"
16 | FROM $SUPERVISOR AS supervisor
17 | FROM $WEBIDE AS webide
18 | FROM --platform=linux/amd64 $OPENVSCODE AS openvscode
19 |
20 | FROM alpine:3.16 AS staging
21 |
22 | RUN mkdir /staging
23 | COPY --from=supervisor /.supervisor /staging/supervisor/
24 | COPY --from=openvscode --chown=33333:33333 /home/.openvscode-server /staging/ide/
25 | COPY --from=webide --chown=33333:33333 /ide/startup.sh /ide/codehelper /staging/ide/
26 | COPY --from=webide --chown=33333:33333 /ide/extensions/gitpod-web /staging/ide/extensions/gitpod-web/
27 | RUN echo '{"entrypoint": "/ide/startup.sh", "entrypointArgs": [ "--port", "{IDEPORT}", "--host", "0.0.0.0", "--without-connection-token", "--server-data-dir", "/workspace/.vscode-remote" ]}' > /staging/ide/supervisor-ide-config.json && \
28 | (echo '#!/bin/bash -li'; echo 'cd /ide || exit'; echo 'exec /ide/codehelper "\$@"') > /staging/ide/startup.sh && \
29 | chmod +x /staging/ide/startup.sh && \
30 | mv /staging/ide/bin/openvscode-server /staging/ide/bin/gitpod-code
31 |
32 | RUN cd /staging && tar cvvfz /assets.tar.gz .
33 |
34 | FROM alpine:3.16
35 | COPY --from=staging /assets.tar.gz /
36 |
37 | EOF
38 |
39 | docker build -t "$bldname" "$tmpdir"
40 | docker run --rm -i -v "$(realpath "$base/pkg/runtime/assets"):/out" "$bldname" cp /assets.tar.gz /out/assets.tar.gz
--------------------------------------------------------------------------------
/hack/update-images.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3 | # Licensed under the GNU Affero General Public License (AGPL).
4 | # See License-AGPL.txt in the project root for license information.
5 |
6 |
7 | if [ $# -eq 0 ]; then
8 | echo "usage: $0 "
9 | exit 1
10 | fi
11 |
12 | temp_file="$(mktemp)"
13 |
14 | cat < "$temp_file"
15 | #!/bin/sh
16 | apk add --no-cache yq jq moreutils curl gcompat
17 |
18 | export supervisor=\$(yq '.components.workspace.supervisor.version' < versions.yaml)
19 | export webide="\$(yq '.components.workspace.codeImage.version' < versions.yaml)"
20 |
21 | curl -qL https://github.com/csweichel/oci-tool/releases/download/v0.2.0/oci-tool_0.2.0_linux_amd64.tar.gz | tar xzv
22 | export openvscode="\$(./oci-tool resolve name docker.io/gitpod/openvscode-server:latest)"
23 |
24 | export supervisorImage='eu.gcr.io/gitpod-core-dev/build/supervisor:'\${supervisor}
25 | export webideImage='eu.gcr.io/gitpod-core-dev/build/ide/code:'\${webide}
26 |
27 | echo
28 | echo "supervisor: \${supervisorImage}"
29 | echo "web IDE: \${webideImage}"
30 | echo "Ppen VS Code server: \${openvscode}"
31 |
32 | echo '{"supervisor": "", "gitpod-code":"", "open-vscode":"", "envs":[]}' > /wd/images.json
33 | jq '.supervisor="'\${supervisorImage}'"' /wd/images.json | sponge /wd/images.json
34 | jq '."gitpod-code"="'\${webideImage}'"' /wd/images.json | sponge /wd/images.json
35 | jq '."open-vscode"="'\${openvscode}'"' /wd/images.json | sponge /wd/images.json
36 | ./oci-tool fetch image "\${webideImage}" | jq '{envs: .config.Env}' | jq -s '.[0] * .[1]' /wd/images.json - | sponge /wd/images.json
37 | EOF
38 |
39 | echo "$temp_file"
40 |
41 | wd="$(realpath $(dirname $0)/../pkg/runtime/assets)"
42 | docker run --rm -it -v "$wd:/wd" -v "$temp_file:/run.sh" "eu.gcr.io/gitpod-core-dev/build/versions:$1" sh /run.sh
--------------------------------------------------------------------------------
/hack/update-license-header.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3 | # Licensed under the GNU Affero General Public License (AGPL).
4 | # See License-AGPL.txt in the project root for license information.
5 |
6 | lpwd="$(pwd)"
7 | if ! [ -x "$(command -v addlicense)" ]; then
8 | tmpdir="$(mktemp -d)"
9 | cd "${tmpdir}"
10 | git clone https://github.com/gitpod-io/gitpod.git .
11 | cd dev/addlicense
12 | go install
13 | rm -rf "${tmpdir}"
14 | fi
15 | cd "${lpwd}"
16 | pwd
17 |
18 | addlicense "$(realpath "$(dirname "$0")/..")"
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2020 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 | //go:generate sh hack/update-license-header.sh
6 |
7 | package main
8 |
9 | import (
10 | "github.com/gitpod-io/gitpod/run-gp/cmd"
11 | )
12 |
13 | func main() {
14 | cmd.Execute()
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/config/config.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 config
6 |
7 | import (
8 | "fmt"
9 | "io/ioutil"
10 | "os"
11 | "path/filepath"
12 | "reflect"
13 | "runtime"
14 |
15 | "github.com/gitpod-io/gitpod/run-gp/pkg/console"
16 | "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
17 | "gopkg.in/yaml.v3"
18 | )
19 |
20 | // Config configures run-gp
21 | type Config struct {
22 | Filename string `yaml:"-"`
23 |
24 | AutoUpdate AutoUpdateConfig `yaml:"autoUpdate"`
25 |
26 | Telemetry TelemtryConfig `yaml:"telemetry"`
27 | }
28 |
29 | type AutoUpdateConfig struct {
30 | Enabled bool `yaml:"enabled"`
31 | }
32 |
33 | type TelemtryConfig struct {
34 | Enabled bool `yaml:"enabled"`
35 | Identity string `yaml:"identity"`
36 | }
37 |
38 | var paths = []func() (string, error){
39 | func() (string, error) {
40 | base, err := os.UserConfigDir()
41 | if err != nil {
42 | return "", err
43 | }
44 | return filepath.Join(base, "run-gp", "config.yaml"), nil
45 | },
46 | func() (string, error) {
47 | base, err := os.UserHomeDir()
48 | if err != nil {
49 | return "", err
50 | }
51 | return filepath.Join(base, ".run-gp", "config.yaml"), nil
52 | },
53 | func() (string, error) {
54 | if runtime.GOOS == "linux" {
55 | return "/etc/run-gp/config.yaml", nil
56 | }
57 | return "", nil
58 | },
59 | func() (string, error) {
60 | return os.Getenv("RUNGP_CONFIG_PATH"), nil
61 | },
62 | }
63 |
64 | // ReadInConfig tries to read the config from several paths.
65 | // The first path is used.
66 | func ReadInConfig() (*Config, error) {
67 | var fn string
68 | for _, pf := range paths {
69 | path, err := pf()
70 | if err != nil {
71 | return nil, err
72 | }
73 | if _, err := os.Stat(path); os.IsNotExist(err) {
74 | continue
75 | } else if err != nil {
76 | return nil, err
77 | }
78 |
79 | fn = path
80 | break
81 | }
82 | if fn == "" {
83 | return nil, nil
84 | }
85 |
86 | fc, err := ioutil.ReadFile(fn)
87 | if err != nil {
88 | return nil, fmt.Errorf("failed to read config file from %s: %v", fn, err)
89 | }
90 | var cfg Config
91 | err = yaml.Unmarshal(fc, &cfg)
92 | if err != nil {
93 | return nil, fmt.Errorf("failed to unmarshal config file: %w", err)
94 | }
95 | cfg.Filename = fn
96 |
97 | console.Default.Debugf("read config file: %s", cfg.Filename)
98 |
99 | return &cfg, nil
100 | }
101 |
102 | // Write writes the config file back
103 | func (cfg *Config) Write() error {
104 | if cfg.Filename == "" {
105 | p, err := paths[0]()
106 | if err != nil {
107 | return fmt.Errorf("cannot determine config file name: %w", err)
108 | }
109 | cfg.Filename = p
110 | }
111 |
112 | fc, err := yaml.Marshal(cfg)
113 | if err != nil {
114 | return fmt.Errorf("cannot marshal config: %w", err)
115 | }
116 |
117 | err = os.MkdirAll(filepath.Dir(cfg.Filename), 0755)
118 | if err != nil && !os.IsExist(err) {
119 | return err
120 | }
121 | err = ioutil.WriteFile(cfg.Filename, fc, 0644)
122 | if err != nil {
123 | return err
124 | }
125 | console.Default.Debugf("wrote config file: %v", cfg.Filename)
126 | return nil
127 | }
128 |
129 | func (cfg *Config) Set(path string, value string) error {
130 | fc, err := yaml.Marshal(cfg)
131 | if err != nil {
132 | return err
133 | }
134 | var nd yaml.Node
135 | err = yaml.Unmarshal(fc, &nd)
136 | if err != nil {
137 | return err
138 | }
139 |
140 | pth, err := yamlpath.NewPath(path)
141 | if err != nil {
142 | return err
143 | }
144 |
145 | nds, err := pth.Find(&nd)
146 | if err != nil {
147 | return err
148 | }
149 | switch len(nds) {
150 | case 0:
151 | return fmt.Errorf("path %s unknown", path)
152 | case 1:
153 | default:
154 | return fmt.Errorf("oath %s is not unique", path)
155 | }
156 |
157 | nds[0].Value = value
158 |
159 | fc, err = yaml.Marshal(&nd)
160 | if err != nil {
161 | return err
162 | }
163 | err = yaml.Unmarshal(fc, cfg)
164 | if err != nil {
165 | return err
166 | }
167 |
168 | return nil
169 | }
170 |
171 | func setStructValue(dst interface{}, path []string, value string) error {
172 | if len(path) == 0 {
173 | return nil
174 | }
175 |
176 | var (
177 | field reflect.StructField
178 | found bool
179 | )
180 | for _, field = range reflect.VisibleFields(reflect.TypeOf(dst)) {
181 | if field.Name != path[0] {
182 | continue
183 | }
184 | found = true
185 | break
186 | }
187 | if !found {
188 | return fmt.Errorf("unknown field")
189 | }
190 |
191 | if len(path) == 1 {
192 |
193 | } else {
194 |
195 | }
196 |
197 | return nil
198 | }
199 |
--------------------------------------------------------------------------------
/pkg/console/bubble.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 console
6 |
7 | import (
8 | "bufio"
9 | "fmt"
10 | "io"
11 | "io/ioutil"
12 | "os"
13 | "strings"
14 | "time"
15 |
16 | "github.com/charmbracelet/bubbles/spinner"
17 | tea "github.com/charmbracelet/bubbletea"
18 | "github.com/charmbracelet/lipgloss"
19 | "github.com/mattn/go-isatty"
20 | "github.com/muesli/reflow/indent"
21 | "github.com/pterm/pterm"
22 | "github.com/sirupsen/logrus"
23 | )
24 |
25 | type UIMode int
26 |
27 | const (
28 | UIModeAuto UIMode = iota
29 | UIModeDaemon
30 | UIModeFancy
31 | )
32 |
33 | type BubbleUIOpts struct {
34 | UIMode UIMode
35 | Verbose bool
36 | }
37 |
38 | func NewBubbleTeaUI(opts BubbleUIOpts) (log *BubbleTeaUI, done <-chan struct{}, err error) {
39 | var teaopts []tea.ProgramOption
40 |
41 | if opts.UIMode == UIModeAuto {
42 | isterm := isatty.IsTerminal(os.Stdout.Fd())
43 | if isterm {
44 | opts.UIMode = UIModeFancy
45 | } else {
46 | opts.UIMode = UIModeDaemon
47 | }
48 | }
49 | switch opts.UIMode {
50 | case UIModeDaemon:
51 | teaopts = []tea.ProgramOption{tea.WithoutRenderer()}
52 | case UIModeFancy:
53 | logrus.SetOutput(ioutil.Discard)
54 | }
55 | if opts.Verbose {
56 | logrus.SetLevel(logrus.DebugLevel)
57 | logrus.SetOutput(os.Stdout)
58 | }
59 |
60 | m := newUIModel()
61 | p := tea.NewProgram(m, teaopts...)
62 | go func() {
63 | p.Start()
64 | close(m.done)
65 | }()
66 |
67 | res := &BubbleTeaUI{
68 | prog: p,
69 | // This channel has a high capacity to avoid dropping messages
70 | // because we have no better way to detect a blocking write to bubbletea.
71 | msgs: make(chan tea.Msg, 1000),
72 | }
73 | go res.forwardMessages()
74 |
75 | return res, m.done, err
76 | }
77 |
78 | type BubbleTeaUI struct {
79 | prog *tea.Program
80 | verbose bool
81 |
82 | msgs chan tea.Msg
83 | }
84 |
85 | // forwardMessages sends messages to the bubbletea program.
86 | // Program.Send blocks once the program has ended, which may lead to
87 | // blockig UI operations. By forwarding messages, we can implement our
88 | // own sender mechanism.
89 | func (ui *BubbleTeaUI) forwardMessages() {
90 | for m := range ui.msgs {
91 | ui.prog.Send(m)
92 | }
93 | }
94 |
95 | func (ui *BubbleTeaUI) sendMsg(m tea.Msg) {
96 | select {
97 | case ui.msgs <- m:
98 | default:
99 | // because the ui.sendMsg can be blocking, we
100 | // just drop messages here.
101 | }
102 | }
103 |
104 | func (ui *BubbleTeaUI) Quit() {
105 | ui.sendMsg(tea.Quit())
106 | time.Sleep(100 * time.Millisecond)
107 | }
108 |
109 | // Debugf implements Log
110 | func (*BubbleTeaUI) Debugf(format string, args ...interface{}) {
111 | logrus.Debugf(format, args...)
112 | }
113 |
114 | // Infof implements Log
115 | func (*BubbleTeaUI) Infof(format string, args ...interface{}) {
116 | logrus.Infof(format, args...)
117 | }
118 |
119 | // Warnf implements Log
120 | func (ui *BubbleTeaUI) Warnf(format string, args ...interface{}) {
121 | logrus.Warnf(format, args...)
122 | ui.sendMsg(msgWarning(fmt.Sprintf(format, args...)))
123 | }
124 |
125 | // StartPhase implements Log
126 | func (ui *BubbleTeaUI) StartPhase(name string, description string) Phase {
127 | desc := name + " " + description
128 | ui.sendMsg(msgPhaseStart(desc))
129 | return &bubblePhase{
130 | parent: ui,
131 | start: time.Now(),
132 | desc: desc,
133 | }
134 | }
135 |
136 | // Writer implements Log
137 | func (ui *BubbleTeaUI) Writer() Logs {
138 | rr, rw := io.Pipe()
139 |
140 | go func() {
141 | r := bufio.NewScanner(rr)
142 | for r.Scan() {
143 | l := r.Text()
144 | ui.sendMsg(msgLogLine(l))
145 | }
146 | }()
147 |
148 | return &bubbleLogs{WriteCloser: rw, parent: ui}
149 | }
150 |
151 | func (ui *BubbleTeaUI) SetWorkspaceAccess(info WorkspaceAccess) {
152 | ui.sendMsg(msgSetWorkspaceAccess(info))
153 | }
154 |
155 | type bubbleLogs struct {
156 | io.WriteCloser
157 | parent *BubbleTeaUI
158 | }
159 |
160 | func (b *bubbleLogs) Discard() {
161 | b.parent.sendMsg(msgDiscardLogs{})
162 | }
163 |
164 | type bubblePhase struct {
165 | parent *BubbleTeaUI
166 | start time.Time
167 | desc string
168 | }
169 |
170 | // Failure implements Phase
171 | func (p *bubblePhase) Failure(reason string) {
172 | p.parent.sendMsg(msgPhaseDone{
173 | Duration: time.Since(p.start),
174 | Desc: p.desc,
175 | Failure: reason,
176 | })
177 | }
178 |
179 | // Success implements Phase
180 | func (p *bubblePhase) Success() {
181 | p.parent.sendMsg(msgPhaseDone{
182 | Duration: time.Since(p.start),
183 | Desc: p.desc,
184 | })
185 | }
186 |
187 | type msgPhaseDone uiPhase
188 | type msgPhaseStart string
189 | type msgLogLine string
190 | type msgDiscardLogs struct{}
191 | type msgWarning string
192 | type msgSetWorkspaceAccess WorkspaceAccess
193 |
194 | var _ Log = &BubbleTeaUI{}
195 |
196 | type uiModel struct {
197 | spinner spinner.Model
198 | phases []uiPhase
199 | currentPhase string
200 |
201 | warnings []string
202 |
203 | workspaceAccess *WorkspaceAccess
204 |
205 | quitting bool
206 | done chan struct{}
207 | logs []string
208 | }
209 |
210 | type uiPhase struct {
211 | Duration time.Duration
212 | Desc string
213 | Failure string
214 | }
215 |
216 | func newUIModel() uiModel {
217 | sp := spinner.New()
218 | sp.Spinner = spinner.Points
219 | sp.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("#ff8a00"))
220 | return uiModel{
221 | spinner: sp,
222 | done: make(chan struct{}),
223 | }
224 | }
225 |
226 | func (m uiModel) Init() tea.Cmd {
227 | return spinner.Tick
228 | }
229 |
230 | func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
231 | switch msg := msg.(type) {
232 | case msgPhaseStart:
233 | m.currentPhase = string(msg)
234 | logrus.Infof("%s starting", msg)
235 | case msgPhaseDone:
236 | m.currentPhase = ""
237 | p := uiPhase(msg)
238 | m.phases = append(m.phases, p)
239 | if p.Failure == "" {
240 | logrus.WithField("duration", p.Duration).Infof("%s done", p.Desc)
241 | } else {
242 | logrus.WithField("duration", p.Duration).WithField("failure", p.Failure).Errorf("%s done", p.Desc)
243 | }
244 | case msgLogLine:
245 | m.logs = append(m.logs, string(msg))
246 | maxLogLines := 10
247 | if m.workspaceAccess != nil {
248 | maxLogLines = 6
249 | }
250 | if len(m.logs) >= maxLogLines {
251 | m.logs = m.logs[1:]
252 | }
253 | logrus.Info(msg)
254 | case msgSetWorkspaceAccess:
255 | v := WorkspaceAccess(msg)
256 | m.workspaceAccess = &v
257 | logrus.WithField("SSH port", v.SSHPort).WithField("URL", v.URL).Infof("workspace is available")
258 | case msgDiscardLogs:
259 | m.logs = nil
260 | case msgWarning:
261 | m.warnings = append(m.warnings, string(msg))
262 | case tea.KeyMsg:
263 | if msg.Type == tea.KeyCtrlC || msg.Type == tea.KeyCtrlQ || msg.String() == "q" {
264 | m.quitting = true
265 | return m, tea.Quit
266 | }
267 | return m, nil
268 | case spinner.TickMsg:
269 | var cmd tea.Cmd
270 | m.spinner, cmd = m.spinner.Update(msg)
271 | return m, cmd
272 | }
273 | return m, nil
274 | }
275 |
276 | var banner = `
277 | _______ ______ ____ _____
278 | / ___/ / / / __ \ / __ ` + "`" + `/ __ \
279 | / / / /_/ / / / / / /_/ / /_/ /
280 | /_/ \__,_/_/ /_/ \__, / .___/
281 | /____/_/
282 | `
283 |
284 | var (
285 | stylePhaseDone = lipgloss.NewStyle().Background(lipgloss.Color("#16825d")).Render
286 | stylePhaseFailed = lipgloss.NewStyle().Background(lipgloss.Color("#f51f1f")).Render
287 | stylePhaseDuration = lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Italic(true).Render
288 | styleHelp = lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render
289 | styleWarning = lipgloss.NewStyle().Background(lipgloss.Color("#ffbe5c")).Bold(true).Render
290 | styleWorkspaceURLDesc = lipgloss.NewStyle().Bold(true).Render
291 | styleWorkspaceURL = lipgloss.NewStyle().Bold(true).Underline(true).Render
292 | )
293 |
294 | func (m uiModel) View() string {
295 | var s string
296 |
297 | lines := strings.Split(banner, "\n")
298 | start, _ := pterm.NewRGBFromHEX("#ff8a00")
299 | end, _ := pterm.NewRGBFromHEX("#ffbe5c")
300 | for _, line := range lines {
301 | for i := range line {
302 | s += start.Fade(0, float32(len(line)), float32(i), end).Sprint(line[i : i+1])
303 | }
304 | s += "\n"
305 | }
306 |
307 | if len(m.warnings) > 0 {
308 | for _, w := range m.warnings {
309 | s += styleWarning(" WARNING ") + " " + w + "\n"
310 | }
311 | s += "\n"
312 | }
313 |
314 | if m.workspaceAccess != nil {
315 | s += styleWorkspaceURLDesc("Open the workspace at: ") + styleWorkspaceURL(m.workspaceAccess.URL) + "\n"
316 | s += styleWorkspaceURLDesc(" SSH using: ") + fmt.Sprintf("ssh -p %d gitpod@localhost", m.workspaceAccess.SSHPort) + "\n"
317 | s += "\n"
318 | }
319 |
320 | for _, p := range m.phases {
321 | if p.Failure == "" {
322 | s += stylePhaseDone(" SUCCESS ") + " "
323 | } else {
324 | s += stylePhaseFailed(" FAILURE ") + " "
325 | }
326 | s += p.Desc + stylePhaseDuration(fmt.Sprintf(" (%3.3fs) ", p.Duration.Seconds()))
327 | if p.Failure != "" {
328 | s += "\n " + p.Failure
329 | }
330 | s += "\n"
331 | }
332 |
333 | if m.currentPhase != "" {
334 | s += " " + m.spinner.View() + " " + m.currentPhase + "\n\n"
335 | }
336 |
337 | for _, res := range m.logs {
338 | s += res + "\n"
339 | }
340 | s += "\n"
341 |
342 | if m.quitting {
343 | s += styleWarning(" SHUTTING DOWN ")
344 | } else {
345 | s += styleHelp("Press q to quit") + "\n"
346 | }
347 |
348 | return indent.String(s, 1)
349 | }
350 |
--------------------------------------------------------------------------------
/pkg/console/console.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 console
6 |
7 | import (
8 | "fmt"
9 | "io"
10 |
11 | "github.com/sirupsen/logrus"
12 | )
13 |
14 | var Default Log = ConsoleLog{}
15 |
16 | func Init(l Log) {
17 | Default = l
18 | }
19 |
20 | type Log interface {
21 | // Writer starts a new log printing session which ends once the writer is closed
22 | Writer() Logs
23 |
24 | SetWorkspaceAccess(info WorkspaceAccess)
25 |
26 | StartPhase(name, description string) Phase
27 | Debugf(format string, args ...interface{})
28 | Infof(format string, args ...interface{})
29 | Warnf(format string, args ...interface{})
30 | }
31 |
32 | type Logs interface {
33 | io.WriteCloser
34 | Discard()
35 | }
36 |
37 | type Phase interface {
38 | Success()
39 | Failure(reason string)
40 | }
41 |
42 | type ConsoleLog struct {
43 | w io.Writer
44 | }
45 |
46 | func NewConsoleLog(w io.Writer) ConsoleLog {
47 | return ConsoleLog{
48 | w: w,
49 | }
50 | }
51 |
52 | var _ Log = ConsoleLog{}
53 |
54 | func (c ConsoleLog) Debugf(format string, args ...interface{}) {
55 | logrus.Debugf(format, args...)
56 | }
57 |
58 | func (c ConsoleLog) Infof(format string, args ...interface{}) {
59 | logrus.Infof(format, args...)
60 | }
61 |
62 | func (c ConsoleLog) Warnf(format string, args ...interface{}) {
63 | logrus.Warnf(format, args...)
64 | }
65 |
66 | // Log implements Log
67 | func (c ConsoleLog) Writer() Logs {
68 | return noopWriteCloser{c.w}
69 | }
70 |
71 | func (c ConsoleLog) SetWorkspaceAccess(info WorkspaceAccess) {
72 | c.Infof("workspace access: %v", info)
73 | }
74 |
75 | type WorkspaceAccess struct {
76 | URL string
77 | SSHPort int
78 | }
79 |
80 | // StartPhase implements Log
81 | func (c ConsoleLog) StartPhase(name, description string) Phase {
82 | fmt.Fprintf(c.w, "[%s] %s\n", name, description)
83 | return consolePhase{
84 | w: c.w,
85 | n: name,
86 | }
87 | }
88 |
89 | type consolePhase struct {
90 | w io.Writer
91 | n string
92 | }
93 |
94 | func (c consolePhase) Success() {
95 | fmt.Fprintf(c.w, "[%s] DONE\n", c.n)
96 | }
97 |
98 | func (c consolePhase) Failure(reason string) {
99 | fmt.Fprintf(c.w, "[%s] FAILED! %s\n", c.n, reason)
100 | }
101 |
102 | type noopWriteCloser struct{ io.Writer }
103 |
104 | func (noopWriteCloser) Close() error {
105 | return nil
106 | }
107 |
108 | func (noopWriteCloser) Discard() {}
109 |
--------------------------------------------------------------------------------
/pkg/console/observer.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 console
6 |
7 | import (
8 | "bufio"
9 | "fmt"
10 | "io"
11 | "strings"
12 | )
13 |
14 | type WorkspaceAccessInfo struct {
15 | WorkspaceFolder string
16 | HTTPPort int
17 | SSHPort int
18 | }
19 |
20 | func Observe(log Log, access WorkspaceAccessInfo, onFail func()) Logs {
21 | rr, rw := io.Pipe()
22 |
23 | var (
24 | steady string
25 | phase = "starting"
26 | )
27 | p := log.StartPhase("["+phase+"]", "workspace image")
28 |
29 | go func() {
30 | extensions := make(map[string]struct{})
31 |
32 | var workspaceURL string
33 | scanner := bufio.NewScanner(rr)
34 | for scanner.Scan() {
35 | var (
36 | resetPhase = true
37 | failure string
38 | )
39 | line := scanner.Text()
40 |
41 | switch {
42 | case strings.Contains(line, "Error response from daemon:"):
43 | resetPhase = true
44 | failure = line
45 | case strings.Contains(line, "Web UI available"):
46 | prefix := "folder"
47 | if strings.HasSuffix(access.WorkspaceFolder, ".code-workspace") {
48 | prefix = "workspace"
49 | }
50 | workspaceURL = fmt.Sprintf("http://localhost:%d/?%s=%s", access.HTTPPort, prefix, access.WorkspaceFolder)
51 |
52 | phase = "running"
53 | steady = fmt.Sprintf("workspace at %s", workspaceURL)
54 | log.SetWorkspaceAccess(WorkspaceAccess{
55 | URL: workspaceURL,
56 | SSHPort: access.SSHPort,
57 | })
58 | case strings.Contains(line, "Installing extensions"):
59 | phase = "installing extensions"
60 | steady = "running " + steady
61 | case strings.Contains(line, "IDE was stopped"):
62 | phase = "restarting"
63 | steady = "the workspace"
64 | failure = "IDE was stopped"
65 | case strings.Contains(line, "Installing extension:"):
66 | segs := strings.Split(line, "Installing extension:")
67 | extensions[segs[1]] = struct{}{}
68 | resetPhase = false
69 | case strings.Contains(line, "Downloaded extension"):
70 | for k := range extensions {
71 | if strings.Contains(line, k) {
72 | delete(extensions, k)
73 | }
74 | }
75 | if len(extensions) == 0 {
76 | phase = "ready"
77 | } else {
78 | resetPhase = false
79 | }
80 | default:
81 | resetPhase = false
82 | }
83 |
84 | if !resetPhase {
85 | continue
86 | }
87 | if failure != "" {
88 | onFail()
89 |
90 | p.Failure(failure)
91 | } else {
92 | p.Success()
93 | }
94 | failure = ""
95 | p = log.StartPhase("["+phase+"]", steady)
96 | }
97 | }()
98 |
99 | logs := log.Writer()
100 | return noopWriteCloser{io.MultiWriter(rw, logs)}
101 | }
102 |
--------------------------------------------------------------------------------
/pkg/runtime/assets/assets.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 assets
6 |
7 | import (
8 | "archive/tar"
9 | "compress/gzip"
10 | "embed"
11 | "encoding/json"
12 | "fmt"
13 | "io"
14 | "log"
15 | "os"
16 | "path"
17 | "path/filepath"
18 | "strings"
19 | "time"
20 | )
21 |
22 | //go:generate sh ../../../hack/update-assets.sh
23 |
24 | //go:embed *.tar.gz
25 | var assetPack embed.FS
26 |
27 | //go:embed images.json
28 | var imagesJSON []byte
29 |
30 | // ImageEnvVars returns the image environment variables embedded in the images.json file
31 | func ImageEnvVars() []string {
32 | var res struct {
33 | Envs []string `json:"envs"`
34 | }
35 | _ = json.Unmarshal(imagesJSON, &res)
36 | return res.Envs
37 | }
38 |
39 | // IsEmbedded returns true if the assets are embedded in this binary
40 | func IsEmbedded() bool {
41 | f, err := assetPack.Open("assets.tar.gz")
42 | if err != nil {
43 | return false
44 | }
45 | f.Close()
46 | return true
47 | }
48 |
49 | // Extract extracts the assets to the destionation directory
50 | func Extract(dest string) error {
51 | f, err := assetPack.Open("assets.tar.gz")
52 | if err != nil {
53 | return err
54 | }
55 | defer f.Close()
56 |
57 | return untar(f, dest)
58 | }
59 |
60 | // untar is copied from https://cs.opensource.google/go/x/build/+/2838fbb2:internal/untar/untar.go;l=27
61 | func untar(r io.Reader, dir string) (err error) {
62 | t0 := time.Now()
63 | nFiles := 0
64 | madeDir := map[string]bool{}
65 | defer func() {
66 | td := time.Since(t0)
67 | if err != nil {
68 | log.Printf("error extracting tarball into %s after %d files, %d dirs, %v: %v", dir, nFiles, len(madeDir), td, err)
69 | }
70 | }()
71 | zr, err := gzip.NewReader(r)
72 | if err != nil {
73 | return fmt.Errorf("requires gzip-compressed body: %v", err)
74 | }
75 | tr := tar.NewReader(zr)
76 | loggedChtimesError := false
77 | for {
78 | f, err := tr.Next()
79 | if err == io.EOF {
80 | break
81 | }
82 | if err != nil {
83 | log.Printf("tar reading error: %v", err)
84 | return fmt.Errorf("tar error: %v", err)
85 | }
86 | if !validRelPath(f.Name) {
87 | return fmt.Errorf("tar contained invalid name error %q", f.Name)
88 | }
89 | rel := filepath.FromSlash(f.Name)
90 | abs := filepath.Join(dir, rel)
91 |
92 | fi := f.FileInfo()
93 | mode := fi.Mode()
94 | switch {
95 | case mode.IsRegular():
96 | // Make the directory. This is redundant because it should
97 | // already be made by a directory entry in the tar
98 | // beforehand. Thus, don't check for errors; the next
99 | // write will fail with the same error.
100 | dir := filepath.Dir(abs)
101 | if !madeDir[dir] {
102 | if err := os.MkdirAll(filepath.Dir(abs), 0755); err != nil {
103 | return err
104 | }
105 | madeDir[dir] = true
106 | }
107 | wf, err := os.OpenFile(abs, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm())
108 | if err != nil {
109 | return err
110 | }
111 | n, err := io.Copy(wf, tr)
112 | if closeErr := wf.Close(); closeErr != nil && err == nil {
113 | err = closeErr
114 | }
115 | if err != nil {
116 | return fmt.Errorf("error writing to %s: %v", abs, err)
117 | }
118 | if n != f.Size {
119 | return fmt.Errorf("only wrote %d bytes to %s; expected %d", n, abs, f.Size)
120 | }
121 | modTime := f.ModTime
122 | if modTime.After(t0) {
123 | // Clamp modtimes at system time. See
124 | // golang.org/issue/19062 when clock on
125 | // buildlet was behind the gitmirror server
126 | // doing the git-archive.
127 | modTime = t0
128 | }
129 | if !modTime.IsZero() {
130 | if err := os.Chtimes(abs, modTime, modTime); err != nil && !loggedChtimesError {
131 | // benign error. Gerrit doesn't even set the
132 | // modtime in these, and we don't end up relying
133 | // on it anywhere (the gomote push command relies
134 | // on digests only), so this is a little pointless
135 | // for now.
136 | log.Printf("error changing modtime: %v (further Chtimes errors suppressed)", err)
137 | loggedChtimesError = true // once is enough
138 | }
139 | }
140 | nFiles++
141 | case mode.IsDir():
142 | if err := os.MkdirAll(abs, 0755); err != nil {
143 | return err
144 | }
145 | madeDir[abs] = true
146 | default:
147 | return fmt.Errorf("tar file entry %s contained unsupported file type %v", f.Name, mode)
148 | }
149 | }
150 | return nil
151 | }
152 |
153 | func validRelativeDir(dir string) bool {
154 | if strings.Contains(dir, `\`) || path.IsAbs(dir) {
155 | return false
156 | }
157 | dir = path.Clean(dir)
158 | if strings.HasPrefix(dir, "../") || strings.HasSuffix(dir, "/..") || dir == ".." {
159 | return false
160 | }
161 | return true
162 | }
163 |
164 | func validRelPath(p string) bool {
165 | if p == "" || strings.Contains(p, `\`) || strings.HasPrefix(p, "/") || strings.Contains(p, "../") {
166 | return false
167 | }
168 | return true
169 | }
170 |
--------------------------------------------------------------------------------
/pkg/runtime/assets/images.json:
--------------------------------------------------------------------------------
1 | {
2 | "supervisor": "eu.gcr.io/gitpod-core-dev/build/supervisor:commit-5ed8a4a6febb4798e700b523934a5ef1d053f236",
3 | "gitpod-code": "eu.gcr.io/gitpod-core-dev/build/ide/code:commit-5ed8a4a6febb4798e700b523934a5ef1d053f236",
4 | "open-vscode": "docker.io/gitpod/openvscode-server:latest@sha256:2d34a8e83a3739d414cfd1de4446d96b498226e12859223642b7a5e3ef9edfaa",
5 | "envs": [
6 | "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
7 | "GITPOD_ENV_APPEND_PATH=/ide/bin/remote-cli:",
8 | "GITPOD_ENV_SET_EDITOR=/ide/bin/remote-cli/gitpod-code",
9 | "GITPOD_ENV_SET_VISUAL=/ide/bin/remote-cli/gitpod-code",
10 | "GITPOD_ENV_SET_GP_OPEN_EDITOR=/ide/bin/remote-cli/gitpod-code",
11 | "GITPOD_ENV_SET_GIT_EDITOR=/ide/bin/remote-cli/gitpod-code --wait",
12 | "GITPOD_ENV_SET_GP_PREVIEW_BROWSER=/ide/bin/remote-cli/gitpod-code --preview",
13 | "GITPOD_ENV_SET_GP_EXTERNAL_BROWSER=/ide/bin/remote-cli/gitpod-code --openExternal"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/runtime/docker.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 runtime
6 |
7 | import (
8 | "context"
9 | "encoding/json"
10 | "fmt"
11 | "io"
12 | "io/ioutil"
13 | "os"
14 | "os/exec"
15 | "path/filepath"
16 | "runtime"
17 | "strings"
18 | "time"
19 |
20 | gitpod "github.com/gitpod-io/gitpod/gitpod-protocol"
21 | "github.com/gitpod-io/gitpod/run-gp/pkg/runtime/assets"
22 | "github.com/gitpod-io/gitpod/run-gp/pkg/telemetry"
23 | )
24 |
25 | type docker struct {
26 | Workdir string
27 | Command string
28 | }
29 |
30 | func (dr docker) Name() string {
31 | return dr.Command
32 | }
33 |
34 | // BuildImage builds the workspace image
35 | func (dr docker) BuildImage(ctx context.Context, logs io.WriteCloser, ref string, cfg *gitpod.GitpodConfig) (err error) {
36 | tmpdir, err := os.MkdirTemp("", "rungp-*")
37 | if err != nil {
38 | return err
39 | }
40 | defer os.RemoveAll(tmpdir)
41 |
42 | defer func() {
43 | if err != nil && telemetry.Enabled() {
44 | telemetry.RecordWorkspaceFailure(telemetry.GetGitRemoteOriginURI(dr.Workdir), "build", dr.Command)
45 | }
46 | }()
47 |
48 | var (
49 | assetsHeader string
50 | assetsCmds string
51 | )
52 | if assets.IsEmbedded() {
53 | err := assets.Extract(tmpdir)
54 | if err != nil {
55 | return err
56 | }
57 | assetsCmds = `
58 | COPY supervisor/ /.supervisor/
59 | COPY ide/ /ide/
60 | `
61 | } else {
62 | return fmt.Errorf("missing assets - please make sure you ran go:generate before")
63 | }
64 |
65 | var baseimage string
66 | switch img := cfg.Image.(type) {
67 | case nil:
68 | baseimage = "FROM gitpod/workspace-full:latest"
69 | case string:
70 | baseimage = "FROM " + img
71 | case map[string]interface{}:
72 | fc, err := json.Marshal(img)
73 | if err != nil {
74 | return err
75 | }
76 | var obj gitpod.Image_object
77 | err = json.Unmarshal(fc, &obj)
78 | if err != nil {
79 | return err
80 | }
81 | fc, err = ioutil.ReadFile(filepath.Join(dr.Workdir, obj.Context, obj.File))
82 | if err != nil {
83 | // TODO(cw): make error actionable
84 | return err
85 | }
86 | baseimage = "\n" + string(fc) + "\n"
87 | default:
88 | return fmt.Errorf("unsupported image: %v", img)
89 | }
90 |
91 | df := `
92 | ` + assetsHeader + `
93 | ` + baseimage + `
94 | ` + assetsCmds + `
95 |
96 | USER root
97 | RUN rm /usr/bin/gp-vncsession || true
98 | RUN mkdir -p /workspace && \
99 | chown -R 33333:33333 /workspace
100 | `
101 | df += strings.Join(assetEnvVars(assets.ImageEnvVars()), "\n")
102 |
103 | fmt.Fprintf(logs, "\nDockerfile:%s\n", df)
104 |
105 | err = ioutil.WriteFile(filepath.Join(tmpdir, "Dockerfile"), []byte(df), 0644)
106 | if err != nil {
107 | return err
108 | }
109 |
110 | cmd := exec.Command(dr.Command, "build", "-t", ref, ".")
111 | cmd.Dir = tmpdir
112 | cmd.Stdout = logs
113 | cmd.Stderr = logs
114 |
115 | go func() {
116 | <-ctx.Done()
117 | if proc := cmd.Process; proc != nil {
118 | proc.Kill()
119 | }
120 | }()
121 |
122 | err = cmd.Run()
123 | if _, ok := err.(*exec.ExitError); ok {
124 | return fmt.Errorf("workspace image build failed")
125 | } else if err != nil {
126 | return err
127 | }
128 |
129 | return nil
130 | }
131 |
132 | func assetEnvVars(input []string) []string {
133 | res := make([]string, 0, len(input))
134 |
135 | for _, env := range input {
136 | segs := strings.Split(env, "=")
137 | if len(segs) != 2 {
138 | continue
139 | }
140 | name, value := segs[0], segs[1]
141 | switch {
142 | case strings.HasPrefix(name, "GITPOD_ENV_SET_"):
143 | res = append(res, fmt.Sprintf("ENV %s=\"%s\"", strings.TrimPrefix(name, "GITPOD_ENV_SET_"), value))
144 | }
145 | }
146 |
147 | return res
148 | }
149 |
150 | // Startworkspace actually runs a workspace using a previously built image
151 | func (dr docker) StartWorkspace(ctx context.Context, workspaceImage string, cfg *gitpod.GitpodConfig, opts StartOpts) (err error) {
152 | var logs io.Writer
153 | if opts.Logs != nil {
154 | logs = opts.Logs
155 | defer opts.Logs.Close()
156 | } else {
157 | logs = io.Discard
158 | }
159 |
160 | if cfg.CheckoutLocation == "" {
161 | return fmt.Errorf("missing checkout location")
162 | }
163 | if cfg.WorkspaceLocation == "" {
164 | return fmt.Errorf("missing workspace location")
165 | }
166 |
167 | name := fmt.Sprintf("rungp-%d", time.Now().UnixNano())
168 | args := []string{"run", "--rm", "--user", "root", "--privileged", "-p", fmt.Sprintf("%d:22999", opts.IDEPort), "-v", fmt.Sprintf("%s:%s", dr.Workdir, filepath.Join("/workspace", cfg.CheckoutLocation)), "--name", name}
169 |
170 | if (runtime.GOOS == "darwin" || runtime.GOOS == "linux") && dr.Command == "docker" {
171 | args = append(args, "-v", "/var/run/docker.sock:/var/run/docker.sock")
172 | }
173 |
174 | tasks, err := json.Marshal(cfg.Tasks)
175 | if err != nil {
176 | return err
177 | }
178 |
179 | envs := map[string]string{
180 | "GITPOD_WORKSPACE_URL": "http://localhost",
181 | "GITPOD_THEIA_PORT": "23000",
182 | "GITPOD_IDE_ALIAS": "code",
183 | "THEIA_WORKSPACE_ROOT": filepath.Join("/workspace", cfg.WorkspaceLocation),
184 | "GITPOD_REPO_ROOT": filepath.Join("/workspace", cfg.CheckoutLocation),
185 | "GITPOD_PREVENT_METADATA_ACCESS": "false",
186 | "GITPOD_WORKSPACE_ID": "a-random-name",
187 | "GITPOD_TASKS": string(tasks),
188 | "GITPOD_HEADLESS": "false",
189 | "GITPOD_HOST": "gitpod.local",
190 | "THEIA_SUPERVISOR_TOKENS": `{"token": "invalid","kind": "gitpod","host": "gitpod.local","scope": [],"expiryDate": ` + time.Now().Format(time.RFC3339) + `,"reuse": 2}`,
191 | "VSX_REGISTRY_URL": "https://https://open-vsx.org/",
192 | }
193 | tmpf, err := ioutil.TempFile("", "rungp-*.env")
194 | if err != nil {
195 | return err
196 | }
197 | for k, v := range envs {
198 | tmpf.WriteString(fmt.Sprintf("%s=%s\n", k, v))
199 | }
200 | tmpf.Close()
201 | args = append(args, "--env-file", tmpf.Name())
202 | defer os.Remove(tmpf.Name())
203 |
204 | if opts.SSHPublicKey != "" {
205 | tmpf, err := ioutil.TempFile("", "rungp-*.pub")
206 | if err != nil {
207 | return err
208 | }
209 | tmpf.WriteString(opts.SSHPublicKey)
210 | tmpf.Close()
211 | args = append(args, "-v", fmt.Sprintf("%s:/home/gitpod/.ssh/authorized_keys", tmpf.Name()))
212 | defer os.Remove(tmpf.Name())
213 | }
214 | if opts.SSHPort > 0 {
215 | args = append(args, "-p", fmt.Sprintf("%d:23001", opts.SSHPort))
216 | }
217 |
218 | if !opts.NoPortForwarding {
219 | for _, p := range cfg.Ports {
220 | args = append(args, "-p", fmt.Sprintf("%d:%d", p.Port.(int)+opts.PortOffset, p.Port))
221 | }
222 | }
223 |
224 | args = append(args, workspaceImage)
225 | args = append(args, "/.supervisor/supervisor", "run", "--rungp")
226 |
227 | if telemetry.Enabled() {
228 | telemetry.RecordWorkspaceStarted(telemetry.GetGitRemoteOriginURI(dr.Workdir), dr.Command)
229 | }
230 |
231 | cmd := exec.Command(dr.Command, args...)
232 | cmd.Dir = dr.Workdir
233 | cmd.Stdout = logs
234 | cmd.Stderr = logs
235 |
236 | go func() {
237 | <-ctx.Done()
238 | if cmd.Process != nil {
239 | cmd.Process.Kill()
240 | }
241 |
242 | exec.Command(dr.Command, "kill", name).CombinedOutput()
243 |
244 | if err != nil && telemetry.Enabled() {
245 | telemetry.RecordWorkspaceFailure(telemetry.GetGitRemoteOriginURI(dr.Workdir), "start", dr.Command)
246 | }
247 | }()
248 |
249 | return cmd.Run()
250 | }
251 |
--------------------------------------------------------------------------------
/pkg/runtime/runtime.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 runtime
6 |
7 | import (
8 | "context"
9 | "fmt"
10 | "io"
11 | "os/exec"
12 |
13 | gitpod "github.com/gitpod-io/gitpod/gitpod-protocol"
14 | "github.com/gitpod-io/gitpod/run-gp/pkg/console"
15 | )
16 |
17 | type SupportedRuntime int
18 |
19 | const (
20 | AutodetectRuntime SupportedRuntime = iota
21 | DockerRuntime
22 | NerdctlRuntime
23 | )
24 |
25 | func New(wd string, rt SupportedRuntime) (RuntimeBuilder, error) {
26 | if rt == AutodetectRuntime {
27 | nrt, err := detectRuntime()
28 | if err != nil {
29 | return nil, err
30 | }
31 | rt = nrt
32 | }
33 |
34 | switch rt {
35 | case DockerRuntime:
36 | console.Default.Debugf("using docker as container runtime")
37 | return &docker{Workdir: wd, Command: "docker"}, nil
38 | case NerdctlRuntime:
39 | console.Default.Debugf("using nerdctl as container runtime")
40 | return &docker{Workdir: wd, Command: "nerdctl"}, nil
41 | default:
42 | return nil, fmt.Errorf("unsupported runtime: %v", rt)
43 | }
44 | }
45 |
46 | func detectRuntime() (SupportedRuntime, error) {
47 | if _, err := exec.LookPath("docker"); err == nil {
48 | return DockerRuntime, nil
49 | }
50 | if _, err := exec.LookPath("nerdctl"); err == nil {
51 | return NerdctlRuntime, nil
52 | }
53 | return AutodetectRuntime, fmt.Errorf("no supported container runtime detected")
54 | }
55 |
56 | type RuntimeBuilder interface {
57 | Runtime
58 | Builder
59 |
60 | Name() string
61 | }
62 |
63 | type Runtime interface {
64 | StartWorkspace(ctx context.Context, imageRef string, cfg *gitpod.GitpodConfig, opts StartOpts) error
65 | }
66 |
67 | type Builder interface {
68 | BuildImage(ctx context.Context, logs io.WriteCloser, ref string, cfg *gitpod.GitpodConfig) (err error)
69 | }
70 |
71 | type StartOpts struct {
72 | PortOffset int
73 | NoPortForwarding bool
74 | IDEPort int
75 | SSHPort int
76 | SSHPublicKey string
77 | Logs io.WriteCloser
78 | }
79 |
--------------------------------------------------------------------------------
/pkg/telemetry/telemetry.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 telemetry
6 |
7 | import (
8 | "crypto/sha256"
9 | "fmt"
10 | "math/rand"
11 | "os/exec"
12 | "runtime"
13 | "strings"
14 | "time"
15 |
16 | segment "github.com/segmentio/analytics-go/v3"
17 | "github.com/sirupsen/logrus"
18 | )
19 |
20 | // Injected at build time
21 | var segmentKey = "TgiJIVvFsBGwmxbnnt5NeeDaian9nr3n"
22 |
23 | var opts struct {
24 | Enabled bool
25 | Identity string
26 | Version string
27 |
28 | client segment.Client
29 | }
30 |
31 | // Init initialises the telemetry
32 | func Init(enabled bool, identity, version string) {
33 | opts.Enabled = enabled
34 | if !enabled {
35 | return
36 | }
37 |
38 | opts.Version = version
39 | if identity == "" {
40 | rand.Seed(time.Now().UnixNano())
41 | letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
42 | b := make([]rune, 32)
43 | for i := range b {
44 | b[i] = letters[rand.Intn(len(letters))]
45 | }
46 | identity = string(b)
47 | }
48 | opts.Identity = identity
49 |
50 | if segmentKey != "" {
51 | opts.client = segment.New(segmentKey)
52 | }
53 | }
54 |
55 | func Close() {
56 | if opts.client != nil {
57 | opts.client.Close()
58 | }
59 | }
60 |
61 | // Identity returns the identity
62 | func Identity() string {
63 | return opts.Identity
64 | }
65 |
66 | // Enabled returns true if the telemetry is enabled
67 | func Enabled() bool {
68 | return opts.Enabled && opts.Identity != "" && opts.client != nil
69 | }
70 |
71 | func track(event string, props segment.Properties) {
72 | if !Enabled() {
73 | return
74 | }
75 | logrus.WithField("props", props).WithField("event", event).Debug("Tracking telemetry")
76 |
77 | opts.client.Enqueue(segment.Track{
78 | AnonymousId: opts.Identity,
79 | Event: event,
80 | Timestamp: time.Now(),
81 | Properties: props,
82 | })
83 | }
84 |
85 | // RecordWorkspaceStarted sends telemetry when a workspace is started
86 | func RecordWorkspaceStarted(remoteURI string, containerRuntime string) {
87 | uriHash := sha256.New()
88 | _, _ = uriHash.Write([]byte(remoteURI))
89 |
90 | track("rungp_start_workspace", defaultProperties().
91 | Set("runtime", containerRuntime).
92 | Set("remoteURIHash", fmt.Sprintf("sha256:%x", uriHash.Sum(nil))),
93 | )
94 | }
95 |
96 | // RecordWorkspaceFailure sets telemetry when a workspace fails
97 | func RecordWorkspaceFailure(remoteURI string, phase string, containerRuntime string) {
98 | uriHash := sha256.New()
99 | _, _ = uriHash.Write([]byte(remoteURI))
100 |
101 | track("rungp_workspace_failure", defaultProperties().
102 | Set("runtime", containerRuntime).
103 | Set("phase", phase).
104 | Set("remoteURIHash", fmt.Sprintf("sha256:%x", uriHash.Sum(nil))),
105 | )
106 | }
107 |
108 | // RecordUpdateStatus records the status of an update
109 | func RecordUpdateStatus(phase, newVersion string, err error) {
110 | props := defaultProperties().
111 | Set("phase", phase).
112 | Set("newVersion", newVersion)
113 | if err != nil {
114 | msg := err.Error()
115 | if len(msg) > 32 {
116 | msg = msg[:32]
117 | }
118 | props = props.Set("error", msg)
119 | }
120 |
121 | track("rungp_autoupdate_status", props)
122 | }
123 |
124 | func defaultProperties() segment.Properties {
125 | return segment.NewProperties().
126 | Set("goos", runtime.GOOS).
127 | Set("goarch", runtime.GOARCH).
128 | Set("version", opts.Version)
129 | }
130 |
131 | // GetGitRemoteOriginURI returns the remote origin URI for the specified working directory.
132 | func GetGitRemoteOriginURI(wd string) string {
133 | git := exec.Command("git", "remote", "get-uri", "origin")
134 | git.Dir = wd
135 | gitout, _ := git.CombinedOutput()
136 | return strings.TrimSpace(string(gitout))
137 | }
138 |
--------------------------------------------------------------------------------
/pkg/update/update.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022 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 update
6 |
7 | import (
8 | "context"
9 | "encoding/hex"
10 | "fmt"
11 | "io"
12 | "net/http"
13 | "os"
14 | "runtime"
15 | "strings"
16 | "time"
17 |
18 | "github.com/Masterminds/semver"
19 | "github.com/gitpod-io/gitpod/run-gp/pkg/console"
20 | "github.com/gitpod-io/gitpod/run-gp/pkg/telemetry"
21 | "github.com/google/go-github/v45/github"
22 | "github.com/inconshreveable/go-update"
23 | "golang.org/x/oauth2"
24 | )
25 |
26 | // Update runs the self-update
27 | func Update(ctx context.Context, currentVersion string, discovery ReleaseDiscovery, cfgFN string) (didUpdate bool, err error) {
28 | if currentVersion == "v0.0.0" {
29 | // development builds don't auto update
30 | return false, nil
31 | }
32 |
33 | cv, err := semver.NewVersion(currentVersion)
34 | if err != nil {
35 | return false, fmt.Errorf("cannot parse current version %s: %v", currentVersion, err)
36 | }
37 |
38 | discoveryCtx, discoveryCancel := context.WithTimeout(ctx, 30*time.Second)
39 | defer discoveryCancel()
40 | latest, err := discovery.DiscoverLatest(discoveryCtx)
41 | if err != nil {
42 | telemetry.RecordUpdateStatus("failed-discover-latest", "", err)
43 | return false, fmt.Errorf("cannot discover latest version: %v", err)
44 | }
45 |
46 | if !cv.LessThan(latest.Version) {
47 | return false, nil
48 | }
49 |
50 | console.Default.Warnf("Newer version found: %s", latest.Name)
51 | console.Default.Warnf("will automatically upgrade. To disable this, run \"run-gp config set autoUpdate.enabled false\"")
52 |
53 | var (
54 | arch = strings.ToLower(runtime.GOARCH)
55 | goos = strings.ToLower(runtime.GOOS)
56 | )
57 |
58 | var candidate *Executable
59 | for _, p := range latest.Platforms {
60 | if strings.ToLower(p.Platform.OS) != goos {
61 | continue
62 | }
63 | if strings.ToLower(p.Platform.Arch) != arch {
64 | continue
65 | }
66 | candidate = &p
67 | break
68 | }
69 | if candidate == nil {
70 | telemetry.RecordUpdateStatus("failed-no-supported-executable", latest.Name, err)
71 | return false, fmt.Errorf("no supported executable found for version %s (OS: %s, Architecture: %s)", latest.Name, goos, arch)
72 | }
73 |
74 | phase := console.Default.StartPhase("self-update", "downloading from "+candidate.URL)
75 | defer func() {
76 | if err == nil {
77 | phase.Success()
78 | } else {
79 | phase.Failure(err.Error())
80 | }
81 | }()
82 |
83 | checksum, err := hex.DecodeString(candidate.Checksum)
84 | if err != nil {
85 | telemetry.RecordUpdateStatus("failed-invalid-checksum", latest.Name, err)
86 | return false, fmt.Errorf("candidate %s has invalid checksum: %w", candidate.URL, err)
87 | }
88 |
89 | if candidate.IsArchive {
90 | telemetry.RecordUpdateStatus("failed-is-archive", latest.Name, err)
91 | return false, fmt.Errorf("not supported")
92 | }
93 |
94 | dl, err := discovery.Download(ctx, *candidate)
95 | if err != nil {
96 | telemetry.RecordUpdateStatus("failed-download", latest.Name, err)
97 | return false, err
98 | }
99 | defer dl.Close()
100 |
101 | err = update.Apply(dl, update.Options{
102 | Checksum: checksum,
103 | })
104 | if err != nil {
105 | telemetry.RecordUpdateStatus("failed-apply", latest.Name, err)
106 | return false, err
107 | }
108 |
109 | telemetry.RecordUpdateStatus("success-apply", latest.Name, nil)
110 | return true, nil
111 | }
112 |
113 | type Updater struct {
114 | Discovery ReleaseDiscovery
115 | }
116 |
117 | type ReleaseDiscovery interface {
118 | DiscoverLatest(ctx context.Context) (*Version, error)
119 | Download(ctx context.Context, e Executable) (io.ReadCloser, error)
120 | }
121 |
122 | type Version struct {
123 | Name string
124 | Version *semver.Version
125 | Platforms []Executable
126 | }
127 |
128 | type Platform struct {
129 | OS string
130 | Arch string
131 | }
132 |
133 | type Executable struct {
134 | Platform Platform
135 |
136 | URL string
137 | Checksum string
138 | Filename string
139 | IsArchive bool
140 | }
141 |
142 | // NewGitHubReleaseDiscovery returns a new Discovery which uses GitHub
143 | func NewGitHubReleaseDiscovery(ctx context.Context) *GitHubReleaseDiscovery {
144 | var client *http.Client
145 |
146 | if token := os.Getenv("GITHUB_TOKEN"); token != "" {
147 | ts := oauth2.StaticTokenSource(
148 | &oauth2.Token{AccessToken: token},
149 | )
150 | client = oauth2.NewClient(ctx, ts)
151 |
152 | } else {
153 | client = &http.Client{Timeout: 30 * time.Second}
154 | }
155 |
156 | return &GitHubReleaseDiscovery{
157 | GitHubClient: github.NewClient(client).Repositories,
158 | HTTPClient: client,
159 | }
160 | }
161 |
162 | type GitHubReleaseDiscovery struct {
163 | GitHubClient GithubClient
164 | HTTPClient *http.Client
165 | }
166 |
167 | type GithubClient interface {
168 | GetLatestRelease(ctx context.Context, owner, repo string) (*github.RepositoryRelease, *github.Response, error)
169 | }
170 |
171 | func (g *GitHubReleaseDiscovery) Download(ctx context.Context, e Executable) (io.ReadCloser, error) {
172 | req, err := http.NewRequest("GET", e.URL, nil)
173 | if err != nil {
174 | return nil, err
175 | }
176 | req.Header.Set("Accept", "application/octet-stream")
177 | req = req.WithContext(ctx)
178 |
179 | resp, err := g.HTTPClient.Do(req)
180 | if err != nil {
181 | return nil, err
182 | }
183 | return resp.Body, nil
184 | }
185 |
186 | // DiscoverLatest discovers the latest release
187 | func (g *GitHubReleaseDiscovery) DiscoverLatest(ctx context.Context) (*Version, error) {
188 | rel, _, err := g.GitHubClient.GetLatestRelease(ctx, "gitpod-io", "run-gp")
189 | if err != nil {
190 | return nil, err
191 | }
192 |
193 | var (
194 | platforms []Executable
195 | checksumAssetURL string
196 | )
197 | for _, asset := range rel.Assets {
198 | if asset.GetName() == "checksums.txt" {
199 | checksumAssetURL = asset.GetURL()
200 | continue
201 | }
202 |
203 | segs := strings.Split(asset.GetName(), "_")
204 | if len(segs) < 4 || 5 < len(segs) {
205 | continue
206 | }
207 |
208 | os, arch := segs[2], segs[3]
209 | if len(segs) == 5 {
210 | arch += "_" + segs[4]
211 | }
212 | arch = strings.TrimSuffix(arch, ".tar.gz")
213 |
214 | platforms = append(platforms, Executable{
215 | Platform: Platform{
216 | OS: os,
217 | Arch: arch,
218 | },
219 | URL: asset.GetURL(),
220 | Filename: asset.GetName(),
221 | IsArchive: strings.HasSuffix(asset.GetName(), ".tar.gz"),
222 | })
223 | }
224 |
225 | if checksumAssetURL != "" {
226 | console.Default.Debugf("downloading checksums from: %s", checksumAssetURL)
227 |
228 | req, err := http.NewRequest("GET", checksumAssetURL, nil)
229 | if err != nil {
230 | return nil, err
231 | }
232 | req.Header.Set("Accept", "application/octet-stream")
233 | resp, err := g.HTTPClient.Do(req)
234 | if err != nil {
235 | return nil, err
236 | }
237 | defer resp.Body.Close()
238 | fc, err := io.ReadAll(resp.Body)
239 | if err != nil {
240 | return nil, err
241 | }
242 |
243 | checksums := parseChecksums(string(fc))
244 | for i, e := range platforms {
245 | c, ok := checksums[e.Filename]
246 | if !ok {
247 | continue
248 | }
249 | e.Checksum = c
250 | platforms[i] = e
251 | }
252 | }
253 |
254 | name := rel.GetName()
255 | version, err := semver.NewVersion(name)
256 | if err != nil {
257 | return nil, err
258 | }
259 |
260 | return &Version{
261 | Name: name,
262 | Version: version,
263 | Platforms: platforms,
264 | }, nil
265 | }
266 |
267 | func parseChecksums(fc string) map[string]string {
268 | lines := strings.Split(fc, "\n")
269 |
270 | res := make(map[string]string, len(lines))
271 | for _, line := range lines {
272 | segs := strings.Fields(line)
273 | if len(segs) != 2 {
274 | continue
275 | }
276 | res[segs[1]] = segs[0]
277 | }
278 | return res
279 | }
280 |
--------------------------------------------------------------------------------