diff --git a/cmd/identify_ingress_test.go b/cmd/identify_ingress_test.go index fa42628c..c6be36ad 100644 --- a/cmd/identify_ingress_test.go +++ b/cmd/identify_ingress_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/uselagoon/build-deploy-tool/internal/dbaasclient" + "github.com/uselagoon/build-deploy-tool/internal/helpers" ) func TestIdentifyRoute(t *testing.T) { @@ -530,6 +531,9 @@ func TestIdentifyRoute(t *testing.T) { if string(retJSON) != tt.wantJSON { t.Errorf("returned autogen %v doesn't match want %v", string(retJSON), tt.wantJSON) } + t.Cleanup(func() { + helpers.UnsetEnvVars(nil) + }) }) } } diff --git a/cmd/identify_services.go b/cmd/identify_services.go new file mode 100644 index 00000000..1443bc21 --- /dev/null +++ b/cmd/identify_services.go @@ -0,0 +1,56 @@ +package cmd + +import ( + "encoding/json" + "fmt" + + "github.com/spf13/cobra" + generator "github.com/uselagoon/build-deploy-tool/internal/generator" + "github.com/uselagoon/build-deploy-tool/internal/helpers" +) + +type serviceIdentifyJSON struct { + Name string `json:"name"` + Type string `json:"type"` +} + +var servicesIdentify = &cobra.Command{ + Use: "services", + Aliases: []string{"s"}, + Short: "Identify services that this build would create", + RunE: func(cmd *cobra.Command, args []string) error { + generator, err := generatorInput(false) + if err != nil { + return err + } + ret, err := IdentifyServices(generator) + if err != nil { + return err + } + retJSON, _ := json.Marshal(ret) + fmt.Println(string(retJSON)) + return nil + }, +} + +// IdentifyServices identifies services that this build would create +func IdentifyServices(g generator.GeneratorInput) ([]string, error) { + lagoonBuild, err := generator.NewGenerator( + g, + ) + if err != nil { + return nil, err + } + + services := []string{} + for _, service := range lagoonBuild.BuildValues.Services { + if service.Type != "" { + services = helpers.AppendIfMissing(services, service.OverrideName) + } + } + return services, nil +} + +func init() { + identifyCmd.AddCommand(servicesIdentify) +} diff --git a/cmd/identify_services_test.go b/cmd/identify_services_test.go new file mode 100644 index 00000000..1c9bdd5b --- /dev/null +++ b/cmd/identify_services_test.go @@ -0,0 +1,175 @@ +package cmd + +import ( + "os" + "reflect" + "testing" + "time" + + "github.com/uselagoon/build-deploy-tool/internal/dbaasclient" + "github.com/uselagoon/build-deploy-tool/internal/helpers" +) + +func TestIdentifyServices(t *testing.T) { + type args struct { + alertContact string + statusPageID string + projectName string + environmentName string + branch string + prNumber string + prHeadBranch string + prBaseBranch string + environmentType string + buildType string + activeEnvironment string + standbyEnvironment string + cacheNoCache string + serviceID string + secretPrefix string + projectVars string + envVars string + lagoonVersion string + lagoonYAML string + valuesFilePath string + templatePath string + } + tests := []struct { + name string + args args + want []string + wantErr bool + }{ + { + name: "test1 check LAGOON_FASTLY_SERVICE_IDS with secret no values", + args: args{ + alertContact: "alertcontact", + statusPageID: "statuspageid", + projectName: "example-project", + environmentName: "main", + environmentType: "production", + buildType: "branch", + lagoonVersion: "v2.7.x", + branch: "main", + projectVars: `[{"name":"LAGOON_SYSTEM_ROUTER_PATTERN","value":"${service}-${project}-${environment}.example.com","scope":"internal_system"},{"name":"LAGOON_FASTLY_SERVICE_IDS","value":"example.com:service-id:true:annotationscom","scope":"build"}]`, + envVars: `[]`, + lagoonYAML: "../test-resources/identify-ingress/test1/lagoon.yml", + templatePath: "../test-resources/output", + }, + want: []string{"node"}, + }, + { + name: "test16 autogenerated routes where lagoon.name of service does not match service names", + args: args{ + alertContact: "alertcontact", + statusPageID: "statuspageid", + projectName: "content-example-com", + environmentName: "feature-migration", + environmentType: "development", + buildType: "branch", + lagoonVersion: "v2.7.x", + branch: "feature/migration", + projectVars: `[{"name":"LAGOON_SYSTEM_ROUTER_PATTERN","value":"${environment}.${project}.example.com","scope":"internal_system"}]`, + envVars: `[]`, + lagoonYAML: "../test-resources/identify-ingress/test16/lagoon.yml", + templatePath: "../test-resources/output", + }, + want: []string{"cli", "nginx-php", "mariadb", "redis"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // set the environment variables from args + err := os.Setenv("MONITORING_ALERTCONTACT", tt.args.alertContact) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("MONITORING_STATUSPAGEID", tt.args.statusPageID) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("PROJECT", tt.args.projectName) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("ENVIRONMENT", tt.args.environmentName) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("BRANCH", tt.args.branch) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("LAGOON_GIT_BRANCH", tt.args.branch) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("PR_NUMBER", tt.args.prNumber) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("PR_HEAD_BRANCH", tt.args.prHeadBranch) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("PR_BASE_BRANCH", tt.args.prBaseBranch) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("ENVIRONMENT_TYPE", tt.args.environmentType) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("BUILD_TYPE", tt.args.buildType) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("ACTIVE_ENVIRONMENT", tt.args.activeEnvironment) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("STANDBY_ENVIRONMENT", tt.args.standbyEnvironment) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("LAGOON_FASTLY_NOCACHE_SERVICE_ID", tt.args.cacheNoCache) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("LAGOON_PROJECT_VARIABLES", tt.args.projectVars) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("LAGOON_ENVIRONMENT_VARIABLES", tt.args.envVars) + if err != nil { + t.Errorf("%v", err) + } + err = os.Setenv("LAGOON_VERSION", tt.args.lagoonVersion) + if err != nil { + t.Errorf("%v", err) + } + generator, err := generatorInput(false) + if err != nil { + t.Errorf("%v", err) + } + generator.LagoonYAML = tt.args.lagoonYAML + // add dbaasclient overrides for tests + generator.DBaaSClient = dbaasclient.NewClient(dbaasclient.Client{ + RetryMax: 5, + RetryWaitMin: time.Duration(10) * time.Millisecond, + RetryWaitMax: time.Duration(50) * time.Millisecond, + }) + got, err := IdentifyServices(generator) + if (err != nil) != tt.wantErr { + t.Errorf("IdentifyServices() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("IdentifyServices() = %v, want %v", got, tt.want) + } + t.Cleanup(func() { + helpers.UnsetEnvVars(nil) + }) + }) + } +} diff --git a/internal/helpers/helpers.go b/internal/helpers/helpers.go index cd3b031a..aff1de4a 100644 --- a/internal/helpers/helpers.go +++ b/internal/helpers/helpers.go @@ -219,3 +219,12 @@ func DeepCopy(src, dist interface{}) (err error) { } return gob.NewDecoder(&buf).Decode(dist) } + +func AppendIfMissing(slice []string, i string) []string { + for _, ele := range slice { + if ele == i { + return slice + } + } + return append(slice, i) +} diff --git a/legacy/build-deploy-docker-compose.sh b/legacy/build-deploy-docker-compose.sh index 12170d70..e573f298 100755 --- a/legacy/build-deploy-docker-compose.sh +++ b/legacy/build-deploy-docker-compose.sh @@ -1757,6 +1757,84 @@ set +x currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" patchBuildStep "${buildStartTime}" "${previousStepEnd}" "${currentStepEnd}" "${NAMESPACE}" "deploymentApplyComplete" "Applying Deployments" "false" previousStepEnd=${currentStepEnd} +beginBuildStep "Service/Deployment Cleanup" "cleanupServices" + +############################################## +### CLEANUP services which have been removed from docker-compose.yaml +##############################################s + +set +x +# collect the current routes, its possible to exclude ingress by adding a label 'route.lagoon.sh/remove=false' and it won't get deleted +CURRENT_SERVICES=$(kubectl -n ${NAMESPACE} get deployments -l "lagoon.sh/service-type" -l "lagoon.sh/service" --no-headers | cut -d " " -f 1 | xargs) +# collect the routes that Lagoon thinks it should have based on the .lagoon.yml and any routes that have come from the api +# using the build-deploy-tool generator +SERVICES_TO_JSON=$(build-deploy-tool identify services | jq -r '.[]') + +MATCHED_SERVICE=false +DELETE_SERVICE=() +# loop over the routes from kubernetes +for EXIST_SERVICE in ${CURRENT_SERVICES}; do + # loop over the routes that Lagoon thinks it should have + for SERVICE in ${SERVICES_TO_JSON}; do + if [ "${EXIST_SERVICE}" == "${SERVICE}" ]; then + MATCHED_SERVICE=true + continue + fi + done + if [ "${MATCHED_SERVICE}" != "true" ]; then + DELETE_SERVICE+=($EXIST_SERVICE) + fi + MATCHED_SERVICE=false +done + +SERVICE_CLEANUP_WARNINGS="false" +if [ ${#DELETE_SERVICE[@]} -ne 0 ]; then + SERVICE_CLEANUP_WARNINGS="true" + ((++BUILD_WARNING_COUNT)) + echo ">> Lagoon detected services that have been removed from the docker-compose file" + if [ "$(featureFlag CLEANUP_REMOVED_LAGOON_SERVICES)" != enabled ]; then + echo "> You can remove these in the next build by setting the flag 'LAGOON_FEATURE_FLAG_CLEANUP_REMOVED_LAGOON_SERVICES=enabled' as a GLOBAL scoped variable to this environment or project" + fi + for DS in ${DELETE_SERVICE[@]} + do + if [ "$(featureFlag CLEANUP_REMOVED_LAGOON_SERVICES)" = enabled ]; then + echo ">> Removing deployment ${DS}" + if kubectl -n ${NAMESPACE} get deployments ${DS} &> /dev/null; then + kubectl -n ${NAMESPACE} delete deployment ${DS} + fi + if kubectl -n ${NAMESPACE} get service ${DS} &> /dev/null; then + echo ">>> Removing associated service ${DS}" + kubectl -n ${NAMESPACE} delete service ${DS} + fi + if kubectl -n ${NAMESPACE} get ingress ${DS} &> /dev/null; then + echo ">>> Removing associated ingress ${DS}" + kubectl -n ${NAMESPACE} delete ingress ${DS} + fi + if kubectl -n ${NAMESPACE} get pvc ${DS} &> /dev/null; then + echo ">>> Removing associated persistent volume ${DS}" + kubectl -n ${NAMESPACE} delete pvc ${DS} + fi + #delete anything else? + else + echo ">> The deployment '${DS}' would be removed" + if kubectl -n ${NAMESPACE} get service ${DS} &> /dev/null; then + echo ">>> The associated service '${DS}' would be removed" + fi + if kubectl -n ${NAMESPACE} get ingress ${DS} &> /dev/null; then + echo ">>> The associated ingress '${DS}' would be removed" + fi + if kubectl -n ${NAMESPACE} get pvc ${DS} &> /dev/null; then + echo ">>> The associated persistent volume '${DS}' would be removed" + fi + fi + done +else + echo "No service cleanup required" +fi + +currentStepEnd="$(date +"%Y-%m-%d %H:%M:%S")" +patchBuildStep "${buildStartTime}" "${previousStepEnd}" "${currentStepEnd}" "${NAMESPACE}" "serviceCleanupComplete" "Service/Deployment Cleanup" "${SERVICE_CLEANUP_WARNINGS}" +previousStepEnd=${currentStepEnd} beginBuildStep "Cronjob Cleanup" "cleaningUpCronjobs" set -x