LocalSameApplicationStackReplacer.java

package com.cloudforge.core.local;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * Deletes prior local emulator stacks for the same CloudForge {@code applicationId}
 * when redeploying under a new stack name.
 */
public final class LocalSameApplicationStackReplacer {
    private static final String APPLICATION_TAG = "cloudforge:application";

    private LocalSameApplicationStackReplacer() {
    }

    public record Result(List<String> deletedStacks, Optional<String> warning) {
        public static Result empty() {
            return new Result(List.of(), Optional.empty());
        }
    }

    /**
     * @param replaceEnvVar e.g. {@code CFC_MINISTACK_REPLACE_SAME_APP}; disabled when {@code false} or {@code 0}
     */
    public static boolean replaceEnabled(String replaceEnvVar) {
        String flag = System.getenv(replaceEnvVar);
        return !("false".equalsIgnoreCase(flag) || "0".equals(flag));
    }

    public static Result replace(
            String applicationId,
            String keepCfnStack,
            String replaceEnvVar,
            DeploymentTarget target,
            Supplier<List<String>> activeStacks,
            Function<String, Map<String, String>> tagsForStack,
            Function<String, Optional<String>> catalogApplicationId,
            StackDeleter deleter) {
        if (!replaceEnabled(replaceEnvVar)) {
            return Result.empty();
        }
        if (applicationId == null || applicationId.isBlank()) {
            return Result.empty();
        }
        String appId = applicationId.trim();
        List<String> deleted = new ArrayList<>();
        try {
            for (String stack : activeStacks.get()) {
                if (stack.equals(keepCfnStack)) {
                    continue;
                }
                Map<String, String> tags = tagsForStack.apply(stack);
                String taggedApp = tags.get(APPLICATION_TAG);
                boolean sameApp = appId.equalsIgnoreCase(taggedApp)
                    || catalogApplicationId.apply(stack)
                        .map(id -> appId.equalsIgnoreCase(id))
                        .orElse(false);
                if (!sameApp) {
                    continue;
                }
                deleter.delete(stack);
                deleted.add(stack);
            }
            return new Result(List.copyOf(deleted), Optional.empty());
        } catch (Exception e) {
            String targetLabel = target.name().toLowerCase(Locale.ROOT);
            return new Result(
                List.copyOf(deleted),
                Optional.of("Could not prune prior " + targetLabel + " stacks for "
                    + appId + ": " + e.getMessage()));
        }
    }

    @FunctionalInterface
    public interface StackDeleter {
        void delete(String stackName) throws IOException;
    }
}