From fb6c247ecd84ecec11af0cb3687549c04f09c3c4 Mon Sep 17 00:00:00 2001 From: Dan Finneran Date: Fri, 10 Aug 2018 17:33:45 +0100 Subject: [PATCH 1/2] Add login context --- cmd/ucp.go | 35 +----------- cmd/ucp_login.go | 92 +++++++++++++++++++++++++++++++ pkg/ucp/ucpClient.go | 126 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 204 insertions(+), 49 deletions(-) create mode 100644 cmd/ucp_login.go diff --git a/cmd/ucp.go b/cmd/ucp.go index c7fec63..3b8288e 100644 --- a/cmd/ucp.go +++ b/cmd/ucp.go @@ -2,8 +2,6 @@ package cmd import ( "fmt" - "os" - "strings" "github.com/spf13/cobra" "github.com/thebsdbox/diver/pkg/ucp" @@ -16,14 +14,6 @@ var importPath, exportPath, action string var top, exampleFile bool func init() { - ucpLogin.Flags().StringVar(&ucpClient.Username, "username", os.Getenv("UCP_USERNAME"), "Username that has permissions to authenticate to Docker EE") - ucpLogin.Flags().StringVar(&ucpClient.Password, "password", os.Getenv("UCP_PASSWORD"), "Password allowing a user to authenticate to Docker EE") - ucpLogin.Flags().StringVar(&ucpClient.UCPURL, "url", os.Getenv("UCP_URL"), "URL for Docker Universal Control Plane, e.g. https://10.0.0.1") - ignoreCert := strings.ToLower(os.Getenv("UCP_INSECURE")) == "true" - - ucpLogin.Flags().BoolVar(&ucpClient.IgnoreCert, "ignorecert", ignoreCert, "Ignore x509 certificate") - - UCPRoot.AddCommand(ucpLogin) // Add UCP and subcommands to the main application diverCmd.AddCommand(UCPRoot) @@ -40,7 +30,7 @@ var UCPRoot = &cobra.Command{ if err != nil { // Fatal error if can't read the token cmd.Help() - log.Warn("Unable to find existing session, please login") + log.Errorf("%v", err) return } currentAccount, err := existingClient.AuthStatus() @@ -55,26 +45,3 @@ var UCPRoot = &cobra.Command{ return }, } - -// UCPRoot - This is the root of all UCP commands / flags -var ucpLogin = &cobra.Command{ - Use: "login", - Short: "Authenticate against the Universal Control Pane", - Run: func(cmd *cobra.Command, args []string) { - log.SetLevel(log.Level(logLevel)) - - err := ucpClient.Connect() - - // Check if connection was succesful - if err != nil { - log.Fatalf("%v", err) - } else { - // If succesfull write the token and annouce as succesful - err = ucpClient.WriteToken() - if err != nil { - log.Errorf("%v", err) - } - log.Infof("Succesfully logged into [%s]", ucpClient.UCPURL) - } - }, -} diff --git a/cmd/ucp_login.go b/cmd/ucp_login.go new file mode 100644 index 0000000..2ad2c97 --- /dev/null +++ b/cmd/ucp_login.go @@ -0,0 +1,92 @@ +package cmd + +import ( + "fmt" + "os" + "strings" + "text/tabwriter" + + log "github.com/Sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/thebsdbox/diver/pkg/ucp" +) + +var session int + +func init() { + diverCmd.AddCommand(UCPRoot) + + ucpLogin.Flags().StringVar(&ucpClient.Username, "username", os.Getenv("UCP_USERNAME"), "Username that has permissions to authenticate to Docker EE") + ucpLogin.Flags().StringVar(&ucpClient.Password, "password", os.Getenv("UCP_PASSWORD"), "Password allowing a user to authenticate to Docker EE") + ucpLogin.Flags().StringVar(&ucpClient.UCPURL, "url", os.Getenv("UCP_URL"), "URL for Docker EE, e.g. https://10.0.0.1") + ignoreCert := strings.ToLower(os.Getenv("UCP_INSECURE")) == "true" + + ucpLogin.Flags().BoolVar(&ucpClient.IgnoreCert, "ignorecert", ignoreCert, "Ignore x509 certificate") + + ucpLoginSet.Flags().IntVar(&session, "id", 0, "The session ID to set to active") + + ucpLogin.AddCommand(ucpLoginList) + ucpLogin.AddCommand(ucpLoginSet) + + UCPRoot.AddCommand(ucpLogin) +} + +// UCPLogin - This manages logging in and swapping of contexts +var ucpLogin = &cobra.Command{ + Use: "login", + Short: "Authenticate against the Universal Control Pane", + Run: func(cmd *cobra.Command, args []string) { + log.SetLevel(log.Level(logLevel)) + + err := ucpClient.Connect() + + // Check if connection was succesful + if err != nil { + cmd.Help() + log.Fatalf("%v", err) + } else { + // If succesfull write the token and annouce as succesful + err = ucpClient.WriteToken() + if err != nil { + log.Errorf("%v", err) + } + log.Infof("Succesfully logged into [%s]", ucpClient.UCPURL) + } + }, +} + +// ucpLoginList - This manages logging in and swapping of contexts +var ucpLoginList = &cobra.Command{ + Use: "list", + Short: "List all UCP sessions", + Run: func(cmd *cobra.Command, args []string) { + log.SetLevel(log.Level(logLevel)) + // Retrieve all of the client sessions in the token file + clientTokens, err := ucp.ReadAllClients() + if err != nil { + log.Errorf("%v", err) + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, tabPadding, ' ', 0) + fmt.Fprintln(w, "ID\tAddress\tActive") + for i := range clientTokens { + fmt.Fprintf(w, "%d\t%s\t%t\n", i, clientTokens[i].UCPAddress, clientTokens[i].Active) + } + w.Flush() + }, +} + +// ucpLoginSet - This manages logging in and swapping of contexts +var ucpLoginSet = &cobra.Command{ + Use: "setActive", + Short: "Set the active UCP session", + Run: func(cmd *cobra.Command, args []string) { + log.SetLevel(log.Level(logLevel)) + + err := ucp.SetActiveSession(session) + if err != nil { + log.Fatalf("%v", err) + } + log.Infof("Set session [%d] to active", session) + }, +} diff --git a/pkg/ucp/ucpClient.go b/pkg/ucp/ucpClient.go index 3d4bbf6..2102207 100644 --- a/pkg/ucp/ucpClient.go +++ b/pkg/ucp/ucpClient.go @@ -62,7 +62,7 @@ func (c *Client) Connect() error { if err != nil { return err } - log.Debugf("%v", string(response)) + log.Debugf("%s", response) var responseData map[string]interface{} err = json.Unmarshal(response, &responseData) @@ -330,10 +330,12 @@ func (c *Client) doRequest(req *http.Request) ([]byte, error) { } } -type internal struct { +//ClientSession - returns everything needed to interact with UCP +type ClientSession struct { UCPAddress string `json:"address"` Token string `json:"token"` IgnoreCert bool `json:"ignoreCert"` + Active bool `json:"active"` } // WriteToken - Writes a copy of the token to the @@ -343,17 +345,48 @@ func (c *Client) WriteToken() error { return fmt.Errorf("Not logged in, or no UCP token present") } + // Retrieve existing tokens, to update an existing or append a new session + + clientTokens, err := ReadAllClients() + if err != nil { + // An error here could be related to no existing login or corrupted file + log.Debugf("%v", err) + } + // build path path := fmt.Sprintf("%s/.ucptoken", os.Getenv("HOME")) log.Debugf("Writing Token to [%s]", path) - clientToken := internal{ + clientToken := ClientSession{ UCPAddress: c.UCPURL, Token: c.Token, IgnoreCert: c.IgnoreCert, + Active: true, + } + + var found bool + + for i := range clientTokens { + // Ensure all tokens are disabled + clientTokens[i].Active = false + if clientTokens[i].UCPAddress == c.UCPURL { + // If the session already exists update the token + clientTokens[i].Token = c.Token + // Enable this one as it has been updated + clientTokens[i].Active = true + + found = true + log.Infoln("Updating existing session") + } } - b, err := json.Marshal(clientToken) + if found != true { + clientTokens = append(clientTokens, clientToken) + log.Infoln("Adding new session") + + } + + b, err := json.Marshal(clientTokens) if err != nil { return err } @@ -364,9 +397,10 @@ func (c *Client) WriteToken() error { return nil } -// ReadToken - Reads the token from a file -func ReadToken() (*Client, error) { - // build path +// ReadAllClients - This will read the token file and return all of the clients +func ReadAllClients() ([]ClientSession, error) { + + var clientArray []ClientSession path := fmt.Sprintf("%s/.ucptoken", os.Getenv("HOME")) log.Debugf("Reading Token from [%s]", path) data, err := ioutil.ReadFile(path) @@ -374,18 +408,80 @@ func ReadToken() (*Client, error) { return nil, fmt.Errorf("No Session Token could be found, please login") } - clientToken := internal{} - - err = json.Unmarshal(data, &clientToken) + err = json.Unmarshal(data, &clientArray) if err != nil { return nil, fmt.Errorf("Corrupted Session Token, please login") } - client := &Client{ - UCPURL: clientToken.UCPAddress, - Token: clientToken.Token, - IgnoreCert: clientToken.IgnoreCert, + return clientArray, nil +} + +// ReadToken - Reads the token from a file +func ReadToken() (*Client, error) { + + // Retrieve all of the client sessions in the token file + clientTokens, err := ReadAllClients() + if err != nil { + return nil, err + } + + if len(clientTokens) == 0 { + return nil, fmt.Errorf("No sessions found, please login") + } + + // Find the active session and return it + for i := range clientTokens { + if clientTokens[i].Active == true { + client := &Client{ + UCPURL: clientTokens[i].UCPAddress, + Token: clientTokens[i].Token, + IgnoreCert: clientTokens[i].IgnoreCert, + } + return client, nil + + } + } + + return nil, fmt.Errorf("No active sessions found, please login") + +} + +// SetActiveSession - this will set the active session +func SetActiveSession(sessionID int) error { + // Retrieve existing tokens, to update an existing or append a new session + + clientTokens, err := ReadAllClients() + if err != nil { + // An error here could be related to no existing login or corrupted file + log.Debugf("%v", err) + } + + // Arrays begin at 0 + if (len(clientTokens) - 1) < sessionID { + return fmt.Errorf("Session ID [%d] doesn't exist", sessionID) + } + + // build path + path := fmt.Sprintf("%s/.ucptoken", os.Getenv("HOME")) + log.Debugf("Writing Token to [%s]", path) + + for i := range clientTokens { + // Ensure all tokens are disabled + clientTokens[i].Active = false + if i == sessionID { + // Enable this one as it has been updated + clientTokens[i].Active = true + } + } + + b, err := json.Marshal(clientTokens) + if err != nil { + return err } + err = ioutil.WriteFile(path, b, 0644) + if err != nil { + return err + } + return nil - return client, nil } From 46ffb9d844d00ce86c3b74778aded3f54301d9fb Mon Sep 17 00:00:00 2001 From: Dan Finneran Date: Fri, 10 Aug 2018 17:36:55 +0100 Subject: [PATCH 2/2] Updated the documentation for multiple sessions --- docs/ucp/README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/ucp/README.md b/docs/ucp/README.md index 02bcc67..4a2fc24 100644 --- a/docs/ucp/README.md +++ b/docs/ucp/README.md @@ -12,6 +12,28 @@ INFO[0000] Succesfully logged into [https://docker01.fnnrn.me] **Note** the `login` command will create a `~/.ucptoken` file that is used for all further `diver` commands. In the event that login errors start to occur, or logins fail check the permissions of this file or alternatively remove this file. +### Working with multiple UCP sessions + +When you log into a new UCP server, then a new session will be created. To view the sessions that are available use the following command: + +``` +$ ./diver ucp login list +ID Address Active +0 https://192.168.0.140 true +1 https://docker01.fnnrn.me false +``` + +To set a different session to active, you can use the setActive command to change the active session. + +``` +./diver ucp login setActive --id 1 +INFO[0000] Set session [1] to active +./diver ucp login list +ID Address Active +0 https://192.168.0.140 false +1 https://docker01.fnnrn.me true +``` + ### Checking access