2019-12-05 15:22:38 +02:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2020-02-27 13:11:22 +02:00
|
|
|
"github.com/SAP/jenkins-library/pkg/mock"
|
2019-12-05 15:22:38 +02:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2020-02-25 15:33:34 +02:00
|
|
|
"os"
|
2019-12-05 15:22:38 +02:00
|
|
|
"sync"
|
|
|
|
"testing"
|
|
|
|
)
|
|
|
|
|
2020-02-25 15:33:34 +02:00
|
|
|
type FileUtilsMock struct {
|
|
|
|
copiedFiles []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FileUtilsMock) FileExists(path string) (bool, error) {
|
|
|
|
return path == "dummy.mtar" || path == ".xs_session", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FileUtilsMock) Copy(src, dest string) (int64, error) {
|
|
|
|
f.copiedFiles = append(f.copiedFiles, fmt.Sprintf("%s->%s", src, dest))
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FileUtilsMock) FileRead(path string) ([]byte, error) {
|
|
|
|
return []byte{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FileUtilsMock) FileWrite(path string, content []byte, perm os.FileMode) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FileUtilsMock) MkdirAll(path string, perm os.FileMode) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-12-05 15:22:38 +02:00
|
|
|
func TestDeploy(t *testing.T) {
|
|
|
|
myXsDeployOptions := xsDeployOptions{
|
|
|
|
APIURL: "https://example.org:12345",
|
|
|
|
User: "me",
|
|
|
|
Password: "secretPassword",
|
|
|
|
Org: "myOrg",
|
|
|
|
Space: "mySpace",
|
|
|
|
LoginOpts: "--skip-ssl-validation",
|
|
|
|
DeployOpts: "--dummy-deploy-opts",
|
|
|
|
XsSessionFile: ".xs_session",
|
|
|
|
Mode: "DEPLOY",
|
|
|
|
Action: "NONE",
|
|
|
|
MtaPath: "dummy.mtar",
|
|
|
|
OperationIDLogPattern: `^.*xs bg-deploy -i (.*) -a.*$`,
|
|
|
|
}
|
|
|
|
|
2020-02-27 13:11:22 +02:00
|
|
|
s := mock.ShellMockRunner{}
|
2019-12-05 15:22:38 +02:00
|
|
|
|
|
|
|
var removedFiles []string
|
|
|
|
|
2020-02-28 12:01:09 +02:00
|
|
|
cpeOut := xsDeployCommonPipelineEnvironment{}
|
2020-02-25 15:33:34 +02:00
|
|
|
fileUtilsMock := FileUtilsMock{}
|
2019-12-05 15:22:38 +02:00
|
|
|
|
|
|
|
fRemove := func(path string) error {
|
|
|
|
removedFiles = append(removedFiles, path)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var stdout string
|
|
|
|
|
|
|
|
t.Run("Standard deploy succeeds", func(t *testing.T) {
|
|
|
|
|
|
|
|
defer func() {
|
2020-02-25 15:33:34 +02:00
|
|
|
fileUtilsMock.copiedFiles = nil
|
2019-12-05 15:22:38 +02:00
|
|
|
removedFiles = nil
|
2020-02-27 13:11:22 +02:00
|
|
|
s.Calls = nil
|
2019-12-05 15:22:38 +02:00
|
|
|
stdout = ""
|
|
|
|
}()
|
|
|
|
|
|
|
|
rStdout, wStdout := io.Pipe()
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(1)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
io.Copy(buf, rStdout)
|
|
|
|
stdout = buf.String()
|
|
|
|
wg.Done()
|
|
|
|
}()
|
|
|
|
|
2020-02-28 12:01:09 +02:00
|
|
|
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, wStdout)
|
2019-12-05 15:22:38 +02:00
|
|
|
|
|
|
|
wStdout.Close()
|
|
|
|
wg.Wait()
|
|
|
|
|
2020-02-28 12:20:17 +02:00
|
|
|
assert.NoError(t, e)
|
2019-12-05 15:22:38 +02:00
|
|
|
|
|
|
|
t.Run("Standard checks", func(t *testing.T) {
|
|
|
|
// Contains --> we do not check for the shebang
|
2020-02-27 13:11:22 +02:00
|
|
|
assert.Contains(t, s.Calls[0], "xs login -a https://example.org:12345 -u me -p 'secretPassword' -o myOrg -s mySpace --skip-ssl-validation")
|
|
|
|
assert.Contains(t, s.Calls[1], "xs deploy dummy.mtar --dummy-deploy-opts")
|
|
|
|
assert.Contains(t, s.Calls[2], "xs logout")
|
|
|
|
assert.Len(t, s.Calls, 3)
|
2019-12-05 15:22:38 +02:00
|
|
|
|
|
|
|
// xs session file needs to be removed at end during a normal deployment
|
|
|
|
assert.Len(t, removedFiles, 1)
|
|
|
|
assert.Contains(t, removedFiles, ".xs_session")
|
|
|
|
|
2020-02-25 15:33:34 +02:00
|
|
|
assert.Len(t, fileUtilsMock.copiedFiles, 2)
|
2019-12-05 15:22:38 +02:00
|
|
|
// We copy the xs session file to the workspace in order to be able to use the file later.
|
|
|
|
// This happens directly after login
|
|
|
|
// We copy the xs session file from the workspace to the home folder in order to be able to
|
|
|
|
// use that file. This is important in case we rely on a login which happend e
|
2020-02-25 15:33:34 +02:00
|
|
|
assert.Contains(t, fileUtilsMock.copiedFiles[0], "/.xs_session->.xs_session")
|
|
|
|
assert.Contains(t, fileUtilsMock.copiedFiles[1], ".xs_session->")
|
|
|
|
assert.Contains(t, fileUtilsMock.copiedFiles[1], "/.xs_session")
|
2019-12-05 15:22:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Password not exposed", func(t *testing.T) {
|
|
|
|
assert.NotEmpty(t, stdout)
|
|
|
|
assert.NotContains(t, stdout, myXsDeployOptions.Password)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Standard deploy fails, deployable missing", func(t *testing.T) {
|
|
|
|
|
|
|
|
defer func() {
|
2020-02-25 15:33:34 +02:00
|
|
|
fileUtilsMock.copiedFiles = nil
|
2019-12-05 15:22:38 +02:00
|
|
|
removedFiles = nil
|
2020-02-27 13:11:22 +02:00
|
|
|
s.Calls = nil
|
2019-12-05 15:22:38 +02:00
|
|
|
}()
|
|
|
|
|
|
|
|
oldMtaPath := myXsDeployOptions.MtaPath
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
myXsDeployOptions.MtaPath = oldMtaPath
|
|
|
|
}()
|
|
|
|
|
|
|
|
// this file is not denoted in the file exists mock
|
|
|
|
myXsDeployOptions.MtaPath = "doesNotExist"
|
|
|
|
|
2020-02-28 12:01:09 +02:00
|
|
|
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, ioutil.Discard)
|
2020-02-28 12:20:17 +02:00
|
|
|
assert.EqualError(t, e, "Deployable 'doesNotExist' does not exist")
|
2019-12-05 15:22:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Standard deploy fails, action provided", func(t *testing.T) {
|
|
|
|
|
|
|
|
defer func() {
|
2020-02-25 15:33:34 +02:00
|
|
|
fileUtilsMock.copiedFiles = nil
|
2019-12-05 15:22:38 +02:00
|
|
|
removedFiles = nil
|
2020-02-27 13:11:22 +02:00
|
|
|
s.Calls = nil
|
2019-12-05 15:22:38 +02:00
|
|
|
}()
|
|
|
|
|
|
|
|
myXsDeployOptions.Action = "RETRY"
|
|
|
|
defer func() {
|
|
|
|
myXsDeployOptions.Action = "NONE"
|
|
|
|
}()
|
|
|
|
|
2020-02-28 12:01:09 +02:00
|
|
|
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, ioutil.Discard)
|
2020-02-28 12:20:17 +02:00
|
|
|
assert.EqualError(t, e, "Cannot perform action 'RETRY' in mode 'DEPLOY'. Only action 'NONE' is allowed.")
|
2019-12-05 15:22:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Standard deploy fails, error from underlying process", func(t *testing.T) {
|
|
|
|
|
|
|
|
defer func() {
|
2020-02-25 15:33:34 +02:00
|
|
|
fileUtilsMock.copiedFiles = nil
|
2019-12-05 15:22:38 +02:00
|
|
|
removedFiles = nil
|
2020-02-27 13:11:22 +02:00
|
|
|
s.Calls = nil
|
|
|
|
s.ShouldFailOnCommand = nil
|
2019-12-05 15:22:38 +02:00
|
|
|
}()
|
|
|
|
|
2020-02-27 13:11:22 +02:00
|
|
|
s.ShouldFailOnCommand = map[string]error{"#!/bin/bash\nxs login -a https://example.org:12345 -u me -p 'secretPassword' -o myOrg -s mySpace --skip-ssl-validation\n": errors.New("Error from underlying process")}
|
2019-12-05 15:22:38 +02:00
|
|
|
|
2020-02-28 12:01:09 +02:00
|
|
|
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, ioutil.Discard)
|
2020-02-28 12:20:17 +02:00
|
|
|
assert.EqualError(t, e, "Error from underlying process")
|
2019-12-05 15:22:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("BG deploy succeeds", func(t *testing.T) {
|
|
|
|
|
|
|
|
defer func() {
|
2020-02-25 15:33:34 +02:00
|
|
|
fileUtilsMock.copiedFiles = nil
|
2019-12-05 15:22:38 +02:00
|
|
|
removedFiles = nil
|
2020-02-27 13:11:22 +02:00
|
|
|
s.Calls = nil
|
2020-02-28 10:40:22 +02:00
|
|
|
s.StdoutReturn = make(map[string]string)
|
2019-12-05 15:22:38 +02:00
|
|
|
}()
|
|
|
|
|
2020-02-28 10:40:22 +02:00
|
|
|
s.StdoutReturn = make(map[string]string)
|
|
|
|
s.StdoutReturn[".*xs bg-deploy.*"] = "Use \"xs bg-deploy -i 1234 -a resume\" to resume the process.\n"
|
|
|
|
|
2019-12-05 15:22:38 +02:00
|
|
|
oldMode := myXsDeployOptions.Mode
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
myXsDeployOptions.Mode = oldMode
|
|
|
|
}()
|
|
|
|
|
|
|
|
myXsDeployOptions.Mode = "BG_DEPLOY"
|
|
|
|
|
2020-02-28 12:01:09 +02:00
|
|
|
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, ioutil.Discard)
|
2019-12-05 15:22:38 +02:00
|
|
|
|
2020-02-28 12:20:17 +02:00
|
|
|
if assert.NoError(t, e) {
|
|
|
|
if assert.Len(t, s.Calls, 2) { // There are two entries --> no logout in this case.
|
|
|
|
assert.Contains(t, s.Calls[0], "xs login")
|
|
|
|
assert.Contains(t, s.Calls[1], "xs bg-deploy dummy.mtar --dummy-deploy-opts")
|
|
|
|
}
|
|
|
|
}
|
2019-12-05 15:22:38 +02:00
|
|
|
})
|
|
|
|
|
2020-02-28 10:40:22 +02:00
|
|
|
t.Run("BG deploy fails, missing operationID", func(t *testing.T) {
|
|
|
|
|
2020-02-28 12:01:09 +02:00
|
|
|
s.StdoutReturn = make(map[string]string)
|
|
|
|
s.StdoutReturn[".*bg_deploy.*"] = "There is no operationID ...\n"
|
2020-02-28 10:40:22 +02:00
|
|
|
defer func() {
|
|
|
|
fileUtilsMock.copiedFiles = nil
|
|
|
|
removedFiles = nil
|
|
|
|
s.Calls = nil
|
|
|
|
s.StdoutReturn = make(map[string]string)
|
|
|
|
}()
|
|
|
|
|
|
|
|
oldMode := myXsDeployOptions.Mode
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
myXsDeployOptions.Mode = oldMode
|
|
|
|
}()
|
|
|
|
|
|
|
|
myXsDeployOptions.Mode = "BG_DEPLOY"
|
|
|
|
|
2020-02-28 12:01:09 +02:00
|
|
|
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, ioutil.Discard)
|
2020-02-28 12:20:17 +02:00
|
|
|
assert.EqualError(t, e, "No operationID found")
|
2020-02-28 10:40:22 +02:00
|
|
|
})
|
|
|
|
|
2019-12-05 15:22:38 +02:00
|
|
|
t.Run("BG deploy abort succeeds", func(t *testing.T) {
|
|
|
|
|
|
|
|
defer func() {
|
2020-02-25 15:33:34 +02:00
|
|
|
fileUtilsMock.copiedFiles = nil
|
2019-12-05 15:22:38 +02:00
|
|
|
removedFiles = nil
|
2020-02-27 13:11:22 +02:00
|
|
|
s.Calls = nil
|
2019-12-05 15:22:38 +02:00
|
|
|
}()
|
|
|
|
|
|
|
|
oldMode := myXsDeployOptions.Mode
|
|
|
|
oldAction := myXsDeployOptions.Action
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
myXsDeployOptions.Mode = oldMode
|
|
|
|
myXsDeployOptions.Action = oldAction
|
|
|
|
myXsDeployOptions.OperationID = ""
|
|
|
|
}()
|
|
|
|
|
|
|
|
myXsDeployOptions.Mode = "BG_DEPLOY"
|
|
|
|
myXsDeployOptions.Action = "ABORT"
|
|
|
|
myXsDeployOptions.OperationID = "12345"
|
|
|
|
|
2020-02-28 12:01:09 +02:00
|
|
|
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, ioutil.Discard)
|
2019-12-05 15:22:38 +02:00
|
|
|
|
2020-02-28 12:20:17 +02:00
|
|
|
if assert.NoError(t, e) {
|
|
|
|
if assert.Len(t, s.Calls, 2) { // There is no login --> we have two calls
|
|
|
|
assert.Contains(t, s.Calls[0], "xs bg-deploy -i 12345 -a abort")
|
|
|
|
assert.Contains(t, s.Calls[1], "xs logout")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2019-12-05 15:22:38 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("BG deploy abort fails due to missing operationId", func(t *testing.T) {
|
|
|
|
|
|
|
|
defer func() {
|
2020-02-25 15:33:34 +02:00
|
|
|
fileUtilsMock.copiedFiles = nil
|
2019-12-05 15:22:38 +02:00
|
|
|
removedFiles = nil
|
2020-02-27 13:11:22 +02:00
|
|
|
s.Calls = nil
|
2019-12-05 15:22:38 +02:00
|
|
|
}()
|
|
|
|
|
|
|
|
oldMode := myXsDeployOptions.Mode
|
|
|
|
oldAction := myXsDeployOptions.Action
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
myXsDeployOptions.Mode = oldMode
|
|
|
|
myXsDeployOptions.Action = oldAction
|
|
|
|
}()
|
|
|
|
|
|
|
|
myXsDeployOptions.Mode = "BG_DEPLOY"
|
|
|
|
myXsDeployOptions.Action = "ABORT"
|
|
|
|
|
2020-02-28 12:01:09 +02:00
|
|
|
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, ioutil.Discard)
|
2020-02-28 12:20:17 +02:00
|
|
|
assert.EqualError(t, e, "OperationID was not provided. This is required for action 'ABORT'.")
|
2019-12-05 15:22:38 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRetrieveOperationID(t *testing.T) {
|
|
|
|
operationID := retrieveOperationID(`
|
|
|
|
Uploading 1 files:
|
|
|
|
myFolder/dummy.mtar
|
|
|
|
File upload finished
|
|
|
|
|
|
|
|
Detected MTA schema version: "3.1.0"
|
|
|
|
Detected deploy target as "myOrg mySpace"
|
|
|
|
Detected deployed MTA with ID "my_mta" and version "0.0.1"
|
|
|
|
Deployed MTA color: blue
|
|
|
|
New MTA color: green
|
|
|
|
Detected new MTA version: "0.0.1"
|
|
|
|
Deployed MTA version: 0.0.1
|
|
|
|
Service "xxx" is not modified and will not be updated
|
|
|
|
Creating application "db-green" from MTA module "xx"...
|
|
|
|
Uploading application "xx-green"...
|
|
|
|
Staging application "xx-green"...
|
|
|
|
Application "xx-green" staged
|
|
|
|
Executing task "deploy" on application "xx-green"...
|
|
|
|
Task execution status: succeeded
|
|
|
|
Process has entered validation phase. After testing your new deployment you can resume or abort the process.
|
|
|
|
Use "xs bg-deploy -i 1234 -a resume" to resume the process.
|
|
|
|
Use "xs bg-deploy -i 1234 -a abort" to abort the process.
|
|
|
|
Hint: Use the '--no-confirm' option of the bg-deploy command to skip this phase.
|
|
|
|
`, `^.*xs bg-deploy -i (.*) -a.*$`)
|
|
|
|
|
|
|
|
assert.Equal(t, "1234", operationID)
|
|
|
|
}
|