From 5e8c5a565b81e7a2cc8b95c8794e28f2e7317823 Mon Sep 17 00:00:00 2001 From: cgoodwin90 Date: Thu, 16 Nov 2023 13:46:39 +1100 Subject: [PATCH 1/4] Fixes & updates user-sshkey --- cmd/users.go | 95 +++++++++++++++++++++++++++++++++++++++------------- go.mod | 4 +-- go.sum | 3 ++ 3 files changed, 77 insertions(+), 25 deletions(-) diff --git a/cmd/users.go b/cmd/users.go index 1178213b..cd21cf3c 100644 --- a/cmd/users.go +++ b/cmd/users.go @@ -1,11 +1,15 @@ package cmd import ( + "context" "encoding/json" "errors" "fmt" + l "github.com/uselagoon/machinery/api/lagoon" + lclient "github.com/uselagoon/machinery/api/lagoon/client" "io/ioutil" "os" + "strconv" "strings" "github.com/spf13/cobra" @@ -147,22 +151,36 @@ var deleteSSHKeyCmd = &cobra.Command{ Use: "user-sshkey", Aliases: []string{"u"}, Short: "Delete an SSH key from Lagoon", - Run: func(cmd *cobra.Command, args []string) { - if sshKeyName == "" { - fmt.Println("Missing arguments: SSH key name is not defined") - cmd.Help() - os.Exit(1) + PreRunE: func(_ *cobra.Command, _ []string) error { + return validateTokenE(cmdLagoon) + }, + RunE: func(cmd *cobra.Command, args []string) error { + debug, err := cmd.Flags().GetBool("debug") + sshKeyID, err := cmd.Flags().GetUint("id") + if err != nil { + return err } - var customReqResult []byte - var err error - if yesNo(fmt.Sprintf("You are attempting to delete SSH key named '%s', are you sure?", sshKeyName)) { - customReqResult, err = uClient.DeleteSSHKey(sshKeyName) + if sshKeyID == 0 { + fmt.Println("Missing arguments: SSH key ID is not defined") + return nil + } + current := lagoonCLIConfig.Current + token := lagoonCLIConfig.Lagoons[current].Token + lc := lclient.New( + lagoonCLIConfig.Lagoons[current].GraphQL, + lagoonCLIVersion, + &token, + debug) + + if yesNo(fmt.Sprintf("You are attempting to delete SSH key ID:'%d', are you sure?", sshKeyID)) { + _, err := l.RemoveSSHKey(context.TODO(), sshKeyID, lc) handleError(err) resultData := output.Result{ - Result: string(customReqResult), + Result: "success", } output.RenderResult(resultData, outputOptions) } + return nil }, } @@ -226,23 +244,55 @@ var getUserKeysCmd = &cobra.Command{ Aliases: []string{"us"}, Short: "Get a user's SSH keys", Long: `Get a user's SSH keys. This will only work for users that are part of a group`, - Run: func(cmd *cobra.Command, args []string) { + PreRunE: func(_ *cobra.Command, _ []string) error { + return validateTokenE(cmdLagoon) + }, + RunE: func(cmd *cobra.Command, args []string) error { + debug, err := cmd.Flags().GetBool("debug") + if err != nil { + return err + } + userEmail, err := cmd.Flags().GetString("email") + if err != nil { + return err + } if userEmail == "" { fmt.Println("Missing arguments: Email address is not defined") - cmd.Help() - os.Exit(1) + return nil } - returnedJSON, err := uClient.ListUserSSHKeys(groupName, strings.ToLower(userEmail), false) - handleError(err) - var dataMain output.Table - err = json.Unmarshal([]byte(returnedJSON), &dataMain) + + current := lagoonCLIConfig.Current + token := lagoonCLIConfig.Lagoons[current].Token + lc := lclient.New( + lagoonCLIConfig.Lagoons[current].GraphQL, + lagoonCLIVersion, + &token, + debug) + userKeys, err := l.GetUserSSHKeysByEmail(context.TODO(), userEmail, lc) handleError(err) - if len(dataMain.Data) == 0 { + if len(userKeys.SSHKeys) == 0 { output.RenderInfo(fmt.Sprintf("No SSH keys for user '%s'", strings.ToLower(userEmail)), outputOptions) - os.Exit(0) + return nil + } + + data := []output.Data{} + for _, userkey := range userKeys.SSHKeys { + data = append(data, []string{ + strconv.Itoa(int(userkey.ID)), + userKeys.Email, + userkey.Name, + string(userkey.KeyType), + userkey.KeyValue, + }) } - output.RenderOutput(dataMain, outputOptions) + dataMain := output.Table{ + Header: []string{"ID", "Email", "Name", "Type", "Value"}, + Data: data, + } + + output.RenderOutput(dataMain, outputOptions) + return nil }, } @@ -281,12 +331,11 @@ func init() { addUserSSHKeyCmd.Flags().StringVarP(&pubKeyFile, "pubkey", "K", "", "Specify path to the public key to add") addUserSSHKeyCmd.Flags().StringVarP(&pubKeyValue, "keyvalue", "V", "", "Value of the public key to add (ssh-ed25519 AAA..)") deleteUserCmd.Flags().StringVarP(&userEmail, "email", "E", "", "Email address of the user") - deleteSSHKeyCmd.Flags().StringVarP(&sshKeyName, "keyname", "N", "", "Name of the SSH key") + deleteSSHKeyCmd.Flags().Uint("id", 0, "ID of the SSH key") updateUserCmd.Flags().StringVarP(&userFirstName, "firstName", "F", "", "New first name of the user") updateUserCmd.Flags().StringVarP(&userLastName, "lastName", "L", "", "New last name of the user") updateUserCmd.Flags().StringVarP(&userEmail, "email", "E", "", "New email address of the user") updateUserCmd.Flags().StringVarP(¤tUserEmail, "current-email", "C", "", "Current email address of the user") - getUserKeysCmd.Flags().StringVarP(&userEmail, "email", "E", "", "New email address of the user") - getUserKeysCmd.Flags().StringVarP(&groupName, "name", "N", "", "Name of the group to check users in (if not specified, will default to all groups)") + getUserKeysCmd.Flags().StringP("email", "E", "", "New email address of the user") getAllUserKeysCmd.Flags().StringVarP(&groupName, "name", "N", "", "Name of the group to list users in (if not specified, will default to all groups)") } diff --git a/go.mod b/go.mod index 4ec1069e..ec8cfc7e 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/guregu/null v4.0.0+incompatible // workaround for https://github.com/manifoldco/promptui/issues/98 github.com/nicksnyder/go-i18n v1.10.1 // indirect - github.com/uselagoon/machinery v0.0.11 + github.com/uselagoon/machinery v0.0.13-0.20231116024123-c712ade42522 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 // indirect golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect @@ -41,4 +41,4 @@ replace github.com/olekukonko/tablewriter => github.com/shreddedbacon/tablewrite // replace github.com/machinebox/graphql => ../../shreddedbacon/graphql -// replace github.com/olekukonko/tablewriter => ../../shreddedbacon/tablewriter \ No newline at end of file +// replace github.com/olekukonko/tablewriter => ../../shreddedbacon/tablewriter diff --git a/go.sum b/go.sum index fe18d60a..050d8cda 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE= github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3 h1:I4BOK3PBMjhWfQM2zPJKK7lOBGsrsvOB7kBELP33hiE= github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= @@ -107,6 +108,8 @@ github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYv github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/uselagoon/machinery v0.0.11 h1:s6EhyU/pj1+C4FdS0EqmR6C0dLsoeCd9n+5xHL1YDag= github.com/uselagoon/machinery v0.0.11/go.mod h1:IXLxlkahEAEgpCmu9Xa/Wmjo6ja4Aoq7tf8G7VrileE= +github.com/uselagoon/machinery v0.0.13-0.20231116024123-c712ade42522 h1:6YU7GMpDL+3xwIbdtTwIYEOWJFbrKd20BeOXtAFEkyg= +github.com/uselagoon/machinery v0.0.13-0.20231116024123-c712ade42522/go.mod h1:h/qeMWQR4Qqu33x+8AulNDeolEwvb/G+aIsn/jyUtwk= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= From 7bb404c2a1f0bf3b6f4aa25bb6ab53093c81e33d Mon Sep 17 00:00:00 2001 From: cgoodwin90 Date: Mon, 20 Nov 2023 10:45:28 +1100 Subject: [PATCH 2/4] Removed unused function --- pkg/lagoon/users/main.go | 1 - pkg/lagoon/users/users.go | 20 +------------------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/pkg/lagoon/users/main.go b/pkg/lagoon/users/main.go index 4afa8fb6..c9e55044 100644 --- a/pkg/lagoon/users/main.go +++ b/pkg/lagoon/users/main.go @@ -23,7 +23,6 @@ type Client interface { ListUsers(string) ([]byte, error) AddUser(api.User) ([]byte, error) AddSSHKeyToUser(api.User, api.SSHKey) ([]byte, error) - DeleteSSHKey(string) ([]byte, error) DeleteUser(api.User) ([]byte, error) ModifyUser(api.User, api.User) ([]byte, error) ListUserSSHKeys(string, string, bool) ([]byte, error) diff --git a/pkg/lagoon/users/users.go b/pkg/lagoon/users/users.go index c4c15897..411a92bb 100644 --- a/pkg/lagoon/users/users.go +++ b/pkg/lagoon/users/users.go @@ -61,24 +61,6 @@ func (u *Users) AddSSHKeyToUser(user api.User, sshKey api.SSHKey) ([]byte, error return returnResult, nil } -// DeleteSSHKey function -func (u *Users) DeleteSSHKey(keyName string) ([]byte, error) { - customReq := api.CustomRequest{ - Query: `mutation deleteSshKey ($keyname: String!) { - deleteSshKey(input:{name: $keyname}) - }`, - Variables: map[string]interface{}{ - "keyname": keyName, - }, - MappedResult: "deleteSshKey", - } - returnResult, err := u.api.Request(customReq) - if err != nil { - return []byte(""), err - } - return returnResult, nil -} - // DeleteUser function func (u *Users) DeleteUser(user api.User) ([]byte, error) { customReq := api.CustomRequest{ @@ -218,7 +200,7 @@ func (u *Users) ListUserSSHKeys(groupName string, email string, allUsers bool) ( customReq := api.CustomRequest{ Query: `query allGroups ($name: String) { allGroups (name: $name) { - name + nameListUserSSHKeys id members{ user{ From c74aada669e66705820f12699fd658ed5bf30859 Mon Sep 17 00:00:00 2001 From: cgoodwin90 Date: Tue, 21 Nov 2023 15:07:52 +1100 Subject: [PATCH 3/4] Refactored getAllUserKeys & removed unused function --- cmd/users.go | 64 +++++++++++++++++++++++++++++++++------ go.mod | 2 +- go.sum | 2 ++ pkg/lagoon/users/main.go | 1 - pkg/lagoon/users/users.go | 52 ------------------------------- 5 files changed, 57 insertions(+), 64 deletions(-) diff --git a/cmd/users.go b/cmd/users.go index cd21cf3c..72b306e2 100644 --- a/cmd/users.go +++ b/cmd/users.go @@ -7,6 +7,7 @@ import ( "fmt" l "github.com/uselagoon/machinery/api/lagoon" lclient "github.com/uselagoon/machinery/api/lagoon/client" + s "github.com/uselagoon/machinery/api/schema" "io/ioutil" "os" "strconv" @@ -302,18 +303,61 @@ var getAllUserKeysCmd = &cobra.Command{ Aliases: []string{"aus"}, Short: "Get all user SSH keys", Long: `Get all user SSH keys. This will only work for users that are part of a group`, - Run: func(cmd *cobra.Command, args []string) { - returnedJSON, err := uClient.ListUserSSHKeys(groupName, strings.ToLower(userEmail), true) - handleError(err) - var dataMain output.Table - err = json.Unmarshal([]byte(returnedJSON), &dataMain) + PreRunE: func(_ *cobra.Command, _ []string) error { + return validateTokenE(cmdLagoon) + }, + RunE: func(cmd *cobra.Command, args []string) error { + debug, err := cmd.Flags().GetBool("debug") + if err != nil { + return err + } + groupName, err := cmd.Flags().GetString("name") + if err != nil { + return err + } + + current := lagoonCLIConfig.Current + token := lagoonCLIConfig.Lagoons[current].Token + lc := lclient.New( + lagoonCLIConfig.Lagoons[current].GraphQL, + lagoonCLIVersion, + &token, + debug) + groupMembers, err := l.ListAllGroupMembersWithKeys(context.TODO(), groupName, lc) handleError(err) - if len(dataMain.Data) == 0 { - output.RenderInfo("No SSH keys for any users", outputOptions) - os.Exit(0) + + var userGroups []s.AddSSHKeyInput + for _, group := range *groupMembers { + for _, member := range group.Members { + for _, key := range member.User.SSHKeys { + userGroups = append(userGroups, s.AddSSHKeyInput{SSHKey: key, UserEmail: member.User.Email}) + } + } } - output.RenderOutput(dataMain, outputOptions) + var data []output.Data + for _, userData := range userGroups { + keyID := strconv.Itoa(int(userData.SSHKey.ID)) + userEmail := returnNonEmptyString(strings.Replace(userData.UserEmail, " ", "_", -1)) + keyName := returnNonEmptyString(strings.Replace(userData.SSHKey.Name, " ", "_", -1)) + keyValue := returnNonEmptyString(strings.Replace(userData.SSHKey.KeyValue, " ", "_", -1)) + keyType := returnNonEmptyString(strings.Replace(string(userData.SSHKey.KeyType), " ", "_", -1)) + data = append(data, []string{ + keyID, + userEmail, + keyName, + keyType, + keyValue, + }) + } + + dataMain := output.Table{ + Header: []string{"ID", "Email", "Name", "Type", "Value"}, + Data: data, + } + + output.RenderOutput(dataMain, outputOptions) + return nil }, } @@ -337,5 +381,5 @@ func init() { updateUserCmd.Flags().StringVarP(&userEmail, "email", "E", "", "New email address of the user") updateUserCmd.Flags().StringVarP(¤tUserEmail, "current-email", "C", "", "Current email address of the user") getUserKeysCmd.Flags().StringP("email", "E", "", "New email address of the user") - getAllUserKeysCmd.Flags().StringVarP(&groupName, "name", "N", "", "Name of the group to list users in (if not specified, will default to all groups)") + getAllUserKeysCmd.Flags().StringP("name", "N", "", "Name of the group to list users in (if not specified, will default to all groups)") } diff --git a/go.mod b/go.mod index ec8cfc7e..8b3a1ac3 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/guregu/null v4.0.0+incompatible // workaround for https://github.com/manifoldco/promptui/issues/98 github.com/nicksnyder/go-i18n v1.10.1 // indirect - github.com/uselagoon/machinery v0.0.13-0.20231116024123-c712ade42522 + github.com/uselagoon/machinery v0.0.13-0.20231121033119-b49fba1fd1a8 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 // indirect golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect diff --git a/go.sum b/go.sum index 050d8cda..6ba827e7 100644 --- a/go.sum +++ b/go.sum @@ -110,6 +110,8 @@ github.com/uselagoon/machinery v0.0.11 h1:s6EhyU/pj1+C4FdS0EqmR6C0dLsoeCd9n+5xHL github.com/uselagoon/machinery v0.0.11/go.mod h1:IXLxlkahEAEgpCmu9Xa/Wmjo6ja4Aoq7tf8G7VrileE= github.com/uselagoon/machinery v0.0.13-0.20231116024123-c712ade42522 h1:6YU7GMpDL+3xwIbdtTwIYEOWJFbrKd20BeOXtAFEkyg= github.com/uselagoon/machinery v0.0.13-0.20231116024123-c712ade42522/go.mod h1:h/qeMWQR4Qqu33x+8AulNDeolEwvb/G+aIsn/jyUtwk= +github.com/uselagoon/machinery v0.0.13-0.20231121033119-b49fba1fd1a8 h1:FaESz9DLg4644OxrMyNAhzfZiR4jaga8gR0FOPje/e4= +github.com/uselagoon/machinery v0.0.13-0.20231121033119-b49fba1fd1a8/go.mod h1:h/qeMWQR4Qqu33x+8AulNDeolEwvb/G+aIsn/jyUtwk= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= diff --git a/pkg/lagoon/users/main.go b/pkg/lagoon/users/main.go index c9e55044..39d40039 100644 --- a/pkg/lagoon/users/main.go +++ b/pkg/lagoon/users/main.go @@ -25,7 +25,6 @@ type Client interface { AddSSHKeyToUser(api.User, api.SSHKey) ([]byte, error) DeleteUser(api.User) ([]byte, error) ModifyUser(api.User, api.User) ([]byte, error) - ListUserSSHKeys(string, string, bool) ([]byte, error) ListGroups(string) ([]byte, error) ListGroupProjects(string, bool) ([]byte, error) } diff --git a/pkg/lagoon/users/users.go b/pkg/lagoon/users/users.go index 411a92bb..1cf3ea5a 100644 --- a/pkg/lagoon/users/users.go +++ b/pkg/lagoon/users/users.go @@ -194,58 +194,6 @@ func processUserList(listUsers []byte) ([]byte, error) { return json.Marshal(dataMain) } -// ListUserSSHKeys function -func (u *Users) ListUserSSHKeys(groupName string, email string, allUsers bool) ([]byte, error) { - //@TODO: once individual user interaction comes in, this will need to be adjusted - customReq := api.CustomRequest{ - Query: `query allGroups ($name: String) { - allGroups (name: $name) { - nameListUserSSHKeys - id - members{ - user{ - id - email - firstName - lastName - sshKeys{ - name - keyType - keyValue - } - } - role - } - } - }`, - Variables: map[string]interface{}{ - "name": groupName, - }, - MappedResult: "allGroups", - } - listUsers, err := u.api.Request(customReq) - if err != nil { - return []byte(""), err - } - returnedKeys, err := processReturnedUserKeysList(listUsers) - if err != nil { - return []byte(""), err - } - var returnResult []byte - if allUsers { - returnResult, err = processAllUserKeysList(returnedKeys) - if err != nil { - return []byte(""), err - } - } else { - returnResult, err = processUserKeysList(returnedKeys, email) - if err != nil { - return []byte(""), err - } - } - return returnResult, nil -} - func processReturnedUserKeysList(listUsers []byte) ([]ExtendedSSHKey, error) { var groupMembers GroupMembers userDataStep1 := []ExtendedSSHKey{} From 2d80446d93d2b0cd65b519e83f069d658dad997a Mon Sep 17 00:00:00 2001 From: cgoodwin90 Date: Tue, 28 Nov 2023 16:48:09 +1100 Subject: [PATCH 4/4] Updated to Machinery v0.0.13 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 8b3a1ac3..2d25de43 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/guregu/null v4.0.0+incompatible // workaround for https://github.com/manifoldco/promptui/issues/98 github.com/nicksnyder/go-i18n v1.10.1 // indirect - github.com/uselagoon/machinery v0.0.13-0.20231121033119-b49fba1fd1a8 + github.com/uselagoon/machinery v0.0.13 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 // indirect golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect diff --git a/go.sum b/go.sum index 6ba827e7..fc20cefd 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,8 @@ github.com/uselagoon/machinery v0.0.13-0.20231116024123-c712ade42522 h1:6YU7GMpD github.com/uselagoon/machinery v0.0.13-0.20231116024123-c712ade42522/go.mod h1:h/qeMWQR4Qqu33x+8AulNDeolEwvb/G+aIsn/jyUtwk= github.com/uselagoon/machinery v0.0.13-0.20231121033119-b49fba1fd1a8 h1:FaESz9DLg4644OxrMyNAhzfZiR4jaga8gR0FOPje/e4= github.com/uselagoon/machinery v0.0.13-0.20231121033119-b49fba1fd1a8/go.mod h1:h/qeMWQR4Qqu33x+8AulNDeolEwvb/G+aIsn/jyUtwk= +github.com/uselagoon/machinery v0.0.13 h1:ZCLBNWJmDr3wikaHs3pWhQ1j8MprhIqRuChgSqmLyZc= +github.com/uselagoon/machinery v0.0.13/go.mod h1:h/qeMWQR4Qqu33x+8AulNDeolEwvb/G+aIsn/jyUtwk= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=