diff --git a/cmd/abapEnvironmentRunATCCheck.go b/cmd/abapEnvironmentRunATCCheck.go index 6bd217af8..79cd4ea0b 100644 --- a/cmd/abapEnvironmentRunATCCheck.go +++ b/cmd/abapEnvironmentRunATCCheck.go @@ -231,7 +231,8 @@ func checkHost(config abapEnvironmentRunATCCheckOptions, details connectionDetai return details, errors.New("Parameters missing. Please provide EITHER the Host of the ABAP server OR the Cloud Foundry ApiEndpoint, Organization, Space, Service Instance and a corresponding Service Key for the Communication Scenario SAP_COM_0510") } var abapServiceKey cloudfoundry.ServiceKey - abapServiceKey, err = cloudfoundry.ReadServiceKeyAbapEnvironment(cfconfig, true) + cf := cloudfoundry.CFUtils{Exec: &command.Command{}} + abapServiceKey, err = cf.ReadServiceKeyAbapEnvironment(cfconfig, true) if err != nil { return details, fmt.Errorf("Reading Service Key failed: %w", err) } diff --git a/pkg/cloudfoundry/Authentication.go b/pkg/cloudfoundry/Authentication.go index 78429a586..c42953006 100644 --- a/pkg/cloudfoundry/Authentication.go +++ b/pkg/cloudfoundry/Authentication.go @@ -10,14 +10,18 @@ import ( "github.com/SAP/jenkins-library/pkg/log" ) -var c command.ExecRunner = &command.Command{} - //LoginCheck checks if user is logged in to Cloud Foundry. //If user is not logged in 'cf api' command will return string that contains 'User is not logged in' only if user is not logged in. //If the returned string doesn't contain the substring 'User is not logged in' we know he is logged in. -func LoginCheck(options LoginOptions) (bool, error) { +func (cf *CFUtils) LoginCheck(options LoginOptions) (bool, error) { var err error + _c := cf.Exec + + if _c == nil { + _c = &command.Command{} + } + if options.CfAPIEndpoint == "" { return false, errors.New("Cloud Foundry API endpoint parameter missing. Please provide the Cloud Foundry Endpoint") } @@ -25,12 +29,26 @@ func LoginCheck(options LoginOptions) (bool, error) { //Check if logged in --> Cf api command responds with "not logged in" if positive var cfCheckLoginScript = append([]string{"api", options.CfAPIEndpoint}, options.CfAPIOpts...) + defer func() { + // We set it back to what is set from the generated stub. Of course this is not + // fully accurate in case we create our own instance above (nothing handed in via + // the receiver). + // Would be better to remember the old stdout and set back to this. + // But command.Command does not allow to get the currently set + // stdout handler. + // Reason for changing the output stream here: we need to parse the output + // of the command issued here in order to check if we are already logged in. + // This is expected to change soon to a boolean variable where we remember the + // login state. + _c.Stdout(log.Writer()) + }() + var cfLoginBytes bytes.Buffer - c.Stdout(&cfLoginBytes) + _c.Stdout(&cfLoginBytes) var result string - err = c.RunExecutable("cf", cfCheckLoginScript...) + err = _c.RunExecutable("cf", cfCheckLoginScript...) if err != nil { return false, fmt.Errorf("Failed to check if logged in: %w", err) @@ -52,17 +70,22 @@ func LoginCheck(options LoginOptions) (bool, error) { //Login logs user in to Cloud Foundry via cf cli. //Checks if user is logged in first, if not perform 'cf login' command with appropriate parameters -func Login(options LoginOptions) error { - +func (cf *CFUtils) Login(options LoginOptions) error { var err error + _c := cf.Exec + + if _c == nil { + _c = &command.Command{} + } + if options.CfAPIEndpoint == "" || options.CfOrg == "" || options.CfSpace == "" || options.Username == "" || options.Password == "" { return fmt.Errorf("Failed to login to Cloud Foundry: %w", errors.New("Parameters missing. Please provide the Cloud Foundry Endpoint, Org, Space, Username and Password")) } var loggedIn bool - loggedIn, err = LoginCheck(options) + loggedIn, err = cf.LoginCheck(options) if loggedIn == true { return err @@ -82,7 +105,7 @@ func Login(options LoginOptions) error { log.Entry().WithField("cfAPI:", options.CfAPIEndpoint).WithField("cfOrg", options.CfOrg).WithField("space", options.CfSpace).Info("Logging into Cloud Foundry..") - err = c.RunExecutable("cf", cfLoginScript...) + err = _c.RunExecutable("cf", cfLoginScript...) } if err != nil { @@ -94,12 +117,19 @@ func Login(options LoginOptions) error { //Logout logs User out of Cloud Foundry //Logout can be perforned via 'cf logout' command regardless if user is logged in or not -func Logout() error { +func (cf *CFUtils) Logout() error { + + _c := cf.Exec + + if _c == nil { + _c = &command.Command{} + } + var cfLogoutScript = "logout" log.Entry().Info("Logging out of Cloud Foundry") - err := c.RunExecutable("cf", cfLogoutScript) + err := _c.RunExecutable("cf", cfLogoutScript) if err != nil { return fmt.Errorf("Failed to Logout of Cloud Foundry: %w", err) } @@ -117,3 +147,13 @@ type LoginOptions struct { CfAPIOpts []string CfLoginOpts []string } + +// CFUtils ... +type CFUtils struct { + // In order to avoid clashes between parallel workflows requiring cf login/logout + // this instance of command.ExecRunner can be configured accordingly by settings the + // environment variables CF_HOME to distict directories. + // In order to ensure plugins installed to the cf cli are found environment variables + // CF_PLUGIN_HOME can be set accordingly. + Exec command.ExecRunner +} diff --git a/pkg/cloudfoundry/CloudFoundry_test.go b/pkg/cloudfoundry/CloudFoundry_test.go index 8cc1ec896..8fd53c08b 100644 --- a/pkg/cloudfoundry/CloudFoundry_test.go +++ b/pkg/cloudfoundry/CloudFoundry_test.go @@ -18,14 +18,10 @@ func TestCloudFoundryLoginCheck(t *testing.T) { m := &mock.ExecMockRunner{} - defer func() { - c = &command.Command{} - }() - c = m - t.Run("CF Login check: missing endpoint parameter", func(t *testing.T) { cfconfig := LoginOptions{} - loggedIn, err := LoginCheck(cfconfig) + cf := CFUtils{Exec: m} + loggedIn, err := cf.LoginCheck(cfconfig) assert.False(t, loggedIn) assert.EqualError(t, err, "Cloud Foundry API endpoint parameter missing. Please provide the Cloud Foundry Endpoint") }) @@ -38,7 +34,8 @@ func TestCloudFoundryLoginCheck(t *testing.T) { cfconfig := LoginOptions{ CfAPIEndpoint: "https://api.endpoint.com", } - loggedIn, err := LoginCheck(cfconfig) + cf := CFUtils{Exec: m} + loggedIn, err := cf.LoginCheck(cfconfig) assert.False(t, loggedIn) assert.Error(t, err) assert.Equal(t, []mock.ExecCall{mock.ExecCall{Exec: "cf", Params: []string{"api", "https://api.endpoint.com"}}}, m.Calls) @@ -51,7 +48,8 @@ func TestCloudFoundryLoginCheck(t *testing.T) { cfconfig := LoginOptions{ CfAPIEndpoint: "https://api.endpoint.com", } - loggedIn, err := LoginCheck(cfconfig) + cf := CFUtils{Exec: m} + loggedIn, err := cf.LoginCheck(cfconfig) if assert.NoError(t, err) { assert.True(t, loggedIn) assert.Equal(t, []mock.ExecCall{mock.ExecCall{Exec: "cf", Params: []string{"api", "https://api.endpoint.com"}}}, m.Calls) @@ -67,7 +65,9 @@ func TestCloudFoundryLoginCheck(t *testing.T) { // should never used in productive environment, but it is useful for rapid prototyping/troubleshooting CfAPIOpts: []string{"--skip-ssl-validation"}, } - loggedIn, err := LoginCheck(cfconfig) + + cf := CFUtils{Exec: m} + loggedIn, err := cf.LoginCheck(cfconfig) if assert.NoError(t, err) { assert.True(t, loggedIn) assert.Equal(t, []mock.ExecCall{ @@ -86,17 +86,13 @@ func TestCloudFoundryLogin(t *testing.T) { m := &mock.ExecMockRunner{} - defer func() { - c = &command.Command{} - }() - c = m - t.Run("CF Login: missing parameter", func(t *testing.T) { defer loginMockCleanup(m) cfconfig := LoginOptions{} - err := Login(cfconfig) + cf := CFUtils{Exec: m} + err := cf.Login(cfconfig) assert.EqualError(t, err, "Failed to login to Cloud Foundry: Parameters missing. Please provide the Cloud Foundry Endpoint, Org, Space, Username and Password") }) t.Run("CF Login: failure", func(t *testing.T) { @@ -114,7 +110,8 @@ func TestCloudFoundryLogin(t *testing.T) { Password: "testPassword", } - err := Login(cfconfig) + cf := CFUtils{Exec: m} + err := cf.Login(cfconfig) if assert.EqualError(t, err, "Failed to login to Cloud Foundry: wrong password or account does not exist") { assert.Equal(t, []mock.ExecCall{ mock.ExecCall{Exec: "cf", Params: []string{"api", "https://api.endpoint.com"}}, @@ -143,7 +140,8 @@ func TestCloudFoundryLogin(t *testing.T) { Username: "testUser", Password: "testPassword", } - err := Login(cfconfig) + cf := CFUtils{Exec: m} + err := cf.Login(cfconfig) if assert.NoError(t, err) { assert.Equal(t, []mock.ExecCall{ mock.ExecCall{Exec: "cf", Params: []string{"api", "https://api.endpoint.com"}}, @@ -180,7 +178,8 @@ func TestCloudFoundryLogin(t *testing.T) { "--skip-ssl-validation", }, } - err := Login(cfconfig) + cf := CFUtils{Exec: m} + err := cf.Login(cfconfig) if assert.NoError(t, err) { assert.Equal(t, []mock.ExecCall{ mock.ExecCall{Exec: "cf", Params: []string{ @@ -206,7 +205,8 @@ func TestCloudFoundryLogin(t *testing.T) { func TestCloudFoundryLogout(t *testing.T) { t.Run("CF Logout", func(t *testing.T) { - err := Logout() + cf := CFUtils{Exec: &mock.ExecMockRunner{}} + err := cf.Logout() if err == nil { assert.Equal(t, nil, err) } else { @@ -227,7 +227,8 @@ func TestCloudFoundryReadServiceKeyAbapEnvironment(t *testing.T) { Password: "testPassword", } var abapKey ServiceKey - abapKey, err := ReadServiceKeyAbapEnvironment(cfconfig, true) + cf := CFUtils{Exec: &command.Command{}} + abapKey, err := cf.ReadServiceKeyAbapEnvironment(cfconfig, true) assert.Equal(t, "", abapKey.Abap.Password) assert.Equal(t, "", abapKey.Abap.Username) assert.Equal(t, "", abapKey.Abap.CommunicationArrangementID) diff --git a/pkg/cloudfoundry/Services.go b/pkg/cloudfoundry/Services.go index 1edebaf1a..c15037bc8 100644 --- a/pkg/cloudfoundry/Services.go +++ b/pkg/cloudfoundry/Services.go @@ -5,14 +5,21 @@ import ( "encoding/json" "errors" "fmt" - "strings" - + "github.com/SAP/jenkins-library/pkg/command" "github.com/SAP/jenkins-library/pkg/log" + "strings" ) //ReadServiceKeyAbapEnvironment from Cloud Foundry and returns it. //Depending on user/developer requirements if he wants to perform further Cloud Foundry actions the cfLogoutOption parameters gives the option to logout after reading ABAP communication arrangement or not. -func ReadServiceKeyAbapEnvironment(options ServiceKeyOptions, cfLogoutOption bool) (ServiceKey, error) { +func (cf *CFUtils) ReadServiceKeyAbapEnvironment(options ServiceKeyOptions, cfLogoutOption bool) (ServiceKey, error) { + + _c := cf.Exec + + if _c == nil { + _c = &command.Command{} + } + var abapServiceKey ServiceKey var err error @@ -25,16 +32,16 @@ func ReadServiceKeyAbapEnvironment(options ServiceKeyOptions, cfLogoutOption boo Password: options.Password, } - err = Login(config) + err = cf.Login(config) var serviceKeyBytes bytes.Buffer - c.Stdout(&serviceKeyBytes) + _c.Stdout(&serviceKeyBytes) if err == nil { //Reading Service Key log.Entry().WithField("cfServiceInstance", options.CfServiceInstance).WithField("cfServiceKey", options.CfServiceKey).Info("Read service key for service instance") cfReadServiceKeyScript := []string{"service-key", options.CfServiceInstance, options.CfServiceKey} - err = c.RunExecutable("cf", cfReadServiceKeyScript...) + err = _c.RunExecutable("cf", cfReadServiceKeyScript...) } if err == nil { var serviceKeyJSON string @@ -54,7 +61,7 @@ func ReadServiceKeyAbapEnvironment(options ServiceKeyOptions, cfLogoutOption boo if err != nil { if cfLogoutOption == true { var logoutErr error - logoutErr = Logout() + logoutErr = cf.Logout() if logoutErr != nil { return abapServiceKey, fmt.Errorf("Failed to Logout of Cloud Foundry: %w", err) } @@ -65,7 +72,7 @@ func ReadServiceKeyAbapEnvironment(options ServiceKeyOptions, cfLogoutOption boo //Logging out of CF if cfLogoutOption == true { var logoutErr error - logoutErr = Logout() + logoutErr = cf.Logout() if logoutErr != nil { return abapServiceKey, fmt.Errorf("Failed to Logout of Cloud Foundry: %w", err) }