Prechádzať zdrojové kódy

Merge pull request #376 from docker/logout

Implement azure logout
Guillaume Tardif 5 rokov pred
rodič
commit
52c2d09eae

+ 4 - 0
azure/backend.go

@@ -340,6 +340,10 @@ func (cs *aciCloudService) Login(ctx context.Context, params map[string]string)
 	return cs.loginService.Login(ctx, params[login.TenantIDLoginParam])
 }
 
+func (cs *aciCloudService) Logout(ctx context.Context) error {
+	return cs.loginService.Logout(ctx)
+}
+
 func (cs *aciCloudService) CreateContextData(ctx context.Context, params map[string]string) (interface{}, string, error) {
 	contextHelper := newContextCreateHelper()
 	return contextHelper.createContextData(ctx, params)

+ 12 - 9
azure/login/login.go

@@ -22,14 +22,13 @@ import (
 	"fmt"
 	"net/http"
 	"net/url"
-	"path/filepath"
+	"os"
 	"strconv"
 	"time"
 
 	"github.com/Azure/go-autorest/autorest"
 	"github.com/Azure/go-autorest/autorest/adal"
 	auth2 "github.com/Azure/go-autorest/autorest/azure/auth"
-	"github.com/Azure/go-autorest/autorest/azure/cli"
 	"github.com/Azure/go-autorest/autorest/date"
 	"github.com/pkg/errors"
 	"golang.org/x/oauth2"
@@ -80,7 +79,7 @@ const tokenStoreFilename = "dockerAccessToken.json"
 
 // NewAzureLoginService creates a NewAzureLoginService
 func NewAzureLoginService() (AzureLoginService, error) {
-	return newAzureLoginServiceFromPath(getTokenStorePath(), azureAPIHelper{})
+	return newAzureLoginServiceFromPath(GetTokenStorePath(), azureAPIHelper{})
 }
 
 func newAzureLoginServiceFromPath(tokenStorePath string, helper apiHelper) (AzureLoginService, error) {
@@ -120,6 +119,15 @@ func (login AzureLoginService) TestLoginFromServicePrincipal(clientID string, cl
 	return nil
 }
 
+// Logout remove azure token data
+func (login AzureLoginService) Logout(ctx context.Context) error {
+	err := login.tokenStore.removeData()
+	if os.IsNotExist(err) {
+		return errors.New("No Azure login data to be removed")
+	}
+	return err
+}
+
 // Login performs an Azure login through a web browser
 func (login AzureLoginService) Login(ctx context.Context, requestedTenantID string) error {
 	queryCh := make(chan localResponse, 1)
@@ -208,11 +216,6 @@ func getTenantID(tenantValues []tenantValue, requestedTenantID string) (string,
 	return "", errors.Errorf("could not find requested azure tenant %s", requestedTenantID)
 }
 
-func getTokenStorePath() string {
-	cliPath, _ := cli.AccessTokensPath()
-	return filepath.Join(filepath.Dir(cliPath), tokenStoreFilename)
-}
-
 func toOAuthToken(token azureToken) oauth2.Token {
 	expireTime := time.Now().Add(time.Duration(token.ExpiresIn) * time.Second)
 	oauthToken := oauth2.Token{
@@ -241,7 +244,7 @@ func spToOAuthToken(token adal.Token) (oauth2.Token, error) {
 
 // NewAuthorizerFromLogin creates an authorizer based on login access token
 func NewAuthorizerFromLogin() (autorest.Authorizer, error) {
-	return newAuthorizerFromLoginStorePath(getTokenStorePath())
+	return newAuthorizerFromLoginStorePath(GetTokenStorePath())
 }
 
 func newAuthorizerFromLoginStorePath(storeTokenPath string) (autorest.Authorizer, error) {

+ 12 - 0
azure/login/tokenStore.go

@@ -23,6 +23,8 @@ import (
 	"os"
 	"path/filepath"
 
+	"github.com/Azure/go-autorest/autorest/azure/cli"
+
 	"golang.org/x/oauth2"
 )
 
@@ -57,6 +59,12 @@ func newTokenStore(path string) (tokenStore, error) {
 	}, nil
 }
 
+// GetTokenStorePath the path for token store
+func GetTokenStorePath() string {
+	cliPath, _ := cli.AccessTokensPath()
+	return filepath.Join(filepath.Dir(cliPath), tokenStoreFilename)
+}
+
 func (store tokenStore) writeLoginInfo(info TokenInfo) error {
 	bytes, err := json.MarshalIndent(info, "", "  ")
 	if err != nil {
@@ -76,3 +84,7 @@ func (store tokenStore) readToken() (TokenInfo, error) {
 	}
 	return loginInfo, nil
 }
+
+func (store tokenStore) removeData() error {
+	return os.Remove(store.filePath)
+}

+ 1 - 1
cli/cmd/login/login.go

@@ -35,7 +35,7 @@ import (
 func Command() *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "login [OPTIONS] [SERVER]",
-		Short: "Log in to a Docker registry",
+		Short: "Log in to a Docker registry or cloud backend",
 		Long:  "Log in to a Docker registry or cloud backend.\nIf no registry server is specified, the default is defined by the daemon.",
 		Args:  cobra.MaximumNArgs(1),
 		RunE:  runLogin,

+ 42 - 0
cli/cmd/logout/azurelogout.go

@@ -0,0 +1,42 @@
+package logout
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+
+	"github.com/docker/api/client"
+	"github.com/docker/api/errdefs"
+)
+
+// AzureLogoutCommand returns the azure logout command
+func AzureLogoutCommand() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "azure",
+		Short: "Logout from Azure",
+		Args:  cobra.MaximumNArgs(0),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return cloudLogout(cmd, "aci")
+		},
+	}
+	return cmd
+}
+
+func cloudLogout(cmd *cobra.Command, backendType string) error {
+	ctx := cmd.Context()
+	cs, err := client.GetCloudService(ctx, backendType)
+	if err != nil {
+		return errors.Wrap(errdefs.ErrLoginFailed, "cannot connect to backend")
+	}
+	err = cs.Logout(ctx)
+	if errors.Is(err, context.Canceled) {
+		return errors.New("logout canceled")
+	}
+	if err != nil {
+		return err
+	}
+	fmt.Println("Removing login credentials for Azure")
+	return nil
+}

+ 25 - 0
cli/cmd/logout/logout.go

@@ -0,0 +1,25 @@
+package logout
+
+import (
+	"github.com/spf13/cobra"
+
+	"github.com/docker/api/cli/mobycli"
+)
+
+// Command returns the login command
+func Command() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "logout [SERVER]",
+		Short: "Log out from a Docker registry or cloud backend",
+		Long:  "Log out from a Docker registry or cloud backend.\nIf no server is specified, the default is defined by the daemon.",
+		Args:  cobra.MaximumNArgs(0),
+		RunE:  runLogout,
+	}
+
+	cmd.AddCommand(AzureLogoutCommand())
+	return cmd
+}
+
+func runLogout(cmd *cobra.Command, args []string) error {
+	return mobycli.ExecCmd(cmd)
+}

+ 4 - 0
cli/main.go

@@ -27,6 +27,8 @@ import (
 	"syscall"
 	"time"
 
+	"github.com/docker/api/cli/cmd/logout"
+
 	"github.com/docker/api/errdefs"
 
 	"github.com/pkg/errors"
@@ -59,6 +61,7 @@ var (
 	ownCommands = map[string]struct{}{
 		"context": {},
 		"login":   {},
+		"logout":  {},
 		"serve":   {},
 		"version": {},
 	}
@@ -117,6 +120,7 @@ func main() {
 		cmd.InspectCommand(),
 		compose.Command(),
 		login.Command(),
+		logout.Command(),
 		cmd.VersionCommand(version),
 	)
 

+ 8 - 1
context/cloud/api.go

@@ -26,7 +26,9 @@ import (
 type Service interface {
 	// Login login to cloud provider
 	Login(ctx context.Context, params map[string]string) error
-	// Login login to cloud provider
+	// Logout logout from cloud provider
+	Logout(ctx context.Context) error
+	// CreateContextData create data for cloud context
 	CreateContextData(ctx context.Context, params map[string]string) (contextData interface{}, description string, err error)
 }
 
@@ -38,6 +40,11 @@ func NotImplementedCloudService() (Service, error) {
 type notImplementedCloudService struct {
 }
 
+// Logout login to cloud provider
+func (cs notImplementedCloudService) Logout(ctx context.Context) error {
+	return errdefs.ErrNotImplemented
+}
+
 func (cs notImplementedCloudService) Login(ctx context.Context, params map[string]string) error {
 	return errdefs.ErrNotImplemented
 }

+ 22 - 0
tests/aci-e2e/e2e-aci_test.go

@@ -22,10 +22,13 @@ import (
 	"math/rand"
 	"net/url"
 	"os"
+	"os/exec"
 	"strings"
 	"testing"
 	"time"
 
+	"github.com/docker/api/errdefs"
+
 	"github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/resources/mgmt/resources"
 	azure_storage "github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/storage/mgmt/storage"
 	"github.com/Azure/azure-storage-file-go/azfile"
@@ -58,6 +61,25 @@ type E2eACISuite struct {
 	Suite
 }
 
+func (s *E2eACISuite) TestLoginLogoutCreateContextError() {
+	s.Step("Logs in azure using service principal credentials", azureLogin)
+
+	s.Step("logout from azure", func() {
+		output := s.NewDockerCommand("logout", "azure").ExecOrDie()
+		Expect(output).To(ContainSubstring(""))
+		_, err := os.Stat(login.GetTokenStorePath())
+		Expect(os.IsNotExist(err)).To(BeTrue())
+	})
+
+	s.Step("check context create fails with an explicit error and returns a specific error code", func() {
+		cmd := exec.Command("docker", "context", "create", "aci", "someContext")
+		bytes, err := cmd.CombinedOutput()
+		Expect(err).NotTo(BeNil())
+		Expect(string(bytes)).To(ContainSubstring("not logged in to azure, you need to run \"docker login azure\" first"))
+		Expect(cmd.ProcessState.ExitCode()).To(Equal(errdefs.ExitCodeLoginRequired))
+	})
+}
+
 func (s *E2eACISuite) TestACIRunSingleContainer() {
 	resourceGroupName := s.setupTestResourceGroup()
 	defer deleteResourceGroup(resourceGroupName)

+ 0 - 12
tests/e2e/e2e_test.go

@@ -18,17 +18,13 @@ package main
 
 import (
 	"os"
-	"os/exec"
 	"path/filepath"
 	"runtime"
 	"testing"
 	"time"
 
-	"github.com/docker/api/errdefs"
-
 	. "github.com/onsi/gomega"
 	"github.com/stretchr/testify/suite"
-
 	"gotest.tools/golden"
 
 	. "github.com/docker/api/tests/framework"
@@ -46,14 +42,6 @@ func (s *E2eSuite) TestContextHelp() {
 	Expect(output).To(ContainSubstring("--resource-group"))
 }
 
-func (s *E2eSuite) TestContextCreateAciExitWithErrorCodeIfLoginRequired() {
-	cmd := exec.Command("docker", "context", "create", "aci", "someContext")
-	output, err := cmd.CombinedOutput()
-	Expect(err).NotTo(BeNil())
-	Expect(string(output)).To(ContainSubstring("not logged in to azure, you need to run \"docker login azure\" first"))
-	Expect(cmd.ProcessState.ExitCode()).To(Equal(errdefs.ExitCodeLoginRequired))
-}
-
 func (s *E2eSuite) TestListAndShowDefaultContext() {
 	output := s.NewDockerCommand("context", "show").ExecOrDie()
 	Expect(output).To(ContainSubstring("default"))