diff --git a/general/envsetup/envsetup.go b/general/envsetup/envsetup.go deleted file mode 100644 index a7cc75943..000000000 --- a/general/envsetup/envsetup.go +++ /dev/null @@ -1,393 +0,0 @@ -package envsetup - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "github.com/google/uuid" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/generic" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" - "github.com/jfrog/jfrog-cli-core/v2/common/commands" - "github.com/jfrog/jfrog-cli-core/v2/general" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" - "github.com/jfrog/jfrog-client-go/access/services" - "github.com/jfrog/jfrog-client-go/http/httpclient" - clientUtils "github.com/jfrog/jfrog-client-go/utils" - "github.com/jfrog/jfrog-client-go/utils/errorutils" - ioUtils "github.com/jfrog/jfrog-client-go/utils/io" - "github.com/jfrog/jfrog-client-go/utils/io/httputils" - "github.com/jfrog/jfrog-client-go/utils/log" - "github.com/pkg/browser" - "net/http" - "time" -) - -type OutputFormat string - -const ( - myJfrogEndPoint = "https://myjfrog-api.jfrog.com/api/v1/activation/cloud/cli/getStatus/" - syncSleepInterval = 5 * time.Second // 5 seconds - maxWaitMinutes = 30 * time.Minute // 30 minutes - - // OutputFormat values - Human OutputFormat = "human" - Machine OutputFormat = "machine" - - // When entering password on terminal the user has limited number of retries. - enterPasswordMaxRetries = 20 - - MessageIdes = "📦 If you're using VS Code, IntelliJ IDEA, WebStorm, PyCharm, Android Studio or GoLand\n" + - " Open the IDE 👉 Install the JFrog extension or plugin 👉 View the JFrog panel" - MessageDockerDesktop = "📦 Open Docker Desktop and install the JFrog Extension to scan any of your \n" + - " local docker images" - MessageDockerScan = "📦 Scan local Docker images from the terminal by running\n" + - " jf docker scan :" -) - -type EnvSetupCommand struct { - registrationURL string - // In case encodedConnectionDetails were provided - we have a registered user that was invited to the platform. - encodedConnectionDetails string - id uuid.UUID - serverDetails *config.ServerDetails - progress ioUtils.ProgressMgr - outputFormat OutputFormat -} - -func (ftc *EnvSetupCommand) SetRegistrationURL(registrationURL string) *EnvSetupCommand { - ftc.registrationURL = registrationURL - return ftc -} - -func (ftc *EnvSetupCommand) SetEncodedConnectionDetails(encodedConnectionDetails string) *EnvSetupCommand { - ftc.encodedConnectionDetails = encodedConnectionDetails - return ftc -} - -func (ftc *EnvSetupCommand) ServerDetails() (*config.ServerDetails, error) { - return nil, nil -} - -func (ftc *EnvSetupCommand) SetProgress(progress ioUtils.ProgressMgr) { - ftc.progress = progress -} - -func (ftc *EnvSetupCommand) SetOutputFormat(format OutputFormat) *EnvSetupCommand { - ftc.outputFormat = format - return ftc -} - -func NewEnvSetupCommand() *EnvSetupCommand { - return &EnvSetupCommand{ - id: uuid.New(), - } -} - -// This function is a wrapper around the 'ftc.progress.SetHeadlineMsg(msg)' API, -// to make sure that ftc.progress isn't nil. It can be nil in case the CI environment variable is set. -// In case ftc.progress is nil, the message sent will be prompted to the screen -// without the progress indication. -func (ftc *EnvSetupCommand) setHeadlineMsg(msg string) { - if ftc.progress != nil { - ftc.progress.SetHeadlineMsg(msg) - } else { - log.Output(msg + "...") - } -} - -// This function is a wrapper around the 'ftc.progress.clearHeadlineMsg()' API, -// to make sure that ftc.progress isn't nil before clearing it. -// It can be nil in case the CI environment variable is set. -func (ftc *EnvSetupCommand) clearHeadlineMsg() { - if ftc.progress != nil { - ftc.progress.ClearHeadlineMsg() - } -} - -// This function is a wrapper around the 'ftc.progress.Quit()' API, -// to make sure that ftc.progress isn't nil before clearing it. -// It can be nil in case the CI environment variable is set. -func (ftc *EnvSetupCommand) quitProgress() error { - if ftc.progress != nil { - return ftc.progress.Quit() - } - return nil -} - -func (ftc *EnvSetupCommand) Run() (err error) { - err = ftc.SetupAndConfigServer() - if err != nil { - return - } - if ftc.outputFormat == Human { - // Closes the progress manger and reset the log prints. - err = ftc.quitProgress() - if err != nil { - return - } - log.Output() - log.Output(coreutils.PrintBold("Congrats! You're all set")) - log.Output("So what's next?") - message := - coreutils.PrintTitle("IDE") + "\n" + - MessageIdes + "\n\n" + - coreutils.PrintTitle("Docker") + "\n" + - "You can scan your local Docker images from the terminal or the Docker Desktop UI\n" + - MessageDockerScan + "\n" + - MessageDockerDesktop + "\n\n" + - coreutils.PrintTitle("Build, scan & deploy") + "\n" + - "1. 'cd' into your code project directory\n" + - "2. Run 'jf project init'\n\n" + - coreutils.PrintTitle("Read more") + "\n" + - "📦 Read more about how to get started at -\n" + - " " + coreutils.PrintLink(coreutils.GettingStartedGuideUrl) - err = coreutils.PrintTable("", "", message, false) - if err != nil { - return - } - if ftc.encodedConnectionDetails == "" { - log.Output(coreutils.PrintBold("Important")) - log.Output("Please use the email we've just sent you, to verify your email address during the next 48 hours.\n") - } - } - return -} - -func (ftc *EnvSetupCommand) SetupAndConfigServer() (err error) { - var server *config.ServerDetails - // If credentials were provided, this means that the user was invited to join an existing JFrog environment. - // Otherwise, this is a brand-new user, that needs to register and set up a new JFrog environment. - if ftc.encodedConnectionDetails == "" { - server, err = ftc.setupNewUser() - } else { - server, err = ftc.setupExistingUser() - } - if err != nil { - return - } - err = general.ConfigServerWithDeducedId(server, false, false) - return -} - -func (ftc *EnvSetupCommand) setupNewUser() (server *config.ServerDetails, err error) { - if ftc.outputFormat == Human { - ftc.setHeadlineMsg("Just fill out its details in your browser 📝") - time.Sleep(8 * time.Second) - } else { - // Closes the progress manger and reset the log prints. - err = ftc.quitProgress() - if err != nil { - return - } - } - err = browser.OpenURL(ftc.registrationURL + "?id=" + ftc.id.String()) - if err != nil { - return - } - server, err = ftc.getNewServerDetails() - return -} - -func (ftc *EnvSetupCommand) setupExistingUser() (server *config.ServerDetails, err error) { - err = ftc.quitProgress() - if err != nil { - return - } - server, err = ftc.decodeConnectionDetails() - if err != nil { - return - } - if server.Url == "" { - err = errorutils.CheckErrorf("The response from JFrog Access does not include a JFrog environment URL") - return - } - if server.AccessToken != "" { - // If the server details received from JFrog Access include an access token, this access token is - // short-lived, and we therefore need to replace it with a new long-lived access token, and configure - // JFrog CLI with it. - err = GenerateNewLongTermRefreshableAccessToken(server) - return - } - if server.User == "" { - err = errorutils.CheckErrorf("The response from JFrog Access does not includes a username or access token") - return - } - // Url and accessToken/userName must be provided in the base64 encoded connection details. - // APIkey/password are optional - In case they were not provided user can enter his password on console. - // Password will be validated before the config command is being called. - if server.Password == "" { - err = ftc.scanAndValidateJFrogPasswordFromConsole(server) - } - return -} - -func (ftc *EnvSetupCommand) scanAndValidateJFrogPasswordFromConsole(server *config.ServerDetails) (err error) { - // User has limited number of retries to enter his correct password. - // Password validation is operated by Artifactory ping API. - server.ArtifactoryUrl = clientUtils.AddTrailingSlashIfNeeded(server.Url) + "artifactory/" - for i := 0; i < enterPasswordMaxRetries; i++ { - server.Password, err = ioutils.ScanJFrogPasswordFromConsole() - if err != nil { - return - } - // Validate correct password by using Artifactory ping API. - pingCmd := generic.NewPingCommand().SetServerDetails(server) - err = commands.Exec(pingCmd) - if err == nil { - // No error while encrypting password => correct password. - return nil - } - log.Output(err.Error()) - } - err = errorutils.CheckErrorf("bad credentials: Wrong password. ") - return -} - -// Take the short-lived token and generate a long term (1 year expiry) refreshable accessToken. -func GenerateNewLongTermRefreshableAccessToken(server *config.ServerDetails) (err error) { - accessManager, err := utils.CreateAccessServiceManager(server, false) - if err != nil { - return - } - // Create refreshable accessToken with 1 year expiry from the given short expiry token. - params := createLongExpirationRefreshableTokenParams() - token, err := accessManager.CreateAccessToken(*params) - if err != nil { - return - } - server.AccessToken = token.AccessToken - server.RefreshToken = token.RefreshToken - return -} - -func createLongExpirationRefreshableTokenParams() *services.CreateTokenParams { - params := services.CreateTokenParams{} - // Using the platform's default expiration (1 year by default). - params.ExpiresIn = nil - params.Refreshable = clientUtils.Pointer(true) - params.Audience = "*@*" - return ¶ms -} - -func (ftc *EnvSetupCommand) decodeConnectionDetails() (server *config.ServerDetails, err error) { - rawDecodedText, err := base64.StdEncoding.DecodeString(ftc.encodedConnectionDetails) - if errorutils.CheckError(err) != nil { - return - } - err = json.Unmarshal(rawDecodedText, &server) - if errorutils.CheckError(err) != nil { - return nil, err - } - return -} - -func (ftc *EnvSetupCommand) CommandName() string { - return "setup" -} - -// Returns the new server details from My-JFrog -func (ftc *EnvSetupCommand) getNewServerDetails() (serverDetails *config.ServerDetails, err error) { - requestBody := &myJfrogGetStatusRequest{CliRegistrationId: ftc.id.String()} - requestContent, err := json.Marshal(requestBody) - if errorutils.CheckError(err) != nil { - return nil, err - } - - httpClientDetails := httputils.HttpClientDetails{ - Headers: map[string]string{"Content-Type": "application/json"}, - } - client, err := httpclient.ClientBuilder().Build() - if err != nil { - return nil, err - } - - // Define the MyJFrog polling logic. - pollingMessage := fmt.Sprintf("Sync: Get MyJFrog status report. Request ID:%s...", ftc.id) - pollingErrorMessage := "Sync: Get MyJFrog status request failed. Attempt: %d. Error: %s" - // The max consecutive polling errors allowed, before completely failing the setup action. - const maxConsecutiveErrors = 6 - errorsCount := 0 - readyMessageDisplayed := false - pollingAction := func() (shouldStop bool, responseBody []byte, err error) { - log.Debug(pollingMessage) - // Send request to MyJFrog. - resp, body, err := client.SendPost(myJfrogEndPoint, requestContent, httpClientDetails, "") - // If an HTTP error occurred. - if err != nil { - errorsCount++ - log.Debug(fmt.Sprintf(pollingErrorMessage, errorsCount, err.Error())) - if errorsCount == maxConsecutiveErrors { - return true, nil, err - } - return false, nil, nil - } - // If the response is not the expected 200 or 404. - if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK, http.StatusNotFound); err != nil { - errorsCount++ - log.Debug(fmt.Sprintf(pollingErrorMessage, errorsCount, err.Error())) - if errorsCount == maxConsecutiveErrors { - return true, nil, err - } - return false, nil, nil - } - errorsCount = 0 - - // Wait for 'ready=true' response from MyJFrog - if resp.StatusCode == http.StatusOK { - if !readyMessageDisplayed { - if ftc.outputFormat == Machine { - log.Output("PREPARING_ENV") - } else { - ftc.clearHeadlineMsg() - ftc.setHeadlineMsg("Almost done! Please hang on while JFrog CLI completes the setup 🛠") - } - readyMessageDisplayed = true - } - statusResponse := myJfrogGetStatusResponse{} - if err = json.Unmarshal(body, &statusResponse); err != nil { - return true, nil, err - } - // Got the new server details - if statusResponse.Ready { - return true, body, nil - } - } - // The expected 404 response or 200 response without 'Ready' - return false, nil, nil - } - - pollingExecutor := &httputils.PollingExecutor{ - Timeout: maxWaitMinutes, - PollingInterval: syncSleepInterval, - PollingAction: pollingAction, - } - - body, err := pollingExecutor.Execute() - if err != nil { - return nil, err - } - statusResponse := myJfrogGetStatusResponse{} - if err = json.Unmarshal(body, &statusResponse); err != nil { - return nil, errorutils.CheckError(err) - } - ftc.clearHeadlineMsg() - serverDetails = &config.ServerDetails{ - Url: statusResponse.PlatformUrl, - AccessToken: statusResponse.AccessToken, - } - ftc.serverDetails = serverDetails - return serverDetails, nil -} - -type myJfrogGetStatusRequest struct { - CliRegistrationId string `json:"cliRegistrationId,omitempty"` -} - -type myJfrogGetStatusResponse struct { - CliRegistrationId string `json:"cliRegistrationId,omitempty"` - Ready bool `json:"ready,omitempty"` - AccessToken string `json:"accessToken,omitempty"` - PlatformUrl string `json:"platformUrl,omitempty"` -} diff --git a/general/envsetup/envsetup_test.go b/general/envsetup/envsetup_test.go deleted file mode 100644 index 281855905..000000000 --- a/general/envsetup/envsetup_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package envsetup - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -/* #nosec G101 -- Dummy tokens for tests. */ -func TestDecodeConnectionDetails(t *testing.T) { - base64Cred := "eyJ1cmwiOiJodHRwOi8vbG9jYWxob3N0OjgwOTAvIiwiYWNjZXNzVG9rZW4iOiJleUoyWlhJaU9pSXlJaXdpZEhsd0lqb2lTbGRVSWl3aVlXeG5Jam9pVWxNeU5UWWlMQ0pyYVdRaU9pSTNkbDlOYkdGYVp6RnpiV1pJVGpSYWFtSkVWbXBSUldSelozUkNOV3N5T1ZodVdIUmpTakJaU2tkUkluMC5leUpsZUhRaU9pSjdYQ0p5WlhadlkyRmliR1ZjSWpwY0luUnlkV1ZjSW4waUxDSnpkV0lpT2lKcVptRmpRREF4Wm5rME1UUXdhR3B5WTJvNE1IY3ljRE15Y1hneE9IbHdYQzkxYzJWeWMxd3ZZVzFwY201aFFHcG1jbTluTG1OdmJTSXNJbk5qY0NJNkltRndjR3hwWldRdGNHVnliV2x6YzJsdmJuTmNMM1Z6WlhJaUxDSmhkV1FpT2lJcVFDb2lMQ0pwYzNNaU9pSnFabVpsUURBd01DSXNJbVY0Y0NJNk1UWTBOekkyT0RNd01Dd2lhV0YwSWpveE5qUTNNalUzTlRBd0xDSnFkR2tpT2lKbU1HUTBOR0UxTUMwME1UazBMVFJoWmpRdFltUTFPUzAyWmprek16SXhNalkzWkdZaWZRLktpa1VkVENkRXFMWkdRR2c4TE43LXpZVWNac3dDNmRmU0Jxb2d1RnpvVHhFelFfYmNnZnJTcnVvQWd3MmRERWlPNEJCOEpHel9mUUxWcUVJY1p1RHRvMHc4c0lPREZJdXQyRUVzY3ZxZ2NuRmIyMXFWaTVMRW5FbVFzeW9iSFpPUGJfY081d3JQWEpfUFVsejJQci1iLWN2WjV4UlR0aXBQR1RFM0FuOUdhY19raDBqX2ZLRHRJQXFvQnh4bG1LVERreUJ4MHNwZ0dOTmlfS1VOMzlwaUNnRkg5d1ROcXBZY3o5SW1MX1FacUFxeXQtRnN3M3E1TFJRams4V2tfX0ZEYU9OYmI2enZRS1E4VnctUUR6bmYxeVNuLVRGVFFvaHdoZ0Jab3ZqeUVjYWRLZlR4YXYyUVNsUHNxckhoVFZGQVNnOWRRakg5cDJjV3FqQmRDWG5XdyJ9" - expectedAccessToken := "eyJ2ZXIiOiIyIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYiLCJraWQiOiI3dl9NbGFaZzFzbWZITjRaamJEVmpRRWRzZ3RCNWsyOVhuWHRjSjBZSkdRIn0.eyJleHQiOiJ7XCJyZXZvY2FibGVcIjpcInRydWVcIn0iLCJzdWIiOiJqZmFjQDAxZnk0MTQwaGpyY2o4MHcycDMycXgxOHlwXC91c2Vyc1wvYW1pcm5hQGpmcm9nLmNvbSIsInNjcCI6ImFwcGxpZWQtcGVybWlzc2lvbnNcL3VzZXIiLCJhdWQiOiIqQCoiLCJpc3MiOiJqZmZlQDAwMCIsImV4cCI6MTY0NzI2ODMwMCwiaWF0IjoxNjQ3MjU3NTAwLCJqdGkiOiJmMGQ0NGE1MC00MTk0LTRhZjQtYmQ1OS02ZjkzMzIxMjY3ZGYifQ.KikUdTCdEqLZGQGg8LN7-zYUcZswC6dfSBqoguFzoTxEzQ_bcgfrSruoAgw2dDEiO4BB8JGz_fQLVqEIcZuDto0w8sIODFIut2EEscvqgcnFb21qVi5LEnEmQsyobHZOPb_cO5wrPXJ_PUlz2Pr-b-cvZ5xRTtipPGTE3An9Gac_kh0j_fKDtIAqoBxxlmKTDkyBx0spgGNNi_KUN39piCgFH9wTNqpYcz9ImL_QZqAqyt-Fsw3q5LRQjk8Wk__FDaONbb6zvQKQ8Vw-QDznf1ySn-TFTQohwhgBZovjyEcadKfTxav2QSlPsqrHhTVFASg9dQjH9p2cWqjBdCXnWw" - setupCmd := NewEnvSetupCommand().SetEncodedConnectionDetails(base64Cred) - server, err := setupCmd.decodeConnectionDetails() - assert.NoError(t, err) - assert.Equal(t, "http://localhost:8090/", server.Url) - assert.Equal(t, expectedAccessToken, server.AccessToken) -} diff --git a/go.mod b/go.mod index 9dfff0a10..4e4137ab5 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/jedib0t/go-pretty/v6 v6.5.9 github.com/jfrog/build-info-go v1.9.29 github.com/jfrog/gofrog v1.7.2 - github.com/jfrog/jfrog-client-go v1.41.0 + github.com/jfrog/jfrog-client-go v1.41.1 github.com/magiconair/properties v1.8.7 github.com/manifoldco/promptui v0.9.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c diff --git a/go.sum b/go.sum index 1cc7cde52..831153302 100644 --- a/go.sum +++ b/go.sum @@ -95,8 +95,8 @@ github.com/jfrog/build-info-go v1.9.29 h1:3vJ+kbk9PpU6wjisXi9c4qISNpYkISh/NmB5mq github.com/jfrog/build-info-go v1.9.29/go.mod h1:AzFJlN/yKfKuKcSBaGy5nNmKN1xzx6+XcRWAswCTLTA= github.com/jfrog/gofrog v1.7.2 h1:VkAaA/9tmbw27IqgUOmaZWnO6ATUqL3vRzDnsROKATw= github.com/jfrog/gofrog v1.7.2/go.mod h1:WJFk88SR9Sr9mKl1bQBig7DmSdXiBGKV3WhL9O6jL9w= -github.com/jfrog/jfrog-client-go v1.41.0 h1:g5OTFvreOVQ6U/5LUXFJfA3Bc+AZCo2PO/EzCLxLbLE= -github.com/jfrog/jfrog-client-go v1.41.0/go.mod h1:AN+/mT2DIBE4oRZicJojqND2BEKLfA7f73i5rT3Lfcc= +github.com/jfrog/jfrog-client-go v1.41.1 h1:AlgEIwEULIrk6CSVgt9gg8fQt3oI+cBqKP+eRNsH2aA= +github.com/jfrog/jfrog-client-go v1.41.1/go.mod h1:AN+/mT2DIBE4oRZicJojqND2BEKLfA7f73i5rT3Lfcc= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=