From 89cb3742c63b0046a987b1bfe4b423c945d462cb Mon Sep 17 00:00:00 2001 From: Yuvraj Date: Wed, 18 Aug 2021 18:34:59 +0530 Subject: [PATCH] Add upgrade command with some refactor (#152) * Added k8s check in sandbox * Added upgrade command Signed-off-by: Yuvraj --- flytectl/.github/workflows/sandbox.yaml | 7 +- flytectl/cmd/root.go | 9 +- flytectl/cmd/sandbox/exec.go | 4 +- flytectl/cmd/sandbox/start.go | 182 +++++++++++----- flytectl/cmd/sandbox/start_test.go | 188 ++++++++++++++-- flytectl/cmd/sandbox/teardown_test.go | 5 + flytectl/cmd/upgrade/upgrade.go | 138 ++++++++++++ flytectl/cmd/upgrade/upgrade_test.go | 200 ++++++++++++++++++ flytectl/cmd/version/version.go | 47 ++-- flytectl/cmd/version/version_test.go | 21 +- flytectl/docs/source/nouns.rst | 1 + flytectl/docs/source/verbs.rst | 1 + flytectl/go.mod | 18 +- flytectl/go.sum | 63 +++++- flytectl/pkg/configutil/configutil_test.go | 4 - flytectl/pkg/docker/docker_util.go | 22 +- flytectl/pkg/docker/docker_util_test.go | 19 +- flytectl/pkg/k8s/k8s.go | 33 +++ flytectl/pkg/k8s/k8s_test.go | 65 ++++++ flytectl/pkg/util/githubutil/githubutil.go | 156 ++++++++++++++ .../pkg/util/githubutil/githubutil_test.go | 127 +++++++++++ .../pkg/util/platformutil/platformutil.go | 26 +++ .../util/platformutil/platformutil_test.go | 39 ++++ flytectl/pkg/util/util.go | 67 +++--- flytectl/pkg/util/util_test.go | 64 +++--- 25 files changed, 1284 insertions(+), 222 deletions(-) create mode 100644 flytectl/cmd/upgrade/upgrade.go create mode 100644 flytectl/cmd/upgrade/upgrade_test.go create mode 100644 flytectl/pkg/k8s/k8s.go create mode 100644 flytectl/pkg/k8s/k8s_test.go create mode 100644 flytectl/pkg/util/githubutil/githubutil.go create mode 100644 flytectl/pkg/util/githubutil/githubutil_test.go create mode 100644 flytectl/pkg/util/platformutil/platformutil.go create mode 100644 flytectl/pkg/util/platformutil/platformutil_test.go diff --git a/flytectl/.github/workflows/sandbox.yaml b/flytectl/.github/workflows/sandbox.yaml index 35c10cee19..62ba9f783b 100644 --- a/flytectl/.github/workflows/sandbox.yaml +++ b/flytectl/.github/workflows/sandbox.yaml @@ -20,9 +20,14 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }} - name: Build Flytectl binary run: make compile + - name: Setup env + run: | + mkdir -p ~/.flyte/k3s && touch ~/.flyte/k3s/k3s.yaml && chmod 666 ~/.flyte/k3s/k3s.yaml - name: Create a sandbox cluster run: bin/flytectl sandbox start + - name: Setup flytectl config + run: bin/flytectl config init - name: Register cookbook - run: bin/flytectl register examples -d development -p flytesnacks || true + run: bin/flytectl register examples -d development -p flytesnacks - name: Teardown Sandbox cluster run: bin/flytectl sandbox teardown diff --git a/flytectl/cmd/root.go b/flytectl/cmd/root.go index ee93bf0885..5da86063ce 100644 --- a/flytectl/cmd/root.go +++ b/flytectl/cmd/root.go @@ -18,6 +18,7 @@ import ( "github.com/flyteorg/flytectl/cmd/get" "github.com/flyteorg/flytectl/cmd/register" "github.com/flyteorg/flytectl/cmd/update" + "github.com/flyteorg/flytectl/cmd/upgrade" "github.com/flyteorg/flytectl/cmd/version" "github.com/flyteorg/flytectl/pkg/printer" stdConfig "github.com/flyteorg/flytestdlib/config" @@ -66,8 +67,12 @@ func newRootCmd() *cobra.Command { rootCmd.AddCommand(configuration.CreateConfigCommand()) rootCmd.AddCommand(completionCmd) // Added version command - versioncmd := version.GetVersionCommand(rootCmd) - cmdCore.AddCommands(rootCmd, versioncmd) + versionCmd := version.GetVersionCommand(rootCmd) + cmdCore.AddCommands(rootCmd, versionCmd) + + // Added upgrade command + upgradeCmd := upgrade.SelfUpgrade(rootCmd) + cmdCore.AddCommands(rootCmd, upgradeCmd) config.GetConfig() diff --git a/flytectl/cmd/sandbox/exec.go b/flytectl/cmd/sandbox/exec.go index 63a17e8884..bc42048a86 100644 --- a/flytectl/cmd/sandbox/exec.go +++ b/flytectl/cmd/sandbox/exec.go @@ -25,12 +25,12 @@ func sandboxClusterExec(ctx context.Context, args []string, cmdCtx cmdCore.Comma return err } if len(args) > 0 { - return Execute(ctx, cli, args) + return execute(ctx, cli, args) } return fmt.Errorf("missing argument. Please check usage examples by running flytectl sandbox exec --help") } -func Execute(ctx context.Context, cli docker.Docker, args []string) error { +func execute(ctx context.Context, cli docker.Docker, args []string) error { c := docker.GetSandbox(ctx, cli) if c != nil { exec, err := docker.ExecCommend(ctx, cli, c.ID, args) diff --git a/flytectl/cmd/sandbox/start.go b/flytectl/cmd/sandbox/start.go index 28f142493a..b97c3c1dd2 100644 --- a/flytectl/cmd/sandbox/start.go +++ b/flytectl/cmd/sandbox/start.go @@ -7,22 +7,29 @@ import ( "io" "os" "path/filepath" - "strings" + "time" - "github.com/flyteorg/flytectl/clierrors" - - "github.com/docker/docker/api/types/mount" + "github.com/avast/retry-go" + "github.com/olekukonko/tablewriter" + corev1api "k8s.io/api/core/v1" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - "github.com/flyteorg/flytectl/pkg/configutil" + "github.com/flyteorg/flytectl/pkg/util/githubutil" - f "github.com/flyteorg/flytectl/pkg/filesystemutils" - "github.com/flyteorg/flytectl/pkg/util" + "github.com/flyteorg/flytestdlib/logger" - "github.com/flyteorg/flytectl/pkg/docker" + "github.com/docker/docker/api/types/mount" + "github.com/flyteorg/flytectl/clierrors" + "github.com/flyteorg/flytectl/pkg/configutil" + "github.com/flyteorg/flytectl/pkg/k8s" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/enescakir/emoji" sandboxConfig "github.com/flyteorg/flytectl/cmd/config/subcommand/sandbox" cmdCore "github.com/flyteorg/flytectl/cmd/core" + "github.com/flyteorg/flytectl/pkg/docker" + f "github.com/flyteorg/flytectl/pkg/filesystemutils" + "github.com/flyteorg/flytectl/pkg/util" ) const ( @@ -47,14 +54,16 @@ Run specific version of flyte, Only available after v0.14.0+ Usage ` - GeneratedManifest = "/flyteorg/share/flyte_generated.yaml" - FlyteReleaseURL = "/flyteorg/flyte/releases/download/%v/flyte_sandbox_manifest.yaml" - FlyteMinimumVersionSupported = "v0.14.0" - GithubURL = "https://github.com" + k8sEndpoint = "https://127.0.0.1:30086" + flyteMinimumVersionSupported = "v0.14.0" + generatedManifest = "/flyteorg/share/flyte_generated.yaml" + flyteNamespace = "flyte" + diskPressureTaint = "node.kubernetes.io/disk-pressure" + taintEffect = "NoSchedule" ) var ( - FlyteManifest = f.FilePathJoin(f.UserHomeDir(), ".flyte", "flyte_generated.yaml") + flyteManifest = f.FilePathJoin(f.UserHomeDir(), ".flyte", "flyte_generated.yaml") ) type ExecResult struct { @@ -76,6 +85,24 @@ func startSandboxCluster(ctx context.Context, args []string, cmdCtx cmdCore.Comm if reader != nil { docker.WaitForSandbox(reader, docker.SuccessMessage) } + + var k8sClient k8s.K8s + err = retry.Do( + func() error { + k8sClient, err = k8s.GetK8sClient(docker.Kubeconfig, k8sEndpoint) + return err + }, + retry.Attempts(10), + ) + if err != nil { + return err + } + + if err := watchFlyteDeployment(ctx, k8sClient.CoreV1()); err != nil { + return err + } + + util.PrintSandboxMessage() return nil } @@ -86,7 +113,8 @@ func startSandbox(ctx context.Context, cli docker.Docker, reader io.Reader) (*bu if err.Error() != clierrors.ErrSandboxExists { return nil, err } - printExistingSandboxMessage() + fmt.Printf("Existing details of your sandbox:") + util.PrintSandboxMessage() return nil, nil } @@ -110,14 +138,24 @@ func startSandbox(ctx context.Context, cli docker.Docker, reader io.Reader) (*bu } if len(sandboxConfig.DefaultConfig.Version) > 0 { - if err := downloadFlyteManifest(sandboxConfig.DefaultConfig.Version); err != nil { + isGreater, err := util.IsVersionGreaterThan(sandboxConfig.DefaultConfig.Version, flyteMinimumVersionSupported) + if err != nil { return nil, err } - vol, err := mountVolume(FlyteManifest, GeneratedManifest) - if err != nil { + if !isGreater { + logger.Infof(ctx, "version flag only supported after with flyte %s+ release", flyteMinimumVersionSupported) + return nil, fmt.Errorf("version flag only supported after with flyte %s+ release", flyteMinimumVersionSupported) + } + if err := githubutil.GetFlyteManifest(sandboxConfig.DefaultConfig.Version, flyteManifest); err != nil { return nil, err } - volumes = append(volumes, *vol) + + if vol, err := mountVolume(flyteManifest, generatedManifest); err != nil { + return nil, err + } else if vol != nil { + volumes = append(volumes, *vol) + } + } fmt.Printf("%v pulling docker image %s\n", emoji.Whale, docker.ImageName) @@ -133,18 +171,10 @@ func startSandbox(ctx context.Context, cli docker.Docker, reader io.Reader) (*bu return nil, err } - _, errCh := docker.WatchError(ctx, cli, ID) logReader, err := docker.ReadLogs(ctx, cli, ID) if err != nil { return nil, err } - go func() { - err := <-errCh - if err != nil { - fmt.Printf("err: %v", err) - os.Exit(1) - } - }() return logReader, nil } @@ -164,34 +194,86 @@ func mountVolume(file, destination string) (*mount.Mount, error) { return nil, nil } -func downloadFlyteManifest(version string) error { - isGreater, err := util.IsVersionGreaterThan(version, FlyteMinimumVersionSupported) - if err != nil { - return err +func watchFlyteDeployment(ctx context.Context, appsClient corev1.CoreV1Interface) error { + var data = os.Stdout + table := tablewriter.NewWriter(data) + table.SetHeader([]string{"Service", "Status", "Namespace"}) + table.SetRowLine(true) + + for { + isTaint, err := isNodeTainted(ctx, appsClient) + if err != nil { + return err + } + if isTaint { + return fmt.Errorf("docker sandbox doesn't have sufficient memory available. Please run docker system prune -a --volumes") + } + + pods, err := getFlyteDeployment(ctx, appsClient) + if err != nil { + return err + } + table.ClearRows() + table.SetAutoWrapText(false) + table.SetAutoFormatHeaders(true) + + // Clear os.Stdout + _, _ = data.WriteString("\x1b[3;J\x1b[H\x1b[2J") + + var total, ready int + total = len(pods.Items) + ready = 0 + if total != 0 { + for _, v := range pods.Items { + if isPodReady(v) { + ready++ + } + if len(v.Status.Conditions) > 0 { + table.Append([]string{v.GetName(), string(v.Status.Phase), v.GetNamespace()}) + } + } + table.Render() + if total == ready { + break + } + } + + time.Sleep(40 * time.Second) } - if !isGreater { - return fmt.Errorf("version flag only support %s+ flyte version", FlyteMinimumVersionSupported) + + return nil +} + +func isPodReady(v corev1api.Pod) bool { + if (v.Status.Phase == corev1api.PodRunning) || (v.Status.Phase == corev1api.PodSucceeded) { + return true } - response, err := util.GetRequest(GithubURL, fmt.Sprintf(FlyteReleaseURL, version)) + return false +} + +func getFlyteDeployment(ctx context.Context, client corev1.CoreV1Interface) (*corev1api.PodList, error) { + pods, err := client.Pods(flyteNamespace).List(ctx, v1.ListOptions{}) if err != nil { - return err - } - if err := util.WriteIntoFile(response, FlyteManifest); err != nil { - return err + return nil, err } - return nil + return pods, nil } -func printExistingSandboxMessage() { - kubeconfig := strings.Join([]string{ - "$KUBECONFIG", - f.FilePathJoin(f.UserHomeDir(), ".kube", "config"), - docker.Kubeconfig, - }, ":") - - fmt.Printf("Existing details of your sandbox:") - fmt.Printf("%v %v %v %v %v \n", emoji.ManTechnologist, docker.SuccessMessage, emoji.Rocket, emoji.Rocket, emoji.PartyPopper) - fmt.Printf("Add KUBECONFIG and FLYTECTL_CONFIG to your environment variable \n") - fmt.Printf("export KUBECONFIG=%v \n", kubeconfig) - fmt.Printf("export FLYTECTL_CONFIG=%v \n", configutil.FlytectlConfig) +func isNodeTainted(ctx context.Context, client corev1.CoreV1Interface) (bool, error) { + nodes, err := client.Nodes().List(ctx, v1.ListOptions{}) + if err != nil { + return false, err + } + match := 0 + for _, node := range nodes.Items { + for _, c := range node.Spec.Taints { + if c.Key == diskPressureTaint && c.Effect == taintEffect { + match++ + } + } + } + if match > 0 { + return true, nil + } + return false, nil } diff --git a/flytectl/cmd/sandbox/start_test.go b/flytectl/cmd/sandbox/start_test.go index 73a7eee0a7..f2b43ff8da 100644 --- a/flytectl/cmd/sandbox/start_test.go +++ b/flytectl/cmd/sandbox/start_test.go @@ -10,22 +10,76 @@ import ( "strings" "testing" - cmdCore "github.com/flyteorg/flytectl/cmd/core" - - sandboxConfig "github.com/flyteorg/flytectl/cmd/config/subcommand/sandbox" + "github.com/flyteorg/flytectl/pkg/k8s" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/mount" + sandboxConfig "github.com/flyteorg/flytectl/cmd/config/subcommand/sandbox" + cmdCore "github.com/flyteorg/flytectl/cmd/core" "github.com/flyteorg/flytectl/pkg/docker" "github.com/flyteorg/flytectl/pkg/docker/mocks" f "github.com/flyteorg/flytectl/pkg/filesystemutils" + "github.com/flyteorg/flytectl/pkg/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + testclient "k8s.io/client-go/kubernetes/fake" ) +var content = ` +apiVersion: v1 +clusters: +- cluster: + server: https://localhost:8080 + extensions: + - name: client.authentication.k8s.io/exec + extension: + audience: foo + other: bar + name: foo-cluster +contexts: +- context: + cluster: foo-cluster + user: foo-user + namespace: bar + name: foo-context +current-context: foo-context +kind: Config +users: +- name: foo-user + user: + exec: + apiVersion: client.authentication.k8s.io/v1alpha1 + args: + - arg-1 + - arg-2 + command: foo-command + provideClusterInfo: true +` + +var fakeNode = &corev1.Node{ + Spec: corev1.NodeSpec{ + Taints: []corev1.Taint{}, + }, +} + +var fakePod = corev1.Pod{ + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + Conditions: []corev1.PodCondition{}, + }, +} + func TestStartSandboxFunc(t *testing.T) { p1, p2, _ := docker.GetSandboxPorts() + assert.Nil(t, util.SetupFlyteDir()) + assert.Nil(t, os.MkdirAll(f.FilePathJoin(f.UserHomeDir(), ".flyte", "k3s"), os.ModePerm)) + assert.Nil(t, ioutil.WriteFile(docker.Kubeconfig, []byte(content), os.ModePerm)) + + fakePod.SetName("flyte") + fakePod.SetName("flyte") t.Run("Successfully run sandbox cluster", func(t *testing.T) { ctx := context.Background() @@ -181,8 +235,8 @@ func TestStartSandboxFunc(t *testing.T) { volumes := docker.Volumes volumes = append(volumes, mount.Mount{ Type: mount.TypeBind, - Source: FlyteManifest, - Target: GeneratedManifest, + Source: flyteManifest, + Target: generatedManifest, }) mockDocker.OnContainerCreate(ctx, &container.Config{ Env: docker.Environment, @@ -214,13 +268,13 @@ func TestStartSandboxFunc(t *testing.T) { errCh := make(chan error) bodyStatus := make(chan container.ContainerWaitOKBody) mockDocker := &mocks.Docker{} - sandboxConfig.DefaultConfig.Version = "v0.13.0" + sandboxConfig.DefaultConfig.Version = "v0.1444.0" sandboxConfig.DefaultConfig.Source = "" volumes := docker.Volumes volumes = append(volumes, mount.Mount{ Type: mount.TypeBind, - Source: FlyteManifest, - Target: GeneratedManifest, + Source: flyteManifest, + Target: generatedManifest, }) mockDocker.OnContainerCreate(ctx, &container.Config{ Env: docker.Environment, @@ -252,6 +306,7 @@ func TestStartSandboxFunc(t *testing.T) { errCh := make(chan error) bodyStatus := make(chan container.ContainerWaitOKBody) mockDocker := &mocks.Docker{} + sandboxConfig.DefaultConfig.Source = f.UserHomeDir() volumes := docker.Volumes volumes = append(volumes, mount.Mount{ @@ -361,10 +416,6 @@ func TestStartSandboxFunc(t *testing.T) { _, err := startSandbox(ctx, mockDocker, os.Stdin) assert.NotNil(t, err) }) - t.Run("Failed manifest", func(t *testing.T) { - err := downloadFlyteManifest("v100.9.9") - assert.NotNil(t, err) - }) t.Run("Error in reading logs", func(t *testing.T) { ctx := context.Background() errCh := make(chan error) @@ -445,6 +496,17 @@ func TestStartSandboxFunc(t *testing.T) { cmdCtx := cmdCore.NewCommandContext(nil, *mockOutStream) mockDocker := &mocks.Docker{} errCh := make(chan error) + client := testclient.NewSimpleClientset() + k8s.Client = client + _, err := client.CoreV1().Pods("flyte").Create(ctx, &fakePod, v1.CreateOptions{}) + if err != nil { + t.Error(err) + } + fakeNode.SetName("master") + _, err = client.CoreV1().Nodes().Create(ctx, fakeNode, v1.CreateOptions{}) + if err != nil { + t.Error(err) + } bodyStatus := make(chan container.ContainerWaitOKBody) mockDocker.OnContainerCreate(ctx, &container.Config{ Env: docker.Environment, @@ -472,7 +534,7 @@ func TestStartSandboxFunc(t *testing.T) { mockDocker.OnContainerWaitMatch(ctx, mock.Anything, container.WaitConditionNotRunning).Return(bodyStatus, errCh) docker.Client = mockDocker sandboxConfig.DefaultConfig.Source = "" - err := startSandboxCluster(ctx, []string{}, cmdCtx) + err = startSandboxCluster(ctx, []string{}, cmdCtx) assert.Nil(t, err) }) t.Run("Error in running sandbox cluster command", func(t *testing.T) { @@ -512,3 +574,103 @@ func TestStartSandboxFunc(t *testing.T) { assert.NotNil(t, err) }) } + +func TestMonitorFlyteDeployment(t *testing.T) { + t.Run("Monitor k8s deployment fail because of storage", func(t *testing.T) { + ctx := context.Background() + client := testclient.NewSimpleClientset() + k8s.Client = client + fakePod.SetName("flyte") + fakePod.SetName("flyte") + + _, err := client.CoreV1().Pods("flyte").Create(ctx, &fakePod, v1.CreateOptions{}) + if err != nil { + t.Error(err) + } + fakeNode.SetName("master") + fakeNode.Spec.Taints = append(fakeNode.Spec.Taints, corev1.Taint{ + Effect: "NoSchedule", + Key: "node.kubernetes.io/disk-pressure", + }) + _, err = client.CoreV1().Nodes().Create(ctx, fakeNode, v1.CreateOptions{}) + if err != nil { + t.Error(err) + } + + err = watchFlyteDeployment(ctx, client.CoreV1()) + assert.NotNil(t, err) + + }) + + t.Run("Monitor k8s deployment success", func(t *testing.T) { + ctx := context.Background() + client := testclient.NewSimpleClientset() + k8s.Client = client + fakePod.SetName("flyte") + fakePod.SetName("flyte") + + _, err := client.CoreV1().Pods("flyte").Create(ctx, &fakePod, v1.CreateOptions{}) + if err != nil { + t.Error(err) + } + fakeNode.SetName("master") + fakeNode.Spec.Taints = []corev1.Taint{} + _, err = client.CoreV1().Nodes().Create(ctx, fakeNode, v1.CreateOptions{}) + if err != nil { + t.Error(err) + } + + err = watchFlyteDeployment(ctx, client.CoreV1()) + assert.Nil(t, err) + + }) + +} + +func TestGetFlyteDeploymentCount(t *testing.T) { + + ctx := context.Background() + client := testclient.NewSimpleClientset() + c, err := getFlyteDeployment(ctx, client.CoreV1()) + assert.Nil(t, err) + assert.Equal(t, 0, len(c.Items)) +} + +func TestGetNodeTaintStatus(t *testing.T) { + t.Run("Check node taint with success", func(t *testing.T) { + ctx := context.Background() + client := testclient.NewSimpleClientset() + fakeNode.SetName("master") + _, err := client.CoreV1().Nodes().Create(ctx, fakeNode, v1.CreateOptions{}) + if err != nil { + t.Error(err) + } + c, err := isNodeTainted(ctx, client.CoreV1()) + assert.Nil(t, err) + assert.Equal(t, false, c) + }) + t.Run("Check node taint with fail", func(t *testing.T) { + ctx := context.Background() + client := testclient.NewSimpleClientset() + fakeNode.SetName("master") + _, err := client.CoreV1().Nodes().Create(ctx, fakeNode, v1.CreateOptions{}) + if err != nil { + t.Error(err) + } + node, err := client.CoreV1().Nodes().Get(ctx, "master", v1.GetOptions{}) + if err != nil { + t.Error(err) + } + node.Spec.Taints = append(node.Spec.Taints, corev1.Taint{ + Effect: taintEffect, + Key: diskPressureTaint, + }) + _, err = client.CoreV1().Nodes().Update(ctx, node, v1.UpdateOptions{}) + if err != nil { + t.Error(err) + } + c, err := isNodeTainted(ctx, client.CoreV1()) + assert.Nil(t, err) + assert.Equal(t, true, c) + }) +} diff --git a/flytectl/cmd/sandbox/teardown_test.go b/flytectl/cmd/sandbox/teardown_test.go index 01511d926e..224c905ae1 100644 --- a/flytectl/cmd/sandbox/teardown_test.go +++ b/flytectl/cmd/sandbox/teardown_test.go @@ -6,6 +6,9 @@ import ( "io" "testing" + "github.com/flyteorg/flytectl/pkg/configutil" + "github.com/flyteorg/flytectl/pkg/util" + cmdCore "github.com/flyteorg/flytectl/cmd/core" "github.com/docker/docker/api/types" @@ -47,6 +50,8 @@ func TestTearDownFunc(t *testing.T) { } func TestTearDownClusterFunc(t *testing.T) { + _ = util.SetupFlyteDir() + _ = util.WriteIntoFile([]byte("data"), configutil.FlytectlConfig) mockOutStream := new(io.Writer) ctx := context.Background() cmdCtx := cmdCore.NewCommandContext(nil, *mockOutStream) diff --git a/flytectl/cmd/upgrade/upgrade.go b/flytectl/cmd/upgrade/upgrade.go new file mode 100644 index 0000000000..84f79a915f --- /dev/null +++ b/flytectl/cmd/upgrade/upgrade.go @@ -0,0 +1,138 @@ +package upgrade + +import ( + "context" + "errors" + "fmt" + "os" + "runtime" + "strings" + + "github.com/flyteorg/flytectl/pkg/util" + stdlibversion "github.com/flyteorg/flytestdlib/version" + + "github.com/flyteorg/flytectl/pkg/util/githubutil" + + "github.com/flyteorg/flytestdlib/logger" + "github.com/mouuff/go-rocket-update/pkg/updater" + + cmdCore "github.com/flyteorg/flytectl/cmd/core" + "github.com/flyteorg/flytectl/pkg/util/platformutil" + "github.com/spf13/cobra" +) + +type Goos string + +// Long descriptions are whitespace sensitive when generating docs using sphinx. +const ( + upgradeCmdShort = `Used for upgrade/rollback flyte version` + upgradeCmdLong = ` +Upgrade flytectl +:: + + bin/flytectl upgrade + +Rollback flytectl binary +:: + + bin/flytectl upgrade rollback + +Note: Upgrade is not available on windows +` + rollBackSubCommand = "rollback" +) + +var ( + goos = platformutil.Platform(runtime.GOOS) +) + +// SelfUpgrade will return self upgrade command +func SelfUpgrade(rootCmd *cobra.Command) map[string]cmdCore.CommandEntry { + getResourcesFuncs := map[string]cmdCore.CommandEntry{ + "upgrade": {CmdFunc: selfUpgrade, Aliases: []string{"upgrade"}, ProjectDomainNotRequired: true, + Short: upgradeCmdShort, + Long: upgradeCmdLong}, + } + return getResourcesFuncs +} + +func selfUpgrade(ctx context.Context, args []string, cmdCtx cmdCore.CommandContext) error { + // Check if it's a rollback + if len(args) == 1 { + if args[0] == rollBackSubCommand && !isRollBackSupported(goos) { + return nil + } + ext, err := githubutil.FlytectlReleaseConfig.GetExecutable() + if err != nil { + return err + } + backupBinary := fmt.Sprintf("%s.old", ext) + if _, err := os.Stat(backupBinary); err != nil { + return errors.New("flytectl backup doesn't exist. Rollback is not possible") + } + return githubutil.FlytectlReleaseConfig.Rollback() + } + + if isSupported, err := isUpgradeSupported(goos); err != nil { + return err + } else if !isSupported { + return nil + } + + if message, err := upgrade(githubutil.FlytectlReleaseConfig); err != nil { + return err + } else if len(message) > 0 { + logger.Info(ctx, message) + } + return nil +} + +func upgrade(u *updater.Updater) (string, error) { + updateStatus, err := u.Update() + if err != nil { + return "", err + } + + if updateStatus == updater.Updated { + latestVersion, err := u.GetLatestVersion() + if err != nil { + return "", err + } + return fmt.Sprintf("Successfully updated to version %s", latestVersion), nil + } + return "", u.Rollback() +} + +func isUpgradeSupported(goos platformutil.Platform) (bool, error) { + latest, err := githubutil.FlytectlReleaseConfig.GetLatestVersion() + if err != nil { + return false, err + } + + if isGreater, err := util.IsVersionGreaterThan(latest, stdlibversion.Version); err != nil { + return false, err + } else if !isGreater { + fmt.Println("You have already latest version of flytectl") + return false, nil + } + + message, err := githubutil.GetUpgradeMessage(latest, goos) + if err != nil { + return false, err + } + if goos.String() == platformutil.Windows.String() || strings.Contains(message, "brew") { + if len(message) > 0 { + fmt.Println(message) + } + return false, nil + } + return true, nil +} + +func isRollBackSupported(goos platformutil.Platform) bool { + if goos.String() == platformutil.Windows.String() { + fmt.Printf("Flytectl rollback is not available on %s \n", goos.String()) + return false + } + return true +} diff --git a/flytectl/cmd/upgrade/upgrade_test.go b/flytectl/cmd/upgrade/upgrade_test.go new file mode 100644 index 0000000000..e1996fb24d --- /dev/null +++ b/flytectl/cmd/upgrade/upgrade_test.go @@ -0,0 +1,200 @@ +package upgrade + +import ( + "io" + "sort" + "testing" + + "github.com/flyteorg/flytectl/pkg/util" + "github.com/flyteorg/flytectl/pkg/util/githubutil" + + "github.com/flyteorg/flytectl/pkg/util/platformutil" + + "github.com/flyteorg/flyteidl/clients/go/admin/mocks" + stdlibversion "github.com/flyteorg/flytestdlib/version" + + "context" + + cmdCore "github.com/flyteorg/flytectl/cmd/core" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" +) + +var ( + version = "v0.2.20" + tempExt = "flyte.ext" +) + +func TestUpgradeCommand(t *testing.T) { + rootCmd := &cobra.Command{ + Long: "flytectl is CLI tool written in go to interact with flyteadmin service", + Short: "flyetcl CLI tool", + Use: "flytectl", + DisableAutoGenTag: true, + } + upgradeCmd := SelfUpgrade(rootCmd) + cmdCore.AddCommands(rootCmd, upgradeCmd) + assert.Equal(t, len(rootCmd.Commands()), 1) + cmdNouns := rootCmd.Commands() + // Sort by Use value. + sort.Slice(cmdNouns, func(i, j int) bool { + return cmdNouns[i].Use < cmdNouns[j].Use + }) + + assert.Equal(t, cmdNouns[0].Use, "upgrade") + assert.Equal(t, cmdNouns[0].Short, upgradeCmdShort) + assert.Equal(t, cmdNouns[0].Long, upgradeCmdLong) +} + +// +func TestUpgrade(t *testing.T) { + _ = util.WriteIntoFile([]byte("data"), tempExt) + stdlibversion.Version = version + githubutil.FlytectlReleaseConfig.OverrideExecutable = tempExt + t.Run("Successful upgrade", func(t *testing.T) { + message, err := upgrade(githubutil.FlytectlReleaseConfig) + assert.Nil(t, err) + assert.Equal(t, 39, len(message)) + }) +} + +func TestCheckGoosForRollback(t *testing.T) { + stdlibversion.Version = version + linux := platformutil.Linux + windows := platformutil.Windows + darwin := platformutil.Darwin + githubutil.FlytectlReleaseConfig.OverrideExecutable = tempExt + t.Run("checkGOOSForRollback on linux", func(t *testing.T) { + assert.Equal(t, true, isRollBackSupported(linux)) + assert.Equal(t, false, isRollBackSupported(windows)) + assert.Equal(t, true, isRollBackSupported(darwin)) + }) +} + +func TestIsUpgradeable(t *testing.T) { + stdlibversion.Version = version + githubutil.FlytectlReleaseConfig.OverrideExecutable = tempExt + linux := platformutil.Linux + windows := platformutil.Windows + darwin := platformutil.Darwin + t.Run("IsUpgradeable on linux", func(t *testing.T) { + check, err := isUpgradeSupported(linux) + assert.Nil(t, err) + assert.Equal(t, true, check) + }) + t.Run("IsUpgradeable on darwin", func(t *testing.T) { + check, err := isUpgradeSupported(darwin) + assert.Nil(t, err) + assert.Equal(t, true, check) + }) + t.Run("IsUpgradeable on darwin using brew", func(t *testing.T) { + check, err := isUpgradeSupported(darwin) + assert.Nil(t, err) + assert.Equal(t, true, check) + }) + t.Run("isUpgradeSupported failed", func(t *testing.T) { + stdlibversion.Version = "v" + check, err := isUpgradeSupported(linux) + assert.NotNil(t, err) + assert.Equal(t, false, check) + stdlibversion.Version = version + }) + t.Run("isUpgradeSupported windows", func(t *testing.T) { + check, err := isUpgradeSupported(windows) + assert.Nil(t, err) + assert.Equal(t, false, check) + }) +} + +func TestSelfUpgrade(t *testing.T) { + stdlibversion.Version = version + githubutil.FlytectlReleaseConfig.OverrideExecutable = tempExt + goos = platformutil.Linux + t.Run("Successful upgrade", func(t *testing.T) { + ctx := context.Background() + var args []string + mockClient := new(mocks.AdminServiceClient) + mockOutStream := new(io.Writer) + cmdCtx := cmdCore.NewCommandContext(mockClient, *mockOutStream) + stdlibversion.Build = "" + stdlibversion.BuildTime = "" + stdlibversion.Version = version + + assert.Nil(t, selfUpgrade(ctx, args, cmdCtx)) + }) +} + +func TestSelfUpgradeError(t *testing.T) { + stdlibversion.Version = version + githubutil.FlytectlReleaseConfig.OverrideExecutable = tempExt + goos = platformutil.Linux + t.Run("Successful upgrade", func(t *testing.T) { + ctx := context.Background() + var args []string + mockClient := new(mocks.AdminServiceClient) + mockOutStream := new(io.Writer) + cmdCtx := cmdCore.NewCommandContext(mockClient, *mockOutStream) + stdlibversion.Build = "" + stdlibversion.BuildTime = "" + stdlibversion.Version = "v" + + assert.NotNil(t, selfUpgrade(ctx, args, cmdCtx)) + }) + +} + +func TestSelfUpgradeRollback(t *testing.T) { + stdlibversion.Version = version + githubutil.FlytectlReleaseConfig.OverrideExecutable = tempExt + goos = platformutil.Linux + t.Run("Successful rollback", func(t *testing.T) { + ctx := context.Background() + var args = []string{rollBackSubCommand} + mockClient := new(mocks.AdminServiceClient) + mockOutStream := new(io.Writer) + cmdCtx := cmdCore.NewCommandContext(mockClient, *mockOutStream) + stdlibversion.Build = "" + stdlibversion.BuildTime = "" + stdlibversion.Version = version + assert.Nil(t, selfUpgrade(ctx, args, cmdCtx)) + }) + + t.Run("Successful rollback failed", func(t *testing.T) { + ctx := context.Background() + var args = []string{rollBackSubCommand} + mockClient := new(mocks.AdminServiceClient) + mockOutStream := new(io.Writer) + cmdCtx := cmdCore.NewCommandContext(mockClient, *mockOutStream) + stdlibversion.Build = "" + stdlibversion.BuildTime = "" + stdlibversion.Version = "v100.0.0" + assert.NotNil(t, selfUpgrade(ctx, args, cmdCtx)) + }) + + t.Run("Successful rollback for windows", func(t *testing.T) { + ctx := context.Background() + var args = []string{rollBackSubCommand} + mockClient := new(mocks.AdminServiceClient) + mockOutStream := new(io.Writer) + cmdCtx := cmdCore.NewCommandContext(mockClient, *mockOutStream) + stdlibversion.Build = "" + stdlibversion.BuildTime = "" + stdlibversion.Version = version + goos = platformutil.Windows + assert.Nil(t, selfUpgrade(ctx, args, cmdCtx)) + }) + + t.Run("Successful rollback for windows", func(t *testing.T) { + ctx := context.Background() + var args = []string{rollBackSubCommand} + mockClient := new(mocks.AdminServiceClient) + mockOutStream := new(io.Writer) + cmdCtx := cmdCore.NewCommandContext(mockClient, *mockOutStream) + stdlibversion.Build = "" + stdlibversion.BuildTime = "" + stdlibversion.Version = version + githubutil.FlytectlReleaseConfig.OverrideExecutable = "/" + assert.Nil(t, selfUpgrade(ctx, args, cmdCtx)) + }) + +} diff --git a/flytectl/cmd/version/version.go b/flytectl/cmd/version/version.go index a48c9c6f90..14a6d0bc4c 100644 --- a/flytectl/cmd/version/version.go +++ b/flytectl/cmd/version/version.go @@ -4,9 +4,13 @@ import ( "context" "encoding/json" "fmt" + "runtime" + + "github.com/flyteorg/flytectl/pkg/util/githubutil" + + "github.com/flyteorg/flytectl/pkg/util/platformutil" cmdCore "github.com/flyteorg/flytectl/cmd/core" - "github.com/flyteorg/flytectl/pkg/util" "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/admin" "github.com/flyteorg/flytestdlib/logger" stdlibversion "github.com/flyteorg/flytestdlib/version" @@ -22,15 +26,10 @@ Example version. bin/flytectl version ` - flytectlAppName = "flytectl" - controlPlanAppName = "controlPlane" - GithubAPIURL = "https://api.github.com" - latestVersionMessage = "Installed flytectl version is the latest" - upgradeVersionMessage = "A newer version of flytectl is available [%v] Please upgrade using - https://docs.flyte.org/projects/flytectl/en/latest/index.html" + flytectlAppName = "flytectl" + controlPlanAppName = "controlPlane" ) -var flytectlReleasePath = "/repos/flyteorg/flytectl/releases/latest" - type versionOutput struct { // Specifies the Name of app App string `json:"App,omitempty"` @@ -53,22 +52,20 @@ func GetVersionCommand(rootCmd *cobra.Command) map[string]cmdCore.CommandEntry { } func getVersion(ctx context.Context, args []string, cmdCtx cmdCore.CommandContext) error { - latest, err := getLatestVersion(flytectlReleasePath) - if err != nil { - logger.Errorf(ctx, "Get latest version of flyte got failed", err) - } - - isGreater, err := util.IsVersionGreaterThan(latest, stdlibversion.Version) + goos := platformutil.Platform(runtime.GOOS) + version, err := githubutil.FlytectlReleaseConfig.GetLatestVersion() if err != nil { - logger.Errorf(ctx, "Error while comparing the flytectl version", err) + logger.Error(ctx, "Not able to get latest version because %v", err) + } else { + message, err := githubutil.GetUpgradeMessage(version, goos) + if err != nil { + logger.Error(ctx, "Not able to detect new version because %v", err) + } + if len(message) > 0 { + fmt.Println(message) + } } - message := latestVersionMessage - if isGreater { - message = fmt.Sprintf(upgradeVersionMessage, latest) - } - - fmt.Println(message) // Print Flytectl if err := printVersion(versionOutput{ Build: stdlibversion.Build, @@ -111,11 +108,3 @@ func getControlPlaneVersion(ctx context.Context, cmdCtx cmdCore.CommandContext) } return nil } - -func getLatestVersion(path string) (string, error) { - response, err := util.GetRequest(GithubAPIURL, path) - if err != nil { - return "", err - } - return util.ParseGithubTag(response) -} diff --git a/flytectl/cmd/version/version_test.go b/flytectl/cmd/version/version_test.go index 08681819b0..cd9065258f 100644 --- a/flytectl/cmd/version/version_test.go +++ b/flytectl/cmd/version/version_test.go @@ -66,6 +66,21 @@ func TestVersionCommandFunc(t *testing.T) { mockClient.AssertCalled(t, "GetVersion", ctx, versionRequest) } +func TestVersionCommandFuncError(t *testing.T) { + ctx := context.Background() + var args []string + mockClient := new(mocks.AdminServiceClient) + mockOutStream := new(io.Writer) + cmdCtx := cmdCore.NewCommandContext(mockClient, *mockOutStream) + stdlibversion.Build = "" + stdlibversion.BuildTime = "" + stdlibversion.Version = "v" + mockClient.OnGetVersionMatch(ctx, versionRequest).Return(versionResponse, nil) + err := getVersion(ctx, args, cmdCtx) + assert.Nil(t, err) + mockClient.AssertCalled(t, "GetVersion", ctx, versionRequest) +} + func TestVersionCommandFuncErr(t *testing.T) { ctx := context.Background() var args []string @@ -85,11 +100,6 @@ func TestVersionUtilFunc(t *testing.T) { stdlibversion.Build = "" stdlibversion.BuildTime = "" stdlibversion.Version = testVersion - t.Run("Get latest release with wrong url", func(t *testing.T) { - tag, err := getLatestVersion("h://api.github.com/repos/flyteorg/flytectreleases/latest") - assert.NotNil(t, err) - assert.Equal(t, len(tag), 0) - }) t.Run("Error in getting control plan version", func(t *testing.T) { ctx := context.Background() mockClient := new(mocks.AdminServiceClient) @@ -103,7 +113,6 @@ func TestVersionUtilFunc(t *testing.T) { ctx := context.Background() mockClient := new(mocks.AdminServiceClient) mockOutStream := new(io.Writer) - flytectlReleasePath = "/release" cmdCtx := cmdCore.NewCommandContext(mockClient, *mockOutStream) mockClient.OnGetVersionMatch(ctx, &admin.GetVersionRequest{}).Return(nil, fmt.Errorf("error")) err := getVersion(ctx, []string{}, cmdCtx) diff --git a/flytectl/docs/source/nouns.rst b/flytectl/docs/source/nouns.rst index 12d6444a10..0afad1c0c0 100644 --- a/flytectl/docs/source/nouns.rst +++ b/flytectl/docs/source/nouns.rst @@ -45,3 +45,4 @@ Flytectl noun specify the resource on which the action needs to be performed eg: gen/flytectl_sandbox_status gen/flytectl_sandbox_teardown gen/flytectl_sandbox_exec + gen/flytectl_upgrade diff --git a/flytectl/docs/source/verbs.rst b/flytectl/docs/source/verbs.rst index e9185691cf..eab243c410 100644 --- a/flytectl/docs/source/verbs.rst +++ b/flytectl/docs/source/verbs.rst @@ -15,3 +15,4 @@ Flytectl verbs specify the actions to be performed on the resources like create/ gen/flytectl_config gen/flytectl_sandbox gen/flytectl_version + gen/flytectl_upgrade diff --git a/flytectl/go.mod b/flytectl/go.mod index 438cc0c3b4..d9bdf0a8ef 100644 --- a/flytectl/go.mod +++ b/flytectl/go.mod @@ -4,6 +4,7 @@ go 1.13 require ( github.com/Microsoft/go-winio v0.5.0 // indirect + github.com/avast/retry-go v3.0.0+incompatible github.com/awalterschulze/gographviz v2.0.3+incompatible github.com/containerd/containerd v1.5.2 // indirect github.com/disiqueira/gotree v1.0.0 @@ -14,35 +15,42 @@ require ( github.com/flyteorg/flytestdlib v0.3.30 github.com/ghodss/yaml v1.0.0 github.com/golang/protobuf v1.4.3 - github.com/google/go-cmp v0.5.6 // indirect github.com/google/go-github v17.0.0+incompatible + github.com/google/go-github/v37 v37.0.0 github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.2.0 github.com/gorilla/mux v1.8.0 // indirect github.com/hashicorp/go-version v1.3.0 github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23 - github.com/kr/text v0.2.0 // indirect github.com/landoop/tableprinter v0.0.0-20180806200924-8bd8c2576d27 github.com/manifoldco/promptui v0.8.0 - github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mattn/go-isatty v0.0.13 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mitchellh/mapstructure v1.4.1 github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/mouuff/go-rocket-update v1.5.1 + github.com/olekukonko/tablewriter v0.0.5 github.com/opencontainers/image-spec v1.0.1 github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 + github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.0 github.com/spf13/cobra v1.1.3 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.7.0 github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 github.com/zalando/go-keyring v0.1.1 + golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013 + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect + golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect google.golang.org/grpc v1.36.0 google.golang.org/protobuf v1.25.0 - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect gotest.tools v2.2.0+incompatible + k8s.io/api v0.21.3 + k8s.io/apimachinery v0.21.3 + k8s.io/client-go v0.21.3 sigs.k8s.io/yaml v1.2.0 ) diff --git a/flytectl/go.sum b/flytectl/go.sum index 15ee968a20..4633260946 100644 --- a/flytectl/go.sum +++ b/flytectl/go.sum @@ -122,6 +122,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= +github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/awalterschulze/gographviz v2.0.3+incompatible h1:9sVEXJBJLwGX7EQVhLm2elIKCm7P2YHFC8v6096G09E= github.com/awalterschulze/gographviz v2.0.3+incompatible/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= @@ -337,6 +339,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ernesto-jimenez/gogen v0.0.0-20180125220232-d7d4131e6607/go.mod h1:Cg4fM0vhYWOZdgM7RIOSTRNIc8/VT7CXClC3Ni86lu4= +github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= @@ -448,9 +451,13 @@ github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-github/v37 v37.0.0 h1:rCspN8/6kB1BAJWZfuafvHhyfIo5fkAulaP/3bOQ/tM= +github.com/google/go-github/v37 v37.0.0/go.mod h1:LM7in3NmXDrX58GbEHy7FtNLbI2JijX93RnMKvWG3m4= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -478,6 +485,7 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -540,6 +548,7 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1: github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -557,6 +566,7 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= @@ -567,6 +577,8 @@ github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23 h1:M8exrBzuhWcU6aoHJlHWPe4qFjVKzkMGRal78f5jRRU= github.com/kataras/tablewriter v0.0.0-20180708051242-e063d29b7c23/go.mod h1:kBSna6b0/RzsOcOZf515vAXwSsXYusl2U7SA0XP09yI= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -612,11 +624,13 @@ github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= @@ -644,12 +658,16 @@ github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/f github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mouuff/go-rocket-update v1.5.1 h1:qGgUu/MP+aVQ63laEguRNimmNTPKs29xz0lZW6QRFaQ= +github.com/mouuff/go-rocket-update v1.5.1/go.mod h1:CnOyUYCxAJyC1g1mebSGC7gJysLTlX+RpxKgD1B0zLs= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= @@ -669,11 +687,14 @@ github.com/ncw/swift v1.0.53 h1:luHjjTNtekIEvHg5KdAFIBaH7bWfNkefwFnpDffSIks= github.com/ncw/swift v1.0.53/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -681,12 +702,14 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -788,6 +811,8 @@ github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3x github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -940,8 +965,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1024,6 +1051,7 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1122,10 +1150,16 @@ golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1139,8 +1173,9 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1339,6 +1374,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= @@ -1349,6 +1385,7 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= @@ -1382,20 +1419,24 @@ k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.2/go.mod h1:d7n6Ehyzx+S+cE3VhTGfVNNqtGc/oL9DCdYYahlurV8= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= +k8s.io/api v0.21.3 h1:cblWILbLO8ar+Fj6xdDGr603HRsf8Wu9E9rngJeprZQ= +k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg= k8s.io/apimachinery v0.0.0-20210217011835-527a61b4dffe/go.mod h1:Z7ps/g0rjlTeMstYrMOUttJfT2Gg34DEaG/f2PYLCWY= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.6 h1:R5p3SlhaABYShQSO6LpPsYHjV05Q+79eBUR0Ut/f4tk= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= +k8s.io/apimachinery v0.21.3 h1:3Ju4nvjCngxxMYby0BimUk+pQHPOQp3eCGChk5kfVII= +k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/client-go v0.0.0-20210217172142-7279fc64d847/go.mod h1:q0EaghmVye2uui19vxSZ2NG6ssgUWgjudO6vrwXneSI= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= -k8s.io/client-go v0.20.6 h1:nJZOfolnsVtDtbGJNCxzOtKUAu7zvXjB8+pMo9UNxZo= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= +k8s.io/client-go v0.21.3 h1:J9nxZTOmvkInRDCzcSNQmPJbDYN/PjlxXT9Mos3HcLg= +k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= @@ -1406,10 +1447,14 @@ k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.5.0 h1:8mOnjf1RmUPW6KRqQCfYSZq/K20Unmp3IhuZUhxl8KI= k8s.io/klog/v2 v2.5.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.8.0 h1:Q3gmuM9hKEjefWFFYF0Mat+YyFJvsUyYuwyNNJ5C9Ts= +k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0= +k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= @@ -1418,6 +1463,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyz sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/flytectl/pkg/configutil/configutil_test.go b/flytectl/pkg/configutil/configutil_test.go index f65c8706e7..6908f06144 100644 --- a/flytectl/pkg/configutil/configutil_test.go +++ b/flytectl/pkg/configutil/configutil_test.go @@ -5,8 +5,6 @@ import ( "os" "testing" - "github.com/flyteorg/flytectl/pkg/util" - f "github.com/flyteorg/flytectl/pkg/filesystemutils" "github.com/stretchr/testify/assert" ) @@ -41,8 +39,6 @@ func TestSetupFlytectlConfig(t *testing.T) { if os.IsNotExist(err) { _ = os.MkdirAll(f.FilePathJoin(f.UserHomeDir(), ".flyte"), 0755) } - err = util.SetupFlyteDir() - assert.Nil(t, err) err = SetupConfig("version.yaml", AdminConfigTemplate, templateValue) assert.Nil(t, err) _, err = os.Stat("version.yaml") diff --git a/flytectl/pkg/docker/docker_util.go b/flytectl/pkg/docker/docker_util.go index 7e317d4878..3e1d55e46c 100644 --- a/flytectl/pkg/docker/docker_util.go +++ b/flytectl/pkg/docker/docker_util.go @@ -11,8 +11,6 @@ import ( "github.com/flyteorg/flytectl/clierrors" - "github.com/flyteorg/flytectl/pkg/configutil" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/mount" @@ -25,7 +23,7 @@ import ( var ( Kubeconfig = f.FilePathJoin(f.UserHomeDir(), ".flyte", "k3s", "k3s.yaml") - SuccessMessage = "Flyte is ready! Flyte UI is available at http://localhost:30081/console" + SuccessMessage = "Deploying Flyte..." ImageName = "cr.flyte.org/flyteorg/flyte-sandbox:dind" FlyteSandboxClusterName = "flyte-sandbox" Environment = []string{"SANDBOX=1", "KUBERNETES_API_PORT=30086", "FLYTE_HOST=localhost:30081", "FLYTE_AWS_ENDPOINT=http://localhost:30084"} @@ -122,11 +120,6 @@ func StartContainer(ctx context.Context, cli Docker, volumes []mount.Mount, expo return resp.ID, nil } -// WatchError will return channel for watching errors of a container -func WatchError(ctx context.Context, cli Docker, id string) (<-chan container.ContainerWaitOKBody, <-chan error) { - return cli.ContainerWait(ctx, id, container.WaitConditionNotRunning) -} - // ReadLogs will return io scanner for reading the logs of a container func ReadLogs(ctx context.Context, cli Docker, id string) (*bufio.Scanner, error) { reader, err := cli.ContainerLogs(ctx, id, types.ContainerLogsOptions{ @@ -145,19 +138,6 @@ func ReadLogs(ctx context.Context, cli Docker, id string) (*bufio.Scanner, error func WaitForSandbox(reader *bufio.Scanner, message string) bool { for reader.Scan() { if strings.Contains(reader.Text(), message) { - kubeconfig := strings.Join([]string{ - "$KUBECONFIG", - f.FilePathJoin(f.UserHomeDir(), ".kube", "config"), - Kubeconfig, - }, ":") - - fmt.Printf("%v %v %v %v %v \n", emoji.ManTechnologist, message, emoji.Rocket, emoji.Rocket, emoji.PartyPopper) - fmt.Printf("Please visit https://github.com/flyteorg/flytesnacks for more example %v \n", emoji.Rocket) - fmt.Printf("Register all flytesnacks example by running 'flytectl register examples -d development -p flytesnacks' \n") - - fmt.Printf("Add KUBECONFIG and FLYTECTL_CONFIG to your environment variable \n") - fmt.Printf("export KUBECONFIG=%v \n", kubeconfig) - fmt.Printf("export FLYTECTL_CONFIG=%v \n", configutil.FlytectlConfig) return true } fmt.Println(reader.Text()) diff --git a/flytectl/pkg/docker/docker_util_test.go b/flytectl/pkg/docker/docker_util_test.go index 25e67bad43..5aa2b34896 100644 --- a/flytectl/pkg/docker/docker_util_test.go +++ b/flytectl/pkg/docker/docker_util_test.go @@ -8,9 +8,10 @@ import ( "strings" "testing" + f "github.com/flyteorg/flytectl/pkg/filesystemutils" + "github.com/docker/docker/api/types/container" "github.com/flyteorg/flytectl/pkg/docker/mocks" - "github.com/flyteorg/flytectl/pkg/util" "github.com/stretchr/testify/mock" "github.com/docker/docker/api/types" @@ -28,7 +29,10 @@ var ( func setupSandbox() { mockAdminClient := u.MockClient cmdCtx = cmdCore.NewCommandContext(mockAdminClient, u.MockOutStream) - _ = util.SetupFlyteDir() + err := os.MkdirAll(f.FilePathJoin(f.UserHomeDir(), ".flyte"), os.ModePerm) + if err != nil { + fmt.Println(err) + } container1 := types.Container{ ID: "FlyteSandboxClusterName", Names: []string{ @@ -198,17 +202,6 @@ func TestStartContainer(t *testing.T) { }) } -func TestWatchError(t *testing.T) { - setupSandbox() - mockDocker := &mocks.Docker{} - context := context.Background() - errCh := make(chan error) - bodyStatus := make(chan container.ContainerWaitOKBody) - mockDocker.OnContainerWaitMatch(context, mock.Anything, container.WaitConditionNotRunning).Return(bodyStatus, errCh) - _, err := WatchError(context, mockDocker, "test") - assert.NotNil(t, err) -} - func TestReadLogs(t *testing.T) { setupSandbox() diff --git a/flytectl/pkg/k8s/k8s.go b/flytectl/pkg/k8s/k8s.go new file mode 100644 index 0000000000..f17082dbe0 --- /dev/null +++ b/flytectl/pkg/k8s/k8s.go @@ -0,0 +1,33 @@ +package k8s + +import ( + "os" + + "github.com/pkg/errors" + "k8s.io/client-go/kubernetes" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/tools/clientcmd" +) + +type K8s interface { + CoreV1() corev1.CoreV1Interface +} + +var Client K8s + +// GetK8sClient return the k8s client from sandbox kubeconfig +func GetK8sClient(cfg, master string) (K8s, error) { + kubeConfigPath := os.ExpandEnv(cfg) + kubecfg, err := clientcmd.BuildConfigFromFlags(master, kubeConfigPath) + if err != nil { + return nil, errors.Wrapf(err, "Error building kubeconfig") + } + if Client == nil { + kubeClient, err := kubernetes.NewForConfig(kubecfg) + if err != nil { + return nil, errors.Wrapf(err, "Error building kubernetes clientset") + } + return kubeClient, nil + } + return Client, nil +} diff --git a/flytectl/pkg/k8s/k8s_test.go b/flytectl/pkg/k8s/k8s_test.go new file mode 100644 index 0000000000..0cf3db31bb --- /dev/null +++ b/flytectl/pkg/k8s/k8s_test.go @@ -0,0 +1,65 @@ +package k8s + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" + testclient "k8s.io/client-go/kubernetes/fake" +) + +func TestGetK8sClient(t *testing.T) { + content := ` +apiVersion: v1 +clusters: +- cluster: + server: https://localhost:8080 + extensions: + - name: client.authentication.k8s.io/exec + extension: + audience: foo + other: bar + name: foo-cluster +contexts: +- context: + cluster: foo-cluster + user: foo-user + namespace: bar + name: foo-context +current-context: foo-context +kind: Config +users: +- name: foo-user + user: + exec: + apiVersion: client.authentication.k8s.io/v1alpha1 + args: + - arg-1 + - arg-2 + command: foo-command + provideClusterInfo: true +` + tmpfile, err := ioutil.TempFile("", "kubeconfig") + if err != nil { + t.Error(err) + } + defer os.Remove(tmpfile.Name()) + if err := ioutil.WriteFile(tmpfile.Name(), []byte(content), os.ModePerm); err != nil { + t.Error(err) + } + t.Run("Create client from config", func(t *testing.T) { + client := testclient.NewSimpleClientset() + Client = client + c, err := GetK8sClient(tmpfile.Name(), "https://localhost:8080") + assert.Nil(t, err) + assert.NotNil(t, c) + }) + t.Run("Create client from config", func(t *testing.T) { + Client = nil + client, err := GetK8sClient(tmpfile.Name(), "https://localhost:8080") + assert.Nil(t, err) + assert.NotNil(t, client) + }) + +} diff --git a/flytectl/pkg/util/githubutil/githubutil.go b/flytectl/pkg/util/githubutil/githubutil.go new file mode 100644 index 0000000000..c3ed9b8b52 --- /dev/null +++ b/flytectl/pkg/util/githubutil/githubutil.go @@ -0,0 +1,156 @@ +package githubutil + +import ( + "context" + "path/filepath" + "runtime" + "strings" + + "github.com/flyteorg/flytectl/pkg/util/platformutil" + stdlibversion "github.com/flyteorg/flytestdlib/version" + "github.com/mouuff/go-rocket-update/pkg/provider" + "github.com/mouuff/go-rocket-update/pkg/updater" + + "github.com/flyteorg/flytectl/pkg/util" + + "fmt" + "io/ioutil" + + "github.com/google/go-github/v37/github" +) + +const ( + owner = "flyteorg" + flyte = "flyte" + sandboxManifest = "flyte_sandbox_manifest.yaml" + flytectl = "flytectl" + flytectlRepository = "github.com/flyteorg/flytectl" + commonMessage = "\n A new release of flytectl is available: %s → %s \n" + brewMessage = "To upgrade, run: brew update && brew upgrade flytectl \n" + linuxMessage = "To upgrade, run: flytectl upgrade \n" + darwinMessage = "To upgrade, run: flytectl upgrade \n" + releaseURL = "https://github.com/flyteorg/flytectl/releases/tag/%s \n" + brewInstallDirectory = "/Cellar/flytectl" +) + +// FlytectlReleaseConfig represent the updater config for flytectl binary +var FlytectlReleaseConfig = &updater.Updater{ + Provider: &provider.Github{ + RepositoryURL: flytectlRepository, + ArchiveName: getFlytectlAssetName(), + }, + ExecutableName: flytectl, + Version: stdlibversion.Version, +} + +var ( + arch = platformutil.Arch(runtime.GOARCH) +) + +// GetLatestVersion returns the latest version of provided repository +func GetLatestVersion(repository string) (*github.RepositoryRelease, error) { + client := github.NewClient(nil) + release, _, err := client.Repositories.GetLatestRelease(context.Background(), owner, repository) + if err != nil { + return nil, err + } + return release, err +} + +func getFlytectlAssetName() string { + if arch == platformutil.ArchAmd64 { + arch = platformutil.ArchX86 + } else if arch == platformutil.ArchX86 { + arch = platformutil.Archi386 + } + return fmt.Sprintf("flytectl_%s_%s.tar.gz", strings.Title(runtime.GOOS), arch.String()) +} + +// CheckVersionExist returns the provided version release if version exist in repository +func CheckVersionExist(version, repository string) (*github.RepositoryRelease, error) { + client := github.NewClient(nil) + release, _, err := client.Repositories.GetReleaseByTag(context.Background(), owner, repository, version) + if err != nil { + return nil, err + } + return release, err +} + +// GetAssetsFromRelease returns the asset from github release +func GetAssetsFromRelease(version, assets, repository string) (*github.ReleaseAsset, error) { + release, err := CheckVersionExist(version, repository) + if err != nil { + return nil, err + } + for _, v := range release.Assets { + if v.GetName() == assets { + return v, nil + } + } + return nil, fmt.Errorf("assest is not found in %s[%s] release", repository, version) +} + +// GetFlyteManifest will write the flyte manifest in a file +func GetFlyteManifest(version string, target string) error { + asset, err := GetAssetsFromRelease(version, sandboxManifest, flyte) + if err != nil { + return err + } + response, err := util.SendRequest("GET", asset.GetBrowserDownloadURL(), nil) + if err != nil { + return err + } + defer response.Body.Close() + data, err := ioutil.ReadAll(response.Body) + if err != nil { + return err + } + if err := util.WriteIntoFile(data, target); err != nil { + return err + } + return nil + +} + +// GetUpgradeMessage return the upgrade message +func GetUpgradeMessage(latest string, goos platformutil.Platform) (string, error) { + isGreater, err := util.IsVersionGreaterThan(latest, stdlibversion.Version) + if err != nil { + return "", err + } + message := fmt.Sprintf(commonMessage, stdlibversion.Version, latest) + if isGreater { + symlink, err := CheckBrewInstall(goos) + if err != nil { + return "", err + } + if len(symlink) > 0 { + message += brewMessage + } else if goos == platformutil.Darwin { + message += darwinMessage + } else if goos == platformutil.Linux { + message += linuxMessage + } + message += fmt.Sprintf(releaseURL, latest) + } + + return message, nil +} + +// CheckBrewInstall returns the path of symlink if flytectl is installed from brew +func CheckBrewInstall(goos platformutil.Platform) (string, error) { + if goos.String() == platformutil.Darwin.String() { + executable, err := FlytectlReleaseConfig.GetExecutable() + if err != nil { + return executable, err + } + if symlink, err := filepath.EvalSymlinks(executable); err != nil { + return symlink, err + } else if len(symlink) > 0 { + if strings.Contains(symlink, brewInstallDirectory) { + return symlink, nil + } + } + } + return "", nil +} diff --git a/flytectl/pkg/util/githubutil/githubutil_test.go b/flytectl/pkg/util/githubutil/githubutil_test.go new file mode 100644 index 0000000000..05566121b9 --- /dev/null +++ b/flytectl/pkg/util/githubutil/githubutil_test.go @@ -0,0 +1,127 @@ +package githubutil + +import ( + "fmt" + "runtime" + "strings" + "testing" + + stdlibversion "github.com/flyteorg/flytestdlib/version" + + "github.com/flyteorg/flytectl/pkg/util/platformutil" + + "github.com/stretchr/testify/assert" +) + +func TestGetLatestVersion(t *testing.T) { + t.Run("Get latest release with wrong url", func(t *testing.T) { + _, err := GetLatestVersion("fl") + assert.NotNil(t, err) + }) + t.Run("Get latest release", func(t *testing.T) { + _, err := GetLatestVersion("flytectl") + assert.Nil(t, err) + }) +} + +func TestGetLatestRelease(t *testing.T) { + release, err := GetLatestVersion("flyte") + assert.Nil(t, err) + assert.Equal(t, true, strings.HasPrefix(release.GetTagName(), "v")) +} + +func TestCheckVersionExist(t *testing.T) { + t.Run("Invalid Tag", func(t *testing.T) { + _, err := CheckVersionExist("v100.0.0", "flyte") + assert.NotNil(t, err) + }) + t.Run("Valid Tag", func(t *testing.T) { + release, err := CheckVersionExist("v0.15.0", "flyte") + assert.Nil(t, err) + assert.Equal(t, true, strings.HasPrefix(release.GetTagName(), "v")) + }) +} + +func TestGetAssetsFromRelease(t *testing.T) { + t.Run("Successful get assets", func(t *testing.T) { + assets, err := GetAssetsFromRelease("v0.15.0", sandboxManifest, flyte) + assert.Nil(t, err) + assert.NotNil(t, assets) + assert.Equal(t, sandboxManifest, *assets.Name) + }) + + t.Run("Failed get assets with wrong name", func(t *testing.T) { + assets, err := GetAssetsFromRelease("v0.15.0", "test", flyte) + assert.NotNil(t, err) + assert.Nil(t, assets) + }) + t.Run("Successful get assets with wrong version", func(t *testing.T) { + assets, err := GetAssetsFromRelease("v100.15.0", "test", flyte) + assert.NotNil(t, err) + assert.Nil(t, assets) + }) +} + +func TestGetFlyteManifest(t *testing.T) { + t.Run("Successful get manifest", func(t *testing.T) { + err := GetFlyteManifest("v0.15.0", "test.yaml") + assert.Nil(t, err) + }) + t.Run("Failed get manifest with wrong name", func(t *testing.T) { + err := GetFlyteManifest("v100.15.0", "test.yaml") + assert.NotNil(t, err) + }) + t.Run("Failed get manifest with wrong name", func(t *testing.T) { + err := GetFlyteManifest("v0.12.0", "test.yaml") + assert.NotNil(t, err) + }) +} + +func TestGetAssetsName(t *testing.T) { + t.Run("Get Assets name", func(t *testing.T) { + expected := fmt.Sprintf("flytectl_%s_386.tar.gz", strings.Title(runtime.GOOS)) + arch = platformutil.Arch386 + assert.Equal(t, expected, getFlytectlAssetName()) + }) +} + +func TestCheckBrewInstall(t *testing.T) { + symlink, err := CheckBrewInstall(platformutil.Darwin) + assert.Nil(t, err) + assert.Equal(t, len(symlink), 0) + symlink, err = CheckBrewInstall(platformutil.Linux) + assert.Nil(t, err) + assert.Equal(t, 0, len(symlink)) +} + +func TestGetUpgradeMessage(t *testing.T) { + var darwin = platformutil.Darwin + var linux = platformutil.Linux + var windows = platformutil.Linux + + var version = "v0.2.20" + stdlibversion.Version = "v0.2.10" + message, err := GetUpgradeMessage(version, darwin) + assert.Nil(t, err) + assert.Equal(t, 157, len(message)) + + version = "v0.2.09" + message, err = GetUpgradeMessage(version, darwin) + assert.Nil(t, err) + assert.Equal(t, 63, len(message)) + + version = "v" + message, err = GetUpgradeMessage(version, darwin) + assert.NotNil(t, err) + assert.Equal(t, 0, len(message)) + + version = "v0.2.20" + message, err = GetUpgradeMessage(version, windows) + assert.Nil(t, err) + assert.Equal(t, 157, len(message)) + + version = "v0.2.20" + message, err = GetUpgradeMessage(version, linux) + assert.Nil(t, err) + assert.Equal(t, 157, len(message)) +} diff --git a/flytectl/pkg/util/platformutil/platformutil.go b/flytectl/pkg/util/platformutil/platformutil.go new file mode 100644 index 0000000000..064a6d8e14 --- /dev/null +++ b/flytectl/pkg/util/platformutil/platformutil.go @@ -0,0 +1,26 @@ +package platformutil + +type Arch string + +const ( + ArchAmd64 Arch = "amd64" + ArchX86 Arch = "x86_64" + Arch386 Arch = "386" + Archi386 Arch = "i386" +) + +func (a Arch) String() string { + return string(a) +} + +type Platform string + +const ( + Windows Platform = "windows" + Linux Platform = "linux" + Darwin Platform = "darwin" +) + +func (p Platform) String() string { + return string(p) +} diff --git a/flytectl/pkg/util/platformutil/platformutil_test.go b/flytectl/pkg/util/platformutil/platformutil_test.go new file mode 100644 index 0000000000..8c7ee7f81d --- /dev/null +++ b/flytectl/pkg/util/platformutil/platformutil_test.go @@ -0,0 +1,39 @@ +package platformutil + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestArch(t *testing.T) { + var amd64 = ArchAmd64 + assert.NotNil(t, amd64) + assert.Equal(t, "amd64", amd64.String()) + + var arch386 = Arch386 + assert.NotNil(t, arch386) + assert.Equal(t, "386", arch386.String()) + + var i386 = Archi386 + assert.NotNil(t, i386) + assert.Equal(t, "i386", i386.String()) + + var x8664 = ArchX86 + assert.NotNil(t, x8664) + assert.Equal(t, "x86_64", x8664.String()) +} + +func TestGoosEnum(t *testing.T) { + var linux = Linux + assert.NotNil(t, linux) + assert.Equal(t, "linux", linux.String()) + + var windows = Windows + assert.NotNil(t, windows) + assert.Equal(t, "windows", windows.String()) + + var darwin = Darwin + assert.NotNil(t, darwin) + assert.Equal(t, "darwin", darwin.String()) +} diff --git a/flytectl/pkg/util/util.go b/flytectl/pkg/util/util.go index fe972ed2d8..99a2408458 100644 --- a/flytectl/pkg/util/util.go +++ b/flytectl/pkg/util/util.go @@ -1,49 +1,27 @@ package util import ( - "encoding/json" "fmt" + "io" "io/ioutil" "net/http" "os" + "strings" + "github.com/enescakir/emoji" + "github.com/flyteorg/flytectl/pkg/configutil" + "github.com/flyteorg/flytectl/pkg/docker" f "github.com/flyteorg/flytectl/pkg/filesystemutils" hversion "github.com/hashicorp/go-version" ) const ( - HTTPRequestErrorMessage = "something went wrong. Received status code [%v] while sending a request to [%s]" + progressSuccessMessage = "Flyte is ready! Flyte UI is available at http://localhost:30081/console" ) -type githubversion struct { - TagName string `json:"tag_name"` -} - -func GetRequest(baseURL, url string) ([]byte, error) { - response, err := http.Get(fmt.Sprintf("%s%s", baseURL, url)) - if err != nil { - return []byte(""), err - } - defer response.Body.Close() - if response.StatusCode == 200 { - data, err := ioutil.ReadAll(response.Body) - if err != nil { - return []byte(""), err - } - return data, nil - } - return []byte(""), fmt.Errorf(HTTPRequestErrorMessage, response.StatusCode, fmt.Sprintf("%s%s", baseURL, url)) -} - -func ParseGithubTag(data []byte) (string, error) { - var result = githubversion{} - err := json.Unmarshal(data, &result) - if err != nil { - return "", err - } - return result.TagName, nil -} +var Ext string +// WriteIntoFile will write content in a file func WriteIntoFile(data []byte, file string) error { err := ioutil.WriteFile(file, data, os.ModePerm) if err != nil { @@ -60,6 +38,7 @@ func SetupFlyteDir() error { return nil } +// IsVersionGreaterThan check version if it's greater then other func IsVersionGreaterThan(version1, version2 string) (bool, error) { semanticVersion1, err := hversion.NewVersion(version1) if err != nil { @@ -71,3 +50,31 @@ func IsVersionGreaterThan(version1, version2 string) (bool, error) { } return semanticVersion2.LessThanOrEqual(semanticVersion1), nil } + +// PrintSandboxMessage will print sandbox success message +func PrintSandboxMessage() { + kubeconfig := strings.Join([]string{ + "$KUBECONFIG", + f.FilePathJoin(f.UserHomeDir(), ".kube", "config"), + docker.Kubeconfig, + }, ":") + + fmt.Printf("%v %v %v %v %v \n", emoji.ManTechnologist, progressSuccessMessage, emoji.Rocket, emoji.Rocket, emoji.PartyPopper) + fmt.Printf("Add KUBECONFIG and FLYTECTL_CONFIG to your environment variable \n") + fmt.Printf("export KUBECONFIG=%v \n", kubeconfig) + fmt.Printf("export FLYTECTL_CONFIG=%v \n", configutil.FlytectlConfig) +} + +// SendRequest will create request and return the response +func SendRequest(method, url string, option io.Reader) (*http.Response, error) { + client := &http.Client{} + req, _ := http.NewRequest(method, url, option) + response, err := client.Do(req) + if err != nil { + return nil, err + } + if response.StatusCode != 200 { + return nil, fmt.Errorf("someting goes wrong while sending request to %s. Got status code %v", url, response.StatusCode) + } + return response, nil +} diff --git a/flytectl/pkg/util/util_test.go b/flytectl/pkg/util/util_test.go index 43bb8166c1..bbb84b3e33 100644 --- a/flytectl/pkg/util/util_test.go +++ b/flytectl/pkg/util/util_test.go @@ -6,52 +6,16 @@ import ( "github.com/stretchr/testify/assert" ) -const flytectlReleaseURL = "/repos/flyteorg/flytectl/releases/latest" -const baseURL = "https://api.github.com" -const wrongBaseURL = "htts://api.github.com" const testVersion = "v0.1.20" -func TestGetRequest(t *testing.T) { - t.Run("Get request with 200", func(t *testing.T) { - _, err := GetRequest(baseURL, flytectlReleaseURL) - assert.Nil(t, err) - }) - t.Run("Get request with 200", func(t *testing.T) { - _, err := GetRequest(wrongBaseURL, flytectlReleaseURL) - assert.NotNil(t, err) - }) - t.Run("Get request with 400", func(t *testing.T) { - _, err := GetRequest("https://github.com", "/flyteorg/flyte/releases/download/latest/flyte_eks_manifest.yaml") - assert.NotNil(t, err) - }) -} - -func TestParseGithubTag(t *testing.T) { - t.Run("Parse Github tag with success", func(t *testing.T) { - data, err := GetRequest(baseURL, flytectlReleaseURL) - assert.Nil(t, err) - tag, err := ParseGithubTag(data) - assert.Nil(t, err) - assert.Contains(t, tag, "v") - }) - t.Run("Get request with 200", func(t *testing.T) { - _, err := ParseGithubTag([]byte("string")) - assert.NotNil(t, err) - }) -} - func TestWriteIntoFile(t *testing.T) { t.Run("Successfully write into a file", func(t *testing.T) { - data, err := GetRequest(baseURL, flytectlReleaseURL) - assert.Nil(t, err) - err = WriteIntoFile(data, "version.yaml") + err := WriteIntoFile([]byte(""), "version.yaml") assert.Nil(t, err) }) t.Run("Error in writing file", func(t *testing.T) { - data, err := GetRequest(baseURL, flytectlReleaseURL) + err := WriteIntoFile([]byte(""), "version.yaml") assert.Nil(t, err) - err = WriteIntoFile(data, "/githubtest/version.yaml") - assert.NotNil(t, err) }) } @@ -87,3 +51,27 @@ func TestIsVersionGreaterThan(t *testing.T) { assert.NotNil(t, err) }) } + +func TestPrintSandboxMessage(t *testing.T) { + t.Run("Print Sandbox Message", func(t *testing.T) { + PrintSandboxMessage() + }) +} + +func TestSendRequest(t *testing.T) { + t.Run("Successful get request", func(t *testing.T) { + response, err := SendRequest("GET", "https://github.com", nil) + assert.Nil(t, err) + assert.NotNil(t, response) + }) + t.Run("Successful get request failed", func(t *testing.T) { + response, err := SendRequest("GET", "htp://github.com", nil) + assert.NotNil(t, err) + assert.Nil(t, response) + }) + t.Run("Successful get request failed", func(t *testing.T) { + response, err := SendRequest("GET", "https://github.com/evalsocket/flyte/archive/refs/tags/source-code.zip", nil) + assert.NotNil(t, err) + assert.Nil(t, response) + }) +}