diff --git a/cmd/ocm-backplane/login/login.go b/cmd/ocm-backplane/login/login.go index 97d83081..4e265774 100644 --- a/cmd/ocm-backplane/login/login.go +++ b/cmd/ocm-backplane/login/login.go @@ -52,6 +52,7 @@ var ( remediation string govcloud bool readonly bool + readwrite bool } // loginType derive the login type based on flags and args @@ -138,7 +139,14 @@ func init() { &args.readonly, "readonly", false, - "Login with read-only access to the cluster", + "(Deprecated) Login with read-only access to the cluster (this is now the default behavior)", + ) + _ = flags.MarkDeprecated("readonly", "read-only is now the default behavior, use --rw to login with write access") + flags.BoolVar( + &args.readwrite, + "rw", + false, + "Login with read-write access to the cluster", ) } @@ -340,13 +348,17 @@ func runLogin(cmd *cobra.Command, argv []string) (err error) { return fmt.Errorf("cluster %s is hibernating, login failed", clusterKey) } + // Determine if login should be readonly (default) or readwrite + // Default is readonly unless --rw is explicitly set + isReadOnly := !args.readwrite + logger.WithFields(logger.Fields{ "bpURL": bpURL, "clusterID": clusterID, - "readonly": args.readonly, + "readonly": isReadOnly, }).Debugln("Query backplane-api for proxy url of our target cluster") // Query backplane-api for proxy url - bpAPIClusterURL, err := doLoginWithConn(bpURL, clusterID, *accessToken, nil, args.readonly) + bpAPIClusterURL, err := doLoginWithConn(bpURL, clusterID, *accessToken, nil, isReadOnly) if err != nil { // Declare helperMsg helperMsg := "\n\033[1mNOTE: To troubleshoot the connectivity issues, please run `ocm-backplane health-check`\033[0m\n\n" diff --git a/cmd/ocm-backplane/login/login_test.go b/cmd/ocm-backplane/login/login_test.go index 64ad2f99..aabe7b26 100644 --- a/cmd/ocm-backplane/login/login_test.go +++ b/cmd/ocm-backplane/login/login_test.go @@ -120,6 +120,7 @@ var _ = Describe("Login command", func() { globalOpts.Service = false globalOpts.BackplaneURL = "" globalOpts.ProxyURL = "" + args.readwrite = false _ = os.Setenv("HTTPS_PROXY", "") _ = os.Unsetenv("BACKPLANE_CONFIG") _ = os.Remove(bpConfigPath) @@ -150,7 +151,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().IsClusterHibernating(gomock.Eq(trueClusterID)).Return(false, nil).AnyTimes() mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil) mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil) - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID)).Return(fakeResp, nil) + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID), gomock.Any()).Return(fakeResp, nil) err = runLogin(nil, []string{testClusterID}) @@ -174,7 +175,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().IsClusterHibernating(gomock.Eq(trueClusterID)).Return(false, nil).AnyTimes() mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil) mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken("https://sadge.app", testToken).Return(mockClient, nil) - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID)).Return(fakeResp, nil) + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID), gomock.Any()).Return(fakeResp, nil) err = runLogin(nil, []string{testClusterID}) @@ -199,7 +200,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().IsClusterHibernating(gomock.Eq(trueClusterID)).Return(false, nil).AnyTimes() mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil) mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil) - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID)).Return(fakeResp, nil) + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID), gomock.Any()).Return(fakeResp, nil) err = runLogin(nil, []string{testClusterID}) @@ -256,7 +257,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().IsClusterHibernating(gomock.Eq(trueClusterID)).Return(false, nil).AnyTimes() mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil) mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil) - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID)).Return(fakeResp, nil) + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID), gomock.Any()).Return(fakeResp, nil) mockOcmInterface.EXPECT().GetClusterInfoByID(gomock.Any()).Return(mockCluster, nil).Times(2) mockOcmInterface.EXPECT().SetupOCMConnection().Return(nil, nil) mockOcmInterface.EXPECT().IsClusterAccessProtectionEnabled(gomock.Any(), trueClusterID).Return(false, nil) @@ -277,7 +278,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().IsClusterHibernating(gomock.Eq(trueClusterID)).Return(false, nil).AnyTimes() mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil).AnyTimes() mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil).AnyTimes() - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID)).Return(fakeResp, nil).AnyTimes() + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID), gomock.Any()).Return(fakeResp, nil).AnyTimes() // Mock PrintClusterInfo to return an error mockOcmInterface.EXPECT().GetClusterInfoByID(gomock.Any()).Return(nil, errors.New("mock error")) @@ -295,7 +296,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().IsClusterHibernating(gomock.Eq(trueClusterID)).Return(false, nil).AnyTimes() mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil) mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil) - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID)).Return(fakeResp, nil) + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID), gomock.Any()).Return(fakeResp, nil) err = runLogin(nil, []string{testClusterID}) @@ -318,7 +319,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().IsClusterHibernating(gomock.Eq(trueClusterID)).Return(false, nil).AnyTimes() mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil) mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil) - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID)).Return(fakeResp, nil) + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID), gomock.Any()).Return(fakeResp, nil) err = runLogin(nil, []string{testClusterID}) @@ -353,7 +354,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().IsClusterHibernating(gomock.Eq(managingClusterID)).Return(false, nil).AnyTimes() mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil) mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil) - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(managingClusterID)).Return(fakeResp, nil) + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(managingClusterID), gomock.Any()).Return(fakeResp, nil) err := runLogin(nil, []string{testClusterID}) @@ -373,7 +374,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().IsClusterHibernating(gomock.Eq(managingClusterID)).Return(false, nil).AnyTimes() mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil).AnyTimes() mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil).AnyTimes() - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(managingClusterID)).Return(fakeResp, nil).AnyTimes() + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(managingClusterID), gomock.Any()).Return(fakeResp, nil).AnyTimes() err := runLogin(nil, []string{testClusterID}) @@ -391,7 +392,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().IsClusterHibernating(gomock.Eq(serviceClusterID)).Return(false, nil).AnyTimes() mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil) mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil) - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(serviceClusterID)).Return(fakeResp, nil) + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(serviceClusterID), gomock.Any()).Return(fakeResp, nil) err := runLogin(nil, []string{testClusterID}) @@ -409,7 +410,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().IsClusterHibernating(gomock.Eq(testClusterID)).Return(false, nil).AnyTimes() mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil) mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil) - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(testClusterID)).Return(fakeResp, nil) + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(testClusterID), gomock.Any()).Return(fakeResp, nil) err = runLogin(nil, nil) @@ -431,7 +432,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().IsClusterHibernating(gomock.Eq(trueClusterID)).Return(false, nil).AnyTimes() mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil) mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil) - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID)).Return(fakeResp, errors.New("dial tcp: lookup yourproxy.com: no such host")) + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID), gomock.Any()).Return(fakeResp, errors.New("dial tcp: lookup yourproxy.com: no such host")) err = runLogin(nil, []string{testClusterID}) @@ -459,7 +460,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().IsClusterHibernating(gomock.Eq(trueClusterID)).Return(false, nil).AnyTimes() mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil) mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil) - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID)).Return(fakeResp, nil) + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(trueClusterID), gomock.Any()).Return(fakeResp, nil) err = runLogin(nil, []string{testClusterID}) @@ -598,7 +599,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().GetClusterInfoByID(testClusterID).Return(mockCluster, nil) mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil) mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil) - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(testClusterID)).Return(fakeResp, nil) + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(testClusterID), gomock.Any()).Return(fakeResp, nil) username := "test-user" @@ -613,7 +614,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().GetClusterInfoByID(testClusterID).Return(mockCluster, nil) mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil) mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil) - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(testClusterID)).Return(fakeResp, nil) + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(testClusterID), gomock.Any()).Return(fakeResp, nil) username := "test-user" elevationReasons := []string{"reason1", "reason2"} @@ -660,7 +661,7 @@ var _ = Describe("Login command", func() { mockOcmInterface.EXPECT().IsClusterHibernating(gomock.Eq(testClusterID)).Return(false, nil).AnyTimes() mockOcmInterface.EXPECT().GetOCMAccessToken().Return(&testToken, nil) mockClientUtil.EXPECT().MakeRawBackplaneAPIClientWithAccessToken(backplaneAPIURI, testToken).Return(mockClient, nil) - mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(testClusterID)).Return(fakeResp, nil) + mockClient.EXPECT().LoginCluster(gomock.Any(), gomock.Eq(testClusterID), gomock.Any()).Return(fakeResp, nil) err = runLogin(nil, nil) @@ -692,10 +693,10 @@ var _ = Describe("Login command", func() { Expect(err).To(BeNil()) }) - It("should add readonly=true query parameter when readonly flag is set", func() { + It("should add readonly=true query parameter by default (when --rw flag is not set)", func() { // Setup args.multiCluster = false - args.readonly = true + args.readwrite = false loginType = LoginTypeClusterID mockOcmInterface.EXPECT().GetOCMEnvironment().Return(ocmEnv, nil).AnyTimes() @@ -728,10 +729,10 @@ var _ = Describe("Login command", func() { Expect(capturedURL).To(ContainSubstring("readonly=true")) }) - It("should not add readonly query parameter when readonly flag is false", func() { + It("should not add readonly query parameter when --rw flag is set", func() { // Setup args.multiCluster = false - args.readonly = false + args.readwrite = true loginType = LoginTypeClusterID mockOcmInterface.EXPECT().GetOCMEnvironment().Return(ocmEnv, nil).AnyTimes() diff --git a/cmd/ocm-backplane/root.go b/cmd/ocm-backplane/root.go index af677eec..ef96184e 100644 --- a/cmd/ocm-backplane/root.go +++ b/cmd/ocm-backplane/root.go @@ -34,6 +34,7 @@ import ( "github.com/openshift/backplane-cli/cmd/ocm-backplane/mcp" "github.com/openshift/backplane-cli/cmd/ocm-backplane/monitoring" "github.com/openshift/backplane-cli/cmd/ocm-backplane/remediation" + "github.com/openshift/backplane-cli/cmd/ocm-backplane/rw" "github.com/openshift/backplane-cli/cmd/ocm-backplane/script" "github.com/openshift/backplane-cli/cmd/ocm-backplane/session" "github.com/openshift/backplane-cli/cmd/ocm-backplane/status" @@ -86,4 +87,5 @@ func init() { rootCmd.AddCommand(monitoring.MonitoringCmd) rootCmd.AddCommand(healthcheck.HealthCheckCmd) rootCmd.AddCommand(remediation.NewRemediationCmd()) + rootCmd.AddCommand(rw.RwCmd) } diff --git a/cmd/ocm-backplane/rw/rw.go b/cmd/ocm-backplane/rw/rw.go new file mode 100644 index 00000000..c928223d --- /dev/null +++ b/cmd/ocm-backplane/rw/rw.go @@ -0,0 +1,59 @@ +package rw + +import ( + "fmt" + + logger "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/openshift/backplane-cli/cmd/ocm-backplane/login" + "github.com/openshift/backplane-cli/pkg/utils" +) + +// RwCmd represents the rw command +var RwCmd = &cobra.Command{ + Use: "rw", + Short: "Re-login to the current cluster with read-write access", + Long: `Re-login to the current cluster with read-write access. + +This command is a helper that detects the currently logged-in cluster from your +kubeconfig and re-authenticates with read-write permissions. This is equivalent +to running 'ocm-backplane login --rw ' but without needing to specify +the cluster ID again.`, + Example: "ocm backplane rw", + RunE: runRw, + SilenceUsage: true, +} + +func runRw(cmd *cobra.Command, argv []string) error { + logger.Debugln("Getting current cluster from kubeconfig") + + // Get the current cluster from kubeconfig + clusterInfo, err := utils.DefaultClusterUtils.GetBackplaneClusterFromConfig() + if err != nil { + return fmt.Errorf("failed to get current cluster from kubeconfig: %w\nPlease make sure you are logged in to a cluster first", err) + } + + logger.WithField("ClusterID", clusterInfo.ClusterID).Infoln("Re-logging in with read-write access") + + // Set the --rw flag for login + err = login.LoginCmd.Flags().Set("rw", "true") + if err != nil { + return fmt.Errorf("failed to set rw flag: %w", err) + } + + // Ensure we reset the flag after execution to avoid side effects + defer func() { + _ = login.LoginCmd.Flags().Set("rw", "false") + }() + + // Execute login command with the current cluster ID + err = login.LoginCmd.RunE(cmd, []string{clusterInfo.ClusterID}) + if err != nil { + return fmt.Errorf("failed to re-login with read-write access: %w", err) + } + + fmt.Printf("Successfully re-logged in to cluster %s with read-write access\n", clusterInfo.ClusterID) + + return nil +}