2020-07-31 12:38:00 +02:00
package cmd
import (
"fmt"
"github.com/SAP/jenkins-library/pkg/cloudfoundry"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/yaml"
"github.com/stretchr/testify/assert"
"os"
2021-03-03 23:58:29 +02:00
"path/filepath"
2020-07-31 12:38:00 +02:00
"testing"
"time"
)
type manifestMock struct {
manifestFileName string
apps [ ] map [ string ] interface { }
}
func ( m manifestMock ) GetAppName ( index int ) ( string , error ) {
val , err := m . GetApplicationProperty ( index , "name" )
if err != nil {
return "" , err
}
if v , ok := val . ( string ) ; ok {
return v , nil
}
return "" , fmt . Errorf ( "Cannot resolve application name" )
}
func ( m manifestMock ) ApplicationHasProperty ( index int , name string ) ( bool , error ) {
_ , exists := m . apps [ index ] [ name ]
return exists , nil
}
func ( m manifestMock ) GetApplicationProperty ( index int , name string ) ( interface { } , error ) {
return m . apps [ index ] [ name ] , nil
}
func ( m manifestMock ) GetFileName ( ) string {
return m . manifestFileName
}
func ( m manifestMock ) Transform ( ) error {
return nil
}
func ( m manifestMock ) IsModified ( ) bool {
return false
}
func ( m manifestMock ) GetApplications ( ) ( [ ] map [ string ] interface { } , error ) {
return m . apps , nil
}
func ( m manifestMock ) WriteManifest ( ) error {
return nil
}
func TestCfDeployment ( t * testing . T ) {
defer func ( ) {
fileUtils = & piperutils . Files { }
_replaceVariables = yaml . Substitute
} ( )
filesMock := mock . FilesMock { }
filesMock . AddDir ( "/home/me" )
filesMock . Chdir ( "/home/me" )
fileUtils = & filesMock
// everything below in the config map annotated with '//default' is a default in the metadata
// since we don't get injected these values during the tests we set it here.
defaultConfig := cloudFoundryDeployOptions {
Org : "myOrg" ,
Space : "mySpace" ,
Username : "me" ,
Password : "******" ,
APIEndpoint : "https://examples.sap.com/cf" ,
SmokeTestStatusCode : 200 , // default
Manifest : "manifest.yml" , //default
MtaDeployParameters : "-f" , // default
DeployType : "standard" , // default
}
config := defaultConfig
successfulLogin := cloudfoundry . LoginOptions {
CfAPIEndpoint : "https://examples.sap.com/cf" ,
CfOrg : "myOrg" ,
CfSpace : "mySpace" ,
Username : "me" ,
Password : "******" ,
CfLoginOpts : [ ] string { } ,
}
var loginOpts cloudfoundry . LoginOptions
var logoutCalled bool
noopCfAPICalls := func ( t * testing . T , s mock . ExecMockRunner ) {
assert . Empty ( t , s . Calls ) // --> in case of an invalid deploy tool there must be no cf api calls
assert . Empty ( t , loginOpts ) // no login options: login has not been called
assert . False ( t , logoutCalled )
}
prepareDefaultManifestMocking := func ( manifestName string , appNames [ ] string ) func ( ) {
filesMock . AddFile ( manifestName , [ ] byte ( "file content does not matter" ) )
apps := [ ] map [ string ] interface { } { }
for _ , appName := range appNames {
apps = append ( apps , map [ string ] interface { } { "name" : appName } )
}
_getManifest = func ( name string ) ( cloudfoundry . Manifest , error ) {
return manifestMock {
manifestFileName : manifestName ,
apps : apps ,
} , nil
}
return func ( ) {
filesMock . FileRemove ( manifestName ) // slightly mis-use since that is intended to be used by code under test, not test code
_getManifest = getManifest
}
}
withLoginAndLogout := func ( t * testing . T , asserts func ( t * testing . T ) ) {
assert . Equal ( t , successfulLogin , loginOpts )
asserts ( t )
assert . True ( t , logoutCalled )
}
cleanup := func ( ) {
loginOpts = cloudfoundry . LoginOptions { }
logoutCalled = false
config = defaultConfig
}
defer func ( ) {
_cfLogin = cfLogin
_cfLogout = cfLogout
} ( )
_cfLogin = func ( c command . ExecRunner , opts cloudfoundry . LoginOptions ) error {
loginOpts = opts
return nil
}
_cfLogout = func ( c command . ExecRunner ) error {
logoutCalled = true
return nil
}
_replaceVariables = func ( manifest string , replacements map [ string ] interface { } , replacementsFiles [ ] string ) ( bool , error ) {
return false , nil
}
t . Run ( "Test invalid appname" , func ( t * testing . T ) {
defer cleanup ( )
config . AppName = "a_z"
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
assert . EqualError ( t , err , "Your application name 'a_z' contains a '_' (underscore) which is not allowed, only letters, dashes and numbers can be used. Please change the name to fit this requirement(s). For more details please visit https://docs.cloudfoundry.org/devguide/deploy-apps/deploy-app.html#basic-settings." )
} )
t . Run ( "Manifest substitution" , func ( t * testing . T ) {
defer func ( ) {
cleanup ( )
_replaceVariables = func ( manifest string , replacements map [ string ] interface { } , replacementsFiles [ ] string ) ( bool , error ) {
return false , nil
}
} ( )
s := mock . ExecMockRunner { }
var manifestForSubstitution string
var replacements map [ string ] interface { }
var replacementFiles [ ] string
defer prepareDefaultManifestMocking ( "substitute-manifest.yml" , [ ] string { "testAppName" } ) ( )
config . DeployTool = "cf_native"
config . DeployType = "blue-green"
config . AppName = "myApp"
config . Manifest = "substitute-manifest.yml"
_replaceVariables = func ( manifest string , _replacements map [ string ] interface { } , _replacementsFiles [ ] string ) ( bool , error ) {
manifestForSubstitution = manifest
replacements = _replacements
replacementFiles = _replacementsFiles
return false , nil
}
t . Run ( "straight forward" , func ( t * testing . T ) {
defer func ( ) {
config . ManifestVariables = [ ] string { }
config . ManifestVariablesFiles = [ ] string { }
} ( )
config . ManifestVariables = [ ] string { "k1=v1" }
config . ManifestVariablesFiles = [ ] string { "myVars.yml" }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . NoError ( t , err ) {
assert . Equal ( t , "substitute-manifest.yml" , manifestForSubstitution )
assert . Equal ( t , map [ string ] interface { } { "k1" : "v1" } , replacements )
assert . Equal ( t , [ ] string { "myVars.yml" } , replacementFiles )
}
} )
t . Run ( "empty" , func ( t * testing . T ) {
defer func ( ) {
config . ManifestVariables = [ ] string { }
config . ManifestVariablesFiles = [ ] string { }
} ( )
config . ManifestVariables = [ ] string { }
config . ManifestVariablesFiles = [ ] string { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . NoError ( t , err ) {
assert . Equal ( t , "substitute-manifest.yml" , manifestForSubstitution )
assert . Equal ( t , map [ string ] interface { } { } , replacements )
assert . Equal ( t , [ ] string { } , replacementFiles )
}
} )
} )
t . Run ( "Invalid deploytool" , func ( t * testing . T ) {
defer cleanup ( )
s := mock . ExecMockRunner { }
config . DeployTool = "invalid"
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . NoError ( t , err ) {
noopCfAPICalls ( t , s )
}
} )
t . Run ( "deploytool cf native" , func ( t * testing . T ) {
defer cleanup ( )
defer prepareDefaultManifestMocking ( "manifest.yml" , [ ] string { "testAppName" } ) ( )
config . DeployTool = "cf_native"
config . CfHome = "/home/me1"
config . CfPluginHome = "/home/me2"
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . NoError ( t , err ) {
t . Run ( "check cf api calls" , func ( t * testing . T ) {
withLoginAndLogout ( t , func ( t * testing . T ) {
assert . Equal ( t , [ ] mock . ExecCall {
2021-02-05 09:24:41 +02:00
{ Exec : "cf" , Params : [ ] string { "version" } } ,
2020-09-24 08:58:53 +02:00
{ Exec : "cf" , Params : [ ] string { "plugins" } } ,
{ Exec : "cf" , Params : [ ] string { "push" , "-f" , "manifest.yml" } } ,
2020-07-31 12:38:00 +02:00
} , s . Calls )
} )
} )
t . Run ( "check environment variables" , func ( t * testing . T ) {
assert . Contains ( t , s . Env , "CF_HOME=/home/me1" )
assert . Contains ( t , s . Env , "CF_PLUGIN_HOME=/home/me2" )
assert . Contains ( t , s . Env , "STATUS_CODE=200" )
} )
}
} )
t . Run ( "influx reporting" , func ( t * testing . T ) {
defer cleanup ( )
s := mock . ExecMockRunner { }
defer func ( ) {
_now = time . Now
} ( )
_now = func ( ) time . Time {
2021-08-05 17:03:51 +02:00
// There was the big eclipse in Karlsruhe
2020-07-31 12:38:00 +02:00
return time . Date ( 1999 , time . August , 11 , 12 , 32 , 0 , 0 , time . UTC )
}
defer prepareDefaultManifestMocking ( "manifest.yml" , [ ] string { "testAppName" } ) ( )
config . DeployTool = "cf_native"
config . ArtifactVersion = "0.1.2"
2021-08-05 17:03:51 +02:00
config . CommitHash = "123456"
2020-07-31 12:38:00 +02:00
influxData := cloudFoundryDeployInflux { }
err := runCloudFoundryDeploy ( & config , nil , & influxData , & s )
if assert . NoError ( t , err ) {
expected := cloudFoundryDeployInflux { }
expected . deployment_data . fields . artifactURL = "n/a"
expected . deployment_data . fields . deployTime = "AUG 11 1999 12:32:00"
expected . deployment_data . fields . jobTrigger = "n/a"
2021-08-05 17:03:51 +02:00
expected . deployment_data . fields . commitHash = "123456"
2020-07-31 12:38:00 +02:00
expected . deployment_data . tags . artifactVersion = "0.1.2"
expected . deployment_data . tags . deployUser = "me"
expected . deployment_data . tags . deployResult = "SUCCESS"
expected . deployment_data . tags . cfAPIEndpoint = "https://examples.sap.com/cf"
expected . deployment_data . tags . cfOrg = "myOrg"
expected . deployment_data . tags . cfSpace = "mySpace"
assert . Equal ( t , expected , influxData )
}
} )
t . Run ( "deploy cf native with docker image and docker username" , func ( t * testing . T ) {
defer cleanup ( )
config . DeployTool = "cf_native"
config . DeployDockerImage = "repo/image:tag"
config . DockerUsername = "me"
config . AppName = "testAppName"
config . Manifest = ""
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . NoError ( t , err ) {
withLoginAndLogout ( t , func ( t * testing . T ) {
assert . Equal ( t , [ ] mock . ExecCall {
2021-02-05 09:24:41 +02:00
{ Exec : "cf" , Params : [ ] string { "version" } } ,
2020-09-24 08:58:53 +02:00
{ Exec : "cf" , Params : [ ] string { "plugins" } } ,
{ Exec : "cf" , Params : [ ] string { "push" ,
2020-07-31 12:38:00 +02:00
"testAppName" ,
"--docker-image" ,
"repo/image:tag" ,
"--docker-username" ,
"me" ,
} } ,
} , s . Calls )
} )
}
} )
t . Run ( "deploy_cf_native with manifest and docker credentials" , func ( t * testing . T ) {
defer cleanup ( )
// Docker image can be done via manifest.yml.
// if a private Docker registry is used, --docker-username and DOCKER_PASSWORD
// must be set; this is checked by this test
config . DeployTool = "cf_native"
config . DeployDockerImage = "repo/image:tag"
config . DockerUsername = "test_cf_docker"
config . DockerPassword = "********"
config . AppName = "testAppName"
config . Manifest = ""
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . NoError ( t , err ) {
t . Run ( "check shell calls" , func ( t * testing . T ) {
withLoginAndLogout ( t , func ( t * testing . T ) {
assert . Equal ( t , [ ] mock . ExecCall {
2021-02-05 09:24:41 +02:00
{ Exec : "cf" , Params : [ ] string { "version" } } ,
2020-09-24 08:58:53 +02:00
{ Exec : "cf" , Params : [ ] string { "plugins" } } ,
{ Exec : "cf" , Params : [ ] string { "push" ,
2020-07-31 12:38:00 +02:00
"testAppName" ,
"--docker-image" ,
"repo/image:tag" ,
"--docker-username" ,
"test_cf_docker" ,
} } ,
} , s . Calls )
} )
} )
t . Run ( "check environment variables" , func ( t * testing . T ) {
//REVISIT: in the corresponding groovy test we checked for "${'********'}"
// I don't understand why, but we should discuss ...
assert . Contains ( t , s . Env , "CF_DOCKER_PASSWORD=********" )
} )
}
} )
t . Run ( "deploy cf native blue green with manifest and docker credentials" , func ( t * testing . T ) {
defer cleanup ( )
// Blue Green Deploy cf cli plugin does not support --docker-username and --docker-image parameters
// docker username and docker image have to be set in the manifest file
// if a private docker repository is used the CF_DOCKER_PASSWORD env variable must be set
config . DeployTool = "cf_native"
config . DeployType = "blue-green"
config . DockerUsername = "test_cf_docker"
config . DockerPassword = "********"
config . AppName = "testAppName"
defer prepareDefaultManifestMocking ( "manifest.yml" , [ ] string { "testAppName" } ) ( )
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . NoError ( t , err ) {
t . Run ( "check shell calls" , func ( t * testing . T ) {
withLoginAndLogout ( t , func ( t * testing . T ) {
assert . Equal ( t , [ ] mock . ExecCall {
2021-02-05 09:24:41 +02:00
{ Exec : "cf" , Params : [ ] string { "version" } } ,
2020-09-24 08:58:53 +02:00
{ Exec : "cf" , Params : [ ] string { "plugins" } } ,
{ Exec : "cf" , Params : [ ] string {
2020-07-31 12:38:00 +02:00
"blue-green-deploy" ,
"testAppName" ,
"--delete-old-apps" ,
"-f" ,
"manifest.yml" ,
} } ,
} , s . Calls )
} )
} )
t . Run ( "check environment variables" , func ( t * testing . T ) {
//REVISIT: in the corresponding groovy test we checked for "${'********'}"
// I don't understand why, but we should discuss ...
assert . Contains ( t , s . Env , "CF_DOCKER_PASSWORD=********" )
} )
}
} )
t . Run ( "deploy cf native app name from manifest" , func ( t * testing . T ) {
defer cleanup ( )
config . DeployTool = "cf_native"
config . Manifest = "test-manifest.yml"
// app name is not asserted since it does not appear in the cf calls
// but it is checked that an app name is present, hence we need it here.
defer prepareDefaultManifestMocking ( "test-manifest.yml" , [ ] string { "dummyApp" } ) ( )
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . NoError ( t , err ) {
t . Run ( "check shell calls" , func ( t * testing . T ) {
withLoginAndLogout ( t , func ( t * testing . T ) {
assert . Equal ( t , [ ] mock . ExecCall {
2021-02-05 09:24:41 +02:00
{ Exec : "cf" , Params : [ ] string { "version" } } ,
2020-09-24 08:58:53 +02:00
{ Exec : "cf" , Params : [ ] string { "plugins" } } ,
{ Exec : "cf" , Params : [ ] string {
2020-07-31 12:38:00 +02:00
"push" ,
"-f" ,
"test-manifest.yml" ,
} } ,
} , s . Calls )
} )
} )
}
} )
t . Run ( "deploy cf native without app name" , func ( t * testing . T ) {
defer cleanup ( )
config . DeployTool = "cf_native"
config . Manifest = "test-manifest.yml"
// Here we don't provide an application name from the mock. To make that
// more explicit we provide the empty string default explicitly.
defer prepareDefaultManifestMocking ( "test-manifest.yml" , [ ] string { "" } ) ( )
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . EqualError ( t , err , "appName from manifest 'test-manifest.yml' is empty" ) {
t . Run ( "check shell calls" , func ( t * testing . T ) {
noopCfAPICalls ( t , s )
} )
}
} )
// tests from groovy checking for keep old instances are already contained above. Search for '--delete-old-apps'
t . Run ( "deploy cf native blue green keep old instance" , func ( t * testing . T ) {
defer cleanup ( )
config . DeployTool = "cf_native"
config . DeployType = "blue-green"
config . Manifest = "test-manifest.yml"
config . AppName = "myTestApp"
config . KeepOldInstance = true
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . NoError ( t , err ) {
t . Run ( "check shell calls" , func ( t * testing . T ) {
withLoginAndLogout ( t , func ( t * testing . T ) {
assert . Equal ( t , [ ] mock . ExecCall {
2021-02-05 09:24:41 +02:00
{ Exec : "cf" , Params : [ ] string { "version" } } ,
2020-09-24 08:58:53 +02:00
{ Exec : "cf" , Params : [ ] string { "plugins" } } ,
{ Exec : "cf" , Params : [ ] string {
2020-07-31 12:38:00 +02:00
"blue-green-deploy" ,
"myTestApp" ,
"-f" ,
"test-manifest.yml" ,
} } ,
2020-09-24 08:58:53 +02:00
{ Exec : "cf" , Params : [ ] string {
2020-07-31 12:38:00 +02:00
"stop" ,
"myTestApp-old" ,
// MIGRATE FFROM GROOVY: in contrast to groovy there is not redirect of everything &> to a file since we
// read the stream directly now.
} } ,
} , s . Calls )
} )
} )
}
} )
t . Run ( "cf deploy blue green multiple applications" , func ( t * testing . T ) {
defer cleanup ( )
config . DeployTool = "cf_native"
config . DeployType = "blue-green"
config . Manifest = "test-manifest.yml"
config . AppName = "myTestApp"
defer prepareDefaultManifestMocking ( "test-manifest.yml" , [ ] string { "app1" , "app2" } ) ( )
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . EqualError ( t , err , "Your manifest contains more than one application. For blue green deployments your manifest file may contain only one application" ) {
t . Run ( "check shell calls" , func ( t * testing . T ) {
noopCfAPICalls ( t , s )
} )
}
} )
t . Run ( "cf native deploy blue green with no route" , func ( t * testing . T ) {
defer cleanup ( )
config . DeployTool = "cf_native"
config . DeployType = "blue-green"
config . Manifest = "test-manifest.yml"
config . AppName = "myTestApp"
defer func ( ) {
filesMock . FileRemove ( "test-manifest.yml" )
_getManifest = getManifest
} ( )
filesMock . AddFile ( "test-manifest.yml" , [ ] byte ( "Content does not matter" ) )
_getManifest = func ( name string ) ( cloudfoundry . Manifest , error ) {
return manifestMock {
manifestFileName : "test-manifest.yml" ,
apps : [ ] map [ string ] interface { } {
2020-09-24 08:58:53 +02:00
{
2020-07-31 12:38:00 +02:00
"name" : "app1" ,
"no-route" : true ,
} ,
} ,
} ,
nil
}
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . NoError ( t , err ) {
t . Run ( "check shell calls" , func ( t * testing . T ) {
withLoginAndLogout ( t , func ( t * testing . T ) {
assert . Equal ( t , [ ] mock . ExecCall {
2021-02-05 09:24:41 +02:00
{ Exec : "cf" , Params : [ ] string { "version" } } ,
2020-09-24 08:58:53 +02:00
{ Exec : "cf" , Params : [ ] string { "plugins" } } ,
{ Exec : "cf" , Params : [ ] string {
2020-07-31 12:38:00 +02:00
"push" ,
"myTestApp" ,
"-f" ,
"test-manifest.yml" ,
} } ,
} , s . Calls )
} )
} )
}
} )
t . Run ( "cf native deployment failure" , func ( t * testing . T ) {
defer cleanup ( )
config . DeployTool = "cf_native"
config . DeployType = "blue-green"
config . Manifest = "test-manifest.yml"
config . AppName = "myTestApp"
defer prepareDefaultManifestMocking ( "test-manifest.yml" , [ ] string { "app" } ) ( )
s := mock . ExecMockRunner { }
2021-02-05 09:24:41 +02:00
s . ShouldFailOnCommand = map [ string ] error { "cf.*deploy.*" : fmt . Errorf ( "cf deploy failed" ) }
2020-07-31 12:38:00 +02:00
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . EqualError ( t , err , "cf deploy failed" ) {
t . Run ( "check shell calls" , func ( t * testing . T ) {
// we should try to logout in this case
assert . True ( t , logoutCalled )
} )
}
} )
t . Run ( "cf native deployment failure when logging in" , func ( t * testing . T ) {
defer cleanup ( )
config . DeployTool = "cf_native"
config . DeployType = "blue-green"
config . Manifest = "test-manifest.yml"
config . AppName = "myTestApp"
defer func ( ) {
_cfLogin = func ( c command . ExecRunner , opts cloudfoundry . LoginOptions ) error {
loginOpts = opts
return nil
}
} ( )
_cfLogin = func ( c command . ExecRunner , opts cloudfoundry . LoginOptions ) error {
loginOpts = opts
return fmt . Errorf ( "Unable to login" )
}
defer prepareDefaultManifestMocking ( "test-manifest.yml" , [ ] string { "app1" } ) ( )
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . EqualError ( t , err , "Unable to login" ) {
t . Run ( "check shell calls" , func ( t * testing . T ) {
// no calls to the cf client in this case
2021-02-05 09:24:41 +02:00
assert . Equal ( t ,
[ ] mock . ExecCall {
{ Exec : "cf" , Params : [ ] string { "version" } } ,
} , s . Calls )
2020-07-31 12:38:00 +02:00
// no logout
assert . False ( t , logoutCalled )
} )
}
} )
// TODO testCfNativeBlueGreenKeepOldInstanceShouldThrowErrorOnStopError
t . Run ( "cf native deploy standard should not stop instance" , func ( t * testing . T ) {
defer cleanup ( )
config . DeployTool = "cf_native"
config . DeployType = "standard"
config . Manifest = "test-manifest.yml"
config . AppName = "myTestApp"
config . KeepOldInstance = true
defer prepareDefaultManifestMocking ( "test-manifest.yml" , [ ] string { "app" } ) ( )
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . NoError ( t , err ) {
t . Run ( "check shell calls" , func ( t * testing . T ) {
withLoginAndLogout ( t , func ( t * testing . T ) {
assert . Equal ( t , [ ] mock . ExecCall {
2021-02-05 09:24:41 +02:00
{ Exec : "cf" , Params : [ ] string { "version" } } ,
2020-09-24 08:58:53 +02:00
{ Exec : "cf" , Params : [ ] string { "plugins" } } ,
{ Exec : "cf" , Params : [ ] string {
2020-07-31 12:38:00 +02:00
"push" ,
"myTestApp" ,
"-f" ,
"test-manifest.yml" ,
} } ,
//
// There is no cf stop
//
} , s . Calls )
} )
} )
}
} )
t . Run ( "testCfNativeWithoutAppNameBlueGreen" , func ( t * testing . T ) {
defer cleanup ( )
config . DeployTool = "cf_native"
config . DeployType = "blue-green"
config . Manifest = "test-manifest.yml"
defer func ( ) {
filesMock . FileRemove ( "test-manifest.yml" )
_getManifest = getManifest
} ( )
filesMock . AddFile ( "test-manifest.yml" , [ ] byte ( "The content does not matter" ) )
_getManifest = func ( name string ) ( cloudfoundry . Manifest , error ) {
return manifestMock {
manifestFileName : "test-manifest.yml" ,
apps : [ ] map [ string ] interface { } {
2020-09-24 08:58:53 +02:00
{
2020-07-31 12:38:00 +02:00
"there-is" : "no-app-name" ,
} ,
} ,
} ,
nil
}
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . EqualError ( t , err , "Blue-green plugin requires app name to be passed (see https://github.com/bluemixgaragelondon/cf-blue-green-deploy/issues/27)" ) {
t . Run ( "check shell calls" , func ( t * testing . T ) {
noopCfAPICalls ( t , s )
} )
}
} )
// TODO add test for testCfNativeFailureInShellCall
t . Run ( "deploytool mtaDeployPlugin blue green" , func ( t * testing . T ) {
defer cleanup ( )
config . DeployTool = "mtaDeployPlugin"
config . DeployType = "blue-green"
config . MtaPath = "target/test.mtar"
defer func ( ) {
filesMock . FileRemove ( "target/test.mtar" )
} ( )
filesMock . AddFile ( "target/test.mtar" , [ ] byte ( "content does not matter" ) )
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . NoError ( t , err ) {
t . Run ( "check shell calls" , func ( t * testing . T ) {
withLoginAndLogout ( t , func ( t * testing . T ) {
assert . Equal ( t , [ ] mock . ExecCall {
2021-02-05 09:24:41 +02:00
{ Exec : "cf" , Params : [ ] string { "version" } } ,
2020-09-24 08:58:53 +02:00
{ Exec : "cf" , Params : [ ] string { "plugins" } } ,
{ Exec : "cf" , Params : [ ] string {
2020-07-31 12:38:00 +02:00
"bg-deploy" ,
"target/test.mtar" ,
"-f" ,
"--no-confirm" ,
} } ,
//
// There is no cf stop
//
} , s . Calls )
} )
} )
}
} )
// TODO: add test for influx reporting (influx reporting is missing at the moment)
t . Run ( "cf push with variables from file and as list" , func ( t * testing . T ) {
defer cleanup ( )
config . DeployTool = "cf_native"
config . Manifest = "test-manifest.yml"
config . ManifestVariablesFiles = [ ] string { "vars.yaml" }
config . ManifestVariables = [ ] string { "appName=testApplicationFromVarsList" }
config . AppName = "testAppName"
defer func ( ) {
_getManifest = getManifest
2020-09-24 09:39:18 +02:00
_getVarsOptions = cloudfoundry . GetVarsOptions
_getVarsFileOptions = cloudfoundry . GetVarsFileOptions
2020-07-31 12:38:00 +02:00
} ( )
2020-09-24 09:39:18 +02:00
_getVarsOptions = func ( vars [ ] string ) ( [ ] string , error ) {
return [ ] string { "--var" , "appName=testApplicationFromVarsList" } , nil
}
_getVarsFileOptions = func ( varFiles [ ] string ) ( [ ] string , error ) {
return [ ] string { "--vars-file" , "vars.yaml" } , nil
}
2020-07-31 12:38:00 +02:00
filesMock . AddFile ( "test-manifest.yml" , [ ] byte ( "content does not matter" ) )
_getManifest = func ( name string ) ( cloudfoundry . Manifest , error ) {
return manifestMock {
manifestFileName : "test-manifest.yml" ,
apps : [ ] map [ string ] interface { } {
2020-09-24 08:58:53 +02:00
{
2020-07-31 12:38:00 +02:00
"name" : "myApp" ,
} ,
} ,
} ,
nil
}
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . NoError ( t , err ) {
t . Run ( "check shell calls" , func ( t * testing . T ) {
withLoginAndLogout ( t , func ( t * testing . T ) {
// Revisit: we don't verify a log message in case of a non existing vars file
assert . Equal ( t , [ ] mock . ExecCall {
2021-02-05 09:24:41 +02:00
{ Exec : "cf" , Params : [ ] string { "version" } } ,
2020-09-24 08:58:53 +02:00
{ Exec : "cf" , Params : [ ] string { "plugins" } } ,
{ Exec : "cf" , Params : [ ] string {
2020-07-31 12:38:00 +02:00
"push" ,
"testAppName" ,
"--var" ,
"appName=testApplicationFromVarsList" ,
"--vars-file" ,
"vars.yaml" ,
"-f" ,
"test-manifest.yml" ,
} } ,
} , s . Calls )
} )
} )
}
} )
t . Run ( "cf push with variables from file which does not exist" , func ( t * testing . T ) {
defer cleanup ( )
config . DeployTool = "cf_native"
config . Manifest = "test-manifest.yml"
config . ManifestVariablesFiles = [ ] string { "vars.yaml" , "vars-does-not-exist.yaml" }
config . AppName = "testAppName"
defer func ( ) {
filesMock . FileRemove ( "test-manifest.yml" )
filesMock . FileRemove ( "vars.yaml" )
_getManifest = getManifest
2020-09-24 09:39:18 +02:00
_getVarsOptions = cloudfoundry . GetVarsOptions
_getVarsFileOptions = cloudfoundry . GetVarsFileOptions
2020-07-31 12:38:00 +02:00
} ( )
filesMock . AddFile ( "test-manifest.yml" , [ ] byte ( "content does not matter" ) )
_getManifest = func ( name string ) ( cloudfoundry . Manifest , error ) {
return manifestMock {
manifestFileName : "test-manifest.yml" ,
apps : [ ] map [ string ] interface { } {
2020-09-24 08:58:53 +02:00
{
2020-07-31 12:38:00 +02:00
"name" : "myApp" ,
} ,
} ,
} ,
nil
}
s := mock . ExecMockRunner { }
2020-09-24 09:39:18 +02:00
var receivedVarOptions [ ] string
var receivedVarsFileOptions [ ] string
_getVarsOptions = func ( vars [ ] string ) ( [ ] string , error ) {
receivedVarOptions = vars
return [ ] string { } , nil
}
_getVarsFileOptions = func ( varFiles [ ] string ) ( [ ] string , error ) {
receivedVarsFileOptions = varFiles
return [ ] string { "--vars-file" , "vars.yaml" } , nil
}
2020-07-31 12:38:00 +02:00
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . NoError ( t , err ) {
2020-09-24 09:39:18 +02:00
t . Run ( "check received vars options" , func ( t * testing . T ) {
assert . Empty ( t , receivedVarOptions )
} )
t . Run ( "check received vars file options" , func ( t * testing . T ) {
assert . Equal ( t , [ ] string { "vars.yaml" , "vars-does-not-exist.yaml" } , receivedVarsFileOptions )
} )
2020-07-31 12:38:00 +02:00
t . Run ( "check shell calls" , func ( t * testing . T ) {
withLoginAndLogout ( t , func ( t * testing . T ) {
// Revisit: we don't verify a log message in case of a non existing vars file
assert . Equal ( t , [ ] mock . ExecCall {
2021-02-05 09:24:41 +02:00
{ Exec : "cf" , Params : [ ] string { "version" } } ,
2020-09-24 08:58:53 +02:00
{ Exec : "cf" , Params : [ ] string { "plugins" } } ,
{ Exec : "cf" , Params : [ ] string {
2020-07-31 12:38:00 +02:00
"push" ,
"testAppName" ,
"--vars-file" ,
"vars.yaml" ,
"-f" ,
"test-manifest.yml" ,
} } ,
} , s . Calls )
} )
} )
}
} )
// TODO: testCfPushDeploymentWithoutVariableSubstitution is already handled above (?)
// TODO: testCfBlueGreenDeploymentWithVariableSubstitution variable substitution is not handled at the moment (pr pending).
// but anyway we should not test the full cycle here, but only that the variables substitution tool is called in the appropriate way.
// variable substitution should be tested at the variables substitution tool itself (yaml util)
t . Run ( "deploytool mtaDeployPlugin" , func ( t * testing . T ) {
defer cleanup ( )
config . DeployTool = "mtaDeployPlugin"
config . MtaDeployParameters = "-f"
t . Run ( "mta config file from project sources" , func ( t * testing . T ) {
defer filesMock . FileRemove ( "xyz.mtar" )
// The mock is inaccurat here.
// AddFile() adds the file absolute, prefix with the current working directory
// Glob() returns the absolute path - but without leading slash - , whereas
// the real Glob returns the path relative to the current workdir.
2020-09-24 07:41:06 +02:00
// In order to mimic the behavior in the free wild we add the mtar at the root dir.
2020-07-31 12:38:00 +02:00
filesMock . AddDir ( "/" )
assert . NoError ( t , filesMock . Chdir ( "/" ) )
filesMock . AddFile ( "xyz.mtar" , [ ] byte ( "content does not matter" ) )
// restor the expected working dir.
assert . NoError ( t , filesMock . Chdir ( "/home/me" ) )
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
if assert . NoError ( t , err ) {
withLoginAndLogout ( t , func ( t * testing . T ) {
assert . Equal ( t , s . Calls , [ ] mock . ExecCall {
2021-02-05 09:24:41 +02:00
{ Exec : "cf" , Params : [ ] string { "version" } } ,
2020-09-24 08:58:53 +02:00
{ Exec : "cf" , Params : [ ] string { "plugins" } } ,
{ Exec : "cf" , Params : [ ] string { "deploy" , "xyz.mtar" , "-f" } } } )
2020-07-31 12:38:00 +02:00
} )
}
} )
t . Run ( "mta config file from project config does not exist" , func ( t * testing . T ) {
defer func ( ) { config . MtaPath = "" } ( )
config . MtaPath = "my.mtar"
s := mock . ExecMockRunner { }
err := runCloudFoundryDeploy ( & config , nil , nil , & s )
assert . EqualError ( t , err , "mtar file 'my.mtar' retrieved from configuration does not exist" )
} )
// TODO: add test for mtar file from project config which does exist in project sources
} )
}
2020-08-24 18:10:45 +02:00
func TestValidateDeployTool ( t * testing . T ) {
testCases := [ ] struct {
runName string
deployToolGiven string
buildTool string
deployToolExpected string
} {
{ "no params" , "" , "" , "" } ,
{ "build tool MTA" , "" , "mta" , "mtaDeployPlugin" } ,
{ "build tool other" , "" , "other" , "cf_native" } ,
{ "deploy and build tool given" , "given" , "unknown" , "given" } ,
{ "only deploy tool given" , "given" , "" , "given" } ,
}
t . Parallel ( )
for _ , test := range testCases {
t . Run ( test . runName , func ( t * testing . T ) {
config := cloudFoundryDeployOptions { BuildTool : test . buildTool , DeployTool : test . deployToolGiven }
validateDeployTool ( & config )
assert . Equal ( t , test . deployToolExpected , config . DeployTool ,
"expected different deployTool result" )
} )
}
}
2020-07-31 12:38:00 +02:00
func TestMtarLookup ( t * testing . T ) {
defer func ( ) {
fileUtils = piperutils . Files { }
} ( )
filesMock := mock . FilesMock { }
fileUtils = & filesMock
t . Run ( "One MTAR" , func ( t * testing . T ) {
defer filesMock . FileRemove ( "x.mtar" )
filesMock . AddFile ( "x.mtar" , [ ] byte ( "content does not matter" ) )
path , err := findMtar ( )
if assert . NoError ( t , err ) {
assert . Equal ( t , "x.mtar" , path )
}
} )
t . Run ( "No MTAR" , func ( t * testing . T ) {
// nothing needs to be configures. There is simply no
// mtar in the file system mock, so no mtar will be found.
_ , err := findMtar ( )
assert . EqualError ( t , err , "No mtar file matching pattern '**/*.mtar' found" )
} )
t . Run ( "Several MTARs" , func ( t * testing . T ) {
defer func ( ) {
filesMock . FileRemove ( "x.mtar" )
filesMock . FileRemove ( "y.mtar" )
} ( )
filesMock . AddFile ( "x.mtar" , [ ] byte ( "content does not matter" ) )
filesMock . AddFile ( "y.mtar" , [ ] byte ( "content does not matter" ) )
_ , err := findMtar ( )
assert . EqualError ( t , err , "Found multiple mtar files matching pattern '**/*.mtar' (x.mtar,y.mtar), please specify file via parameter 'mtarPath'" )
} )
}
func TestSmokeTestScriptHandling ( t * testing . T ) {
filesMock := mock . FilesMock { }
filesMock . AddDir ( "/home/me" )
filesMock . Chdir ( "/home/me" )
filesMock . AddFileWithMode ( "mySmokeTestScript.sh" , [ ] byte ( "Content does not matter" ) , 0644 )
fileUtils = & filesMock
var canExec os . FileMode = 0755
t . Run ( "non default existing smoke test file" , func ( t * testing . T ) {
parts , err := handleSmokeTestScript ( "mySmokeTestScript.sh" )
if assert . NoError ( t , err ) {
// when the none-default file name is provided the file must already exist
// in the project sources.
assert . False ( t , filesMock . HasWrittenFile ( "mySmokeTestScript.sh" ) )
info , e := filesMock . Stat ( "mySmokeTestScript.sh" )
if assert . NoError ( t , e ) {
assert . Equal ( t , canExec , info . Mode ( ) )
}
assert . Equal ( t , [ ] string {
"--smoke-test" ,
2021-03-03 23:58:29 +02:00
filepath . FromSlash ( "/home/me/mySmokeTestScript.sh" ) ,
2020-07-31 12:38:00 +02:00
} , parts )
}
} )
t . Run ( "non default not existing smoke test file" , func ( t * testing . T ) {
parts , err := handleSmokeTestScript ( "notExistingSmokeTestScript.sh" )
if assert . EqualError ( t , err , "failed to make smoke-test script executable: chmod: notExistingSmokeTestScript.sh: No such file or directory" ) {
assert . False ( t , filesMock . HasWrittenFile ( "notExistingSmokeTestScript.sh" ) )
assert . Equal ( t , [ ] string { } , parts )
}
} )
t . Run ( "default smoke test file" , func ( t * testing . T ) {
parts , err := handleSmokeTestScript ( "blueGreenCheckScript.sh" )
if assert . NoError ( t , err ) {
info , e := filesMock . Stat ( "blueGreenCheckScript.sh" )
if assert . NoError ( t , e ) {
assert . Equal ( t , canExec , info . Mode ( ) )
}
// in this case we provide the file. We overwrite in case there is already such a file ...
assert . True ( t , filesMock . HasWrittenFile ( "blueGreenCheckScript.sh" ) )
content , e := filesMock . FileRead ( "blueGreenCheckScript.sh" )
if assert . NoError ( t , e ) {
assert . Equal ( t , "#!/usr/bin/env bash\n# this is simply testing if the application root returns HTTP STATUS_CODE\ncurl -so /dev/null -w '%{response_code}' https://$1 | grep $STATUS_CODE" , string ( content ) )
}
assert . Equal ( t , [ ] string {
"--smoke-test" ,
2021-03-03 23:58:29 +02:00
filepath . FromSlash ( "/home/me/blueGreenCheckScript.sh" ) ,
2020-07-31 12:38:00 +02:00
} , parts )
}
} )
}
func TestDefaultManifestVariableFilesHandling ( t * testing . T ) {
filesMock := mock . FilesMock { }
filesMock . AddDir ( "/home/me" )
filesMock . Chdir ( "/home/me" )
fileUtils = & filesMock
t . Run ( "default manifest variable file is the only one and exists" , func ( t * testing . T ) {
defer func ( ) {
filesMock . FileRemove ( "manifest-variables.yml" )
} ( )
filesMock . AddFile ( "manifest-variables.yml" , [ ] byte ( "Content does not matter" ) )
manifestFiles , err := validateManifestVariablesFiles (
[ ] string {
"manifest-variables.yml" ,
} ,
)
if assert . NoError ( t , err ) {
assert . Equal ( t ,
[ ] string {
"manifest-variables.yml" ,
} , manifestFiles )
}
} )
t . Run ( "default manifest variable file is the only one and does not exist" , func ( t * testing . T ) {
manifestFiles , err := validateManifestVariablesFiles (
[ ] string {
"manifest-variables.yml" ,
} ,
)
if assert . NoError ( t , err ) {
assert . Equal ( t , [ ] string { } , manifestFiles )
}
} )
t . Run ( "default manifest variable file among others remains if it does not exist" , func ( t * testing . T ) {
// in this case we might fail later.
manifestFiles , err := validateManifestVariablesFiles (
[ ] string {
"manifest-variables.yml" ,
"a-second-file.yml" ,
} ,
)
if assert . NoError ( t , err ) {
// the order in which the files are returned is significant.
assert . Equal ( t , [ ] string {
"manifest-variables.yml" ,
"a-second-file.yml" ,
} , manifestFiles )
}
} )
}
func TestExtensionDescriptorsWithMinusE ( t * testing . T ) {
t . Run ( "ExtensionDescriptorsWithMinusE" , func ( t * testing . T ) {
2021-01-12 10:39:04 +02:00
extDesc , _ := handleMtaExtensionDescriptors ( "-e 1.yaml -e 2.yaml" )
2020-07-31 12:38:00 +02:00
assert . Equal ( t , [ ] string {
"-e" ,
"1.yaml" ,
"-e" ,
"2.yaml" ,
} , extDesc )
} )
t . Run ( "ExtensionDescriptorsFirstOneWithoutMinusE" , func ( t * testing . T ) {
2021-01-12 10:39:04 +02:00
extDesc , _ := handleMtaExtensionDescriptors ( "1.yaml -e 2.yaml" )
2020-07-31 12:38:00 +02:00
assert . Equal ( t , [ ] string {
"-e" ,
"1.yaml" ,
"-e" ,
"2.yaml" ,
} , extDesc )
} )
t . Run ( "NoExtensionDescriptors" , func ( t * testing . T ) {
2021-01-12 10:39:04 +02:00
extDesc , _ := handleMtaExtensionDescriptors ( "" )
2020-07-31 12:38:00 +02:00
assert . Equal ( t , [ ] string { } , extDesc )
} )
}
func TestAppNameChecks ( t * testing . T ) {
t . Run ( "appName with alpha-numeric chars should work" , func ( t * testing . T ) {
err := validateAppName ( "myValidAppName123" )
assert . NoError ( t , err )
} )
t . Run ( "appName with alpha-numeric chars and dash should work" , func ( t * testing . T ) {
err := validateAppName ( "my-Valid-AppName123" )
assert . NoError ( t , err )
} )
t . Run ( "empty appName should work" , func ( t * testing . T ) {
// we consider the empty string as valid appname since we only check app names handed over from outside
// in case there is no (real) app name provided from outside we might still find an appname in the metadata
// That app name in turn is not checked.
err := validateAppName ( "" )
assert . NoError ( t , err )
} )
t . Run ( "single char appName should work" , func ( t * testing . T ) {
err := validateAppName ( "a" )
assert . NoError ( t , err )
} )
t . Run ( "appName with alpha-numeric chars and trailing dash should throw an error" , func ( t * testing . T ) {
err := validateAppName ( "my-Invalid-AppName123-" )
assert . EqualError ( t , err , "Your application name 'my-Invalid-AppName123-' starts or ends with a '-' (dash) which is not allowed, only letters and numbers can be used. Please change the name to fit this requirement(s). For more details please visit https://docs.cloudfoundry.org/devguide/deploy-apps/deploy-app.html#basic-settings." )
} )
t . Run ( "appName with underscores should throw an error" , func ( t * testing . T ) {
err := validateAppName ( "my_invalid_app_name" )
assert . EqualError ( t , err , "Your application name 'my_invalid_app_name' contains a '_' (underscore) which is not allowed, only letters, dashes and numbers can be used. Please change the name to fit this requirement(s). For more details please visit https://docs.cloudfoundry.org/devguide/deploy-apps/deploy-app.html#basic-settings." )
} )
}
2021-01-12 10:39:04 +02:00
func TestMtaExtensionCredentials ( t * testing . T ) {
content := [ ] byte ( ` ' _schema - version : ' 3.1 '
ID : test . ext
extends : test
parameters
test - credentials1 : "<%= testCred1 %>"
test - credentials2 : "<%= testCred2 %>" ` )
filesMock := mock . FilesMock { }
filesMock . AddDir ( "/home/me" )
filesMock . Chdir ( "/home/me" )
filesMock . AddFile ( "mtaext1.mtaext" , content )
filesMock . AddFile ( "mtaext2.mtaext" , content )
filesMock . AddFile ( "mtaext3.mtaext" , content )
fileUtils = & filesMock
_environ = func ( ) [ ] string {
return [ ] string {
"MY_CRED_ENV_VAR1=******" ,
"MY_CRED_ENV_VAR2=++++++" ,
}
}
defer func ( ) {
fileUtils = piperutils . Files { }
_environ = os . Environ
} ( )
t . Run ( "extension file does not exist" , func ( t * testing . T ) {
err := handleMtaExtensionCredentials ( "mtaextDoesNotExist.mtaext" , map [ string ] interface { } { } )
assert . EqualError ( t , err , "Cannot handle credentials for mta extension file 'mtaextDoesNotExist.mtaext': could not read 'mtaextDoesNotExist.mtaext'" )
} )
t . Run ( "credential cannot be retrieved" , func ( t * testing . T ) {
err := handleMtaExtensionCredentials (
"mtaext1.mtaext" ,
map [ string ] interface { } {
"testCred1" : "myCredEnvVar1NotDefined" ,
"testCred2" : "myCredEnvVar2NotDefined" ,
} ,
)
assert . EqualError ( t , err , "Cannot handle mta extension credentials: No credentials found for '[myCredEnvVar1NotDefined myCredEnvVar2NotDefined]'/'[MY_CRED_ENV_VAR1_NOT_DEFINED MY_CRED_ENV_VAR2_NOT_DEFINED]'. Are these credentials maintained?" )
} )
t . Run ( "irrelevant credentials does not cause failures" , func ( t * testing . T ) {
err := handleMtaExtensionCredentials (
"mtaext2.mtaext" ,
map [ string ] interface { } {
"testCred1" : "myCredEnvVar1" ,
"testCred2" : "myCredEnvVar2" ,
"testCredNotUsed" : "myCredEnvVarWhichDoesNotExist" , //<-- This here is not used.
} ,
)
assert . NoError ( t , err )
} )
t . Run ( "replace straight forward" , func ( t * testing . T ) {
mtaFileName := "mtaext3.mtaext"
err := handleMtaExtensionCredentials (
mtaFileName ,
map [ string ] interface { } {
"testCred1" : "myCredEnvVar1" ,
"testCred2" : "myCredEnvVar2" ,
} ,
)
if assert . NoError ( t , err ) {
b , e := fileUtils . FileRead ( mtaFileName )
if e != nil {
assert . Fail ( t , "Cannot read mta extension file: %v" , e )
}
content := string ( b )
assert . Contains ( t , content , "test-credentials1: \"******\"" )
assert . Contains ( t , content , "test-credentials2: \"++++++\"" )
}
} )
}
func TestEnvVarKeyModification ( t * testing . T ) {
envVarCompatibleKey := toEnvVarKey ( "Mta.ExtensionCredential~Credential_Id1" )
assert . Equal ( t , "MTA_EXTENSION_CREDENTIAL_CREDENTIAL_ID1" , envVarCompatibleKey )
}