github.com/SAP/jenkins-library@v1.362.0/cmd/xsDeploy_test.go (about) 1 //go:build unit 2 // +build unit 3 4 package cmd 5 6 import ( 7 "bytes" 8 "errors" 9 "fmt" 10 "io" 11 "sync" 12 "testing" 13 14 "github.com/SAP/jenkins-library/pkg/log" 15 "github.com/SAP/jenkins-library/pkg/mock" 16 "github.com/stretchr/testify/assert" 17 ) 18 19 type FileUtilsMock struct { 20 *mock.FilesMock 21 copiedFiles []string 22 } 23 24 func (f *FileUtilsMock) FileExists(path string) (bool, error) { 25 return path == "dummy.mtar" || path == ".xs_session", nil 26 } 27 28 func (f *FileUtilsMock) Copy(src, dest string) (int64, error) { 29 f.copiedFiles = append(f.copiedFiles, fmt.Sprintf("%s->%s", src, dest)) 30 return 0, nil 31 } 32 33 func TestDeploy(t *testing.T) { 34 myXsDeployOptions := xsDeployOptions{ 35 APIURL: "https://example.org:12345", 36 Username: "me", 37 Password: "secretPassword", 38 Org: "myOrg", 39 Space: "mySpace", 40 LoginOpts: "--skip-ssl-validation", 41 DeployOpts: "--dummy-deploy-opts", 42 XsSessionFile: ".xs_session", 43 Mode: "DEPLOY", 44 Action: "NONE", 45 MtaPath: "dummy.mtar", 46 OperationIDLogPattern: `^.*xs bg-deploy -i (.*) -a.*$`, 47 } 48 49 s := mock.ShellMockRunner{} 50 51 var removedFiles []string 52 53 cpeOut := xsDeployCommonPipelineEnvironment{} 54 fileUtilsMock := FileUtilsMock{} 55 56 fRemove := func(path string) error { 57 removedFiles = append(removedFiles, path) 58 return nil 59 } 60 61 var stdout string 62 63 t.Run("Standard deploy succeeds", func(t *testing.T) { 64 65 defer func() { 66 fileUtilsMock.copiedFiles = nil 67 removedFiles = nil 68 s.Calls = nil 69 stdout = "" 70 }() 71 72 rStdout, wStdout := io.Pipe() 73 74 var wg sync.WaitGroup 75 wg.Add(1) 76 77 go func() { 78 buf := new(bytes.Buffer) 79 if _, err := io.Copy(buf, rStdout); err != nil { 80 log.Entry().Warning("failed to copy buffer") 81 } 82 stdout = buf.String() 83 wg.Done() 84 }() 85 86 e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, wStdout) 87 88 wStdout.Close() 89 wg.Wait() 90 91 assert.NoError(t, e) 92 93 t.Run("Standard checks", func(t *testing.T) { 94 // Contains --> we do not check for the shebang 95 assert.Contains(t, s.Calls[0], "xs login -a https://example.org:12345 -u me -p 'secretPassword' -o myOrg -s mySpace --skip-ssl-validation") 96 assert.Contains(t, s.Calls[1], "xs deploy dummy.mtar --dummy-deploy-opts") 97 assert.Contains(t, s.Calls[2], "xs logout") 98 assert.Len(t, s.Calls, 3) 99 100 // xs session file needs to be removed at end during a normal deployment 101 assert.Len(t, removedFiles, 1) 102 assert.Contains(t, removedFiles, ".xs_session") 103 104 assert.Len(t, fileUtilsMock.copiedFiles, 2) 105 // We copy the xs session file to the workspace in order to be able to use the file later. 106 // This happens directly after login 107 // We copy the xs session file from the workspace to the home folder in order to be able to 108 // use that file. This is important in case we rely on a login which happened e 109 assert.Contains(t, fileUtilsMock.copiedFiles[0], "/.xs_session->.xs_session") 110 assert.Contains(t, fileUtilsMock.copiedFiles[1], ".xs_session->") 111 assert.Contains(t, fileUtilsMock.copiedFiles[1], "/.xs_session") 112 }) 113 114 t.Run("Password not exposed", func(t *testing.T) { 115 assert.NotEmpty(t, stdout) 116 assert.NotContains(t, stdout, myXsDeployOptions.Password) 117 }) 118 }) 119 120 t.Run("Standard deploy fails, deployable missing", func(t *testing.T) { 121 122 defer func() { 123 fileUtilsMock.copiedFiles = nil 124 removedFiles = nil 125 s.Calls = nil 126 }() 127 128 oldMtaPath := myXsDeployOptions.MtaPath 129 130 defer func() { 131 myXsDeployOptions.MtaPath = oldMtaPath 132 }() 133 134 // this file is not denoted in the file exists mock 135 myXsDeployOptions.MtaPath = "doesNotExist" 136 137 e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, io.Discard) 138 assert.EqualError(t, e, "Deployable 'doesNotExist' does not exist") 139 }) 140 141 t.Run("Standard deploy fails, action provided", func(t *testing.T) { 142 143 defer func() { 144 fileUtilsMock.copiedFiles = nil 145 removedFiles = nil 146 s.Calls = nil 147 }() 148 149 myXsDeployOptions.Action = "RETRY" 150 defer func() { 151 myXsDeployOptions.Action = "NONE" 152 }() 153 154 e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, io.Discard) 155 assert.EqualError(t, e, "Cannot perform action 'RETRY' in mode 'DEPLOY'. Only action 'NONE' is allowed.") 156 }) 157 158 t.Run("Standard deploy fails, error from underlying process", func(t *testing.T) { 159 160 defer func() { 161 fileUtilsMock.copiedFiles = nil 162 removedFiles = nil 163 s.Calls = nil 164 s.ShouldFailOnCommand = nil 165 }() 166 167 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")} 168 169 e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, io.Discard) 170 assert.EqualError(t, e, "Error from underlying process") 171 }) 172 173 t.Run("BG deploy succeeds", func(t *testing.T) { 174 175 defer func() { 176 fileUtilsMock.copiedFiles = nil 177 removedFiles = nil 178 s.Calls = nil 179 s.StdoutReturn = make(map[string]string) 180 }() 181 182 s.StdoutReturn = make(map[string]string) 183 s.StdoutReturn[".*xs bg-deploy.*"] = "Use \"xs bg-deploy -i 1234 -a resume\" to resume the process.\n" 184 185 oldMode := myXsDeployOptions.Mode 186 187 defer func() { 188 myXsDeployOptions.Mode = oldMode 189 }() 190 191 myXsDeployOptions.Mode = "BG_DEPLOY" 192 193 e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, io.Discard) 194 195 if assert.NoError(t, e) { 196 if assert.Len(t, s.Calls, 2) { // There are two entries --> no logout in this case. 197 assert.Contains(t, s.Calls[0], "xs login") 198 assert.Contains(t, s.Calls[1], "xs bg-deploy dummy.mtar --dummy-deploy-opts") 199 } 200 } 201 }) 202 203 t.Run("BG deploy fails, missing operationID", func(t *testing.T) { 204 205 s.StdoutReturn = make(map[string]string) 206 s.StdoutReturn[".*bg_deploy.*"] = "There is no operationID ...\n" 207 defer func() { 208 fileUtilsMock.copiedFiles = nil 209 removedFiles = nil 210 s.Calls = nil 211 s.StdoutReturn = make(map[string]string) 212 }() 213 214 oldMode := myXsDeployOptions.Mode 215 216 defer func() { 217 myXsDeployOptions.Mode = oldMode 218 }() 219 220 myXsDeployOptions.Mode = "BG_DEPLOY" 221 222 e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, io.Discard) 223 assert.EqualError(t, e, "No operationID found") 224 }) 225 226 t.Run("BG deploy abort succeeds", func(t *testing.T) { 227 228 defer func() { 229 fileUtilsMock.copiedFiles = nil 230 removedFiles = nil 231 s.Calls = nil 232 }() 233 234 oldMode := myXsDeployOptions.Mode 235 oldAction := myXsDeployOptions.Action 236 237 defer func() { 238 myXsDeployOptions.Mode = oldMode 239 myXsDeployOptions.Action = oldAction 240 myXsDeployOptions.OperationID = "" 241 }() 242 243 myXsDeployOptions.Mode = "BG_DEPLOY" 244 myXsDeployOptions.Action = "ABORT" 245 myXsDeployOptions.OperationID = "12345" 246 247 e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, io.Discard) 248 249 if assert.NoError(t, e) { 250 if assert.Len(t, s.Calls, 2) { // There is no login --> we have two calls 251 assert.Contains(t, s.Calls[0], "xs bg-deploy -i 12345 -a abort") 252 assert.Contains(t, s.Calls[1], "xs logout") 253 } 254 255 } 256 }) 257 258 t.Run("BG deploy abort fails due to missing operationId", func(t *testing.T) { 259 260 defer func() { 261 fileUtilsMock.copiedFiles = nil 262 removedFiles = nil 263 s.Calls = nil 264 }() 265 266 oldMode := myXsDeployOptions.Mode 267 oldAction := myXsDeployOptions.Action 268 269 defer func() { 270 myXsDeployOptions.Mode = oldMode 271 myXsDeployOptions.Action = oldAction 272 }() 273 274 myXsDeployOptions.Mode = "BG_DEPLOY" 275 myXsDeployOptions.Action = "ABORT" 276 277 e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, io.Discard) 278 assert.EqualError(t, e, "OperationID was not provided. This is required for action 'ABORT'.") 279 }) 280 } 281 282 func TestRetrieveOperationID(t *testing.T) { 283 operationID := retrieveOperationID(` 284 Uploading 1 files: 285 myFolder/dummy.mtar 286 File upload finished 287 288 Detected MTA schema version: "3.1.0" 289 Detected deploy target as "myOrg mySpace" 290 Detected deployed MTA with ID "my_mta" and version "0.0.1" 291 Deployed MTA color: blue 292 New MTA color: green 293 Detected new MTA version: "0.0.1" 294 Deployed MTA version: 0.0.1 295 Service "xxx" is not modified and will not be updated 296 Creating application "db-green" from MTA module "xx"... 297 Uploading application "xx-green"... 298 Staging application "xx-green"... 299 Application "xx-green" staged 300 Executing task "deploy" on application "xx-green"... 301 Task execution status: succeeded 302 Process has entered validation phase. After testing your new deployment you can resume or abort the process. 303 Use "xs bg-deploy -i 1234 -a resume" to resume the process. 304 Use "xs bg-deploy -i 1234 -a abort" to abort the process. 305 Hint: Use the '--no-confirm' option of the bg-deploy command to skip this phase. 306 `, `^.*xs bg-deploy -i (.*) -a.*$`) 307 308 assert.Equal(t, "1234", operationID) 309 }