Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2026 Flamingock (https://www.flamingock.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.flamingock.internal.common.core.error;

/**
* Exception thrown when Flamingock runs in validation-only mode and detects pending changes.
*/
public class PendingChangesException extends FlamingockException {

public PendingChangesException() {
super("Flamingock validationOnly=true: pending changes detected. Apply them before running in validation-only mode.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
public enum OperationType {
EXECUTE_APPLY,
EXECUTE_ROLLBACK,
EXECUTE_VALIDATE,
EXECUTE_DRYRUN,
VALIDATE,
Comment thread
bercianor marked this conversation as resolved.
Outdated
AUDIT_LIST,
AUDIT_FIX,
ISSUE_LIST,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import io.flamingock.internal.core.context.SimpleContext;
import io.flamingock.internal.core.external.store.AuditStore;
import io.flamingock.internal.core.external.store.audit.AuditPersistence;
import io.flamingock.internal.core.operation.OperationResolver;
import io.flamingock.internal.core.plan.ExecutionPlanner;
import io.flamingock.internal.core.event.CompositeEventPublisher;
import io.flamingock.internal.core.event.EventPublisher;
Expand All @@ -43,7 +44,6 @@
import io.flamingock.internal.core.event.model.IStageFailedEvent;
import io.flamingock.internal.core.event.model.IStageIgnoredEvent;
import io.flamingock.internal.core.event.model.IStageStartedEvent;
import io.flamingock.internal.core.operation.OperationFactory;
import io.flamingock.internal.core.operation.RunnableOperation;
import io.flamingock.internal.core.pipeline.loaded.LoadedPipeline;
import io.flamingock.internal.core.plugin.Plugin;
Expand Down Expand Up @@ -213,7 +213,7 @@ public final Runner build() {

FlamingockArguments flamingockArgs = FlamingockArguments.parse(applicationArgs);

OperationFactory operationFactory = new OperationFactory(
OperationResolver operationResolver = new OperationResolver(
runnerId,
flamingockArgs,
pipeline,
Expand All @@ -227,7 +227,7 @@ public final Runner build() {
coreConfiguration.isThrowExceptionIfCannotObtainLock(),
persistence.getCloser()
);
RunnableOperation<?, ?> operation = operationFactory.getOperation();
RunnableOperation<?, ?> operation = operationResolver.getOperation();

return new RunnerFactory(runnerId, flamingockArgs, operation, persistence.getCloser()).create();
}
Expand Down Expand Up @@ -359,6 +359,15 @@ public HOLDER setEnabled(boolean enabled) {
return getSelf();
}

public HOLDER setValidationOnly(boolean validationOnly) {
coreConfiguration.setValidationOnly(validationOnly);
return getSelf();
}

public boolean isValidationOnly() {
return coreConfiguration.isValidationOnly();
}

@Override
public HOLDER setServiceIdentifier(String serviceIdentifier) {
coreConfiguration.setServiceIdentifier(serviceIdentifier);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private FlamingockArguments(boolean cliMode,

public static FlamingockArguments parse(String[] args) {
if (args == null || args.length == 0) {
return new FlamingockArguments(false, OperationType.EXECUTE_APPLY, false, null, Collections.emptyMap());
return new FlamingockArguments(false, null, false, null, Collections.emptyMap());
}

boolean cliMode = false;
Expand Down Expand Up @@ -150,8 +150,8 @@ public boolean isCliMode() {
return cliMode;
}

public OperationType getOperation() {
return operation;
public Optional<OperationType> getOperation() {
return Optional.ofNullable(operation);
}

public boolean isOperationProvided() {
Comment thread
bercianor marked this conversation as resolved.
Outdated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ private Runner createCliRunner() {
.map(outputFile -> (ResponseChannel) new FileResponseChannel(outputFile, JsonObjectMapper.DEFAULT_INSTANCE))
.orElseGet(NoOpResponseChannel::new);

return new CliRunner(operation, finalizer, channel, flamingockArgs.getOperation());
return new CliRunner(operation, finalizer, channel, flamingockArgs.getOperation().get());
Comment thread
bercianor marked this conversation as resolved.
Outdated
}

private Runner createDefaultRunner() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ public interface CoreConfigurable {

void setEnabled(boolean enabled);

void setValidationOnly(boolean validationOnly);

boolean isValidationOnly();
Comment thread
bercianor marked this conversation as resolved.

void setServiceIdentifier(String serviceIdentifier);

void setMetadata(Map<String, Object> metadata);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ public class CoreConfiguration implements CoreConfigurable {
*/
private boolean enabled = true;

/**
* If true, Flamingock will only validate that no pending changes exist without applying them
* When Flamingock runs through the CLI, the CLI operation takes precedence over this flag
* Default false
*/
private boolean validationOnly = false;

/**
* Service identifier.
*/
Expand Down Expand Up @@ -91,6 +98,11 @@ public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

@Override
public void setValidationOnly(boolean validationOnly) {
this.validationOnly = validationOnly;
}

@Override
public void setServiceIdentifier(String serviceIdentifier) {
this.serviceIdentifier = serviceIdentifier;
Expand Down Expand Up @@ -126,6 +138,11 @@ public boolean isEnabled() {
return enabled;
}

@Override
public boolean isValidationOnly() {
return validationOnly;
}

@Override
public String getServiceIdentifier() {
return serviceIdentifier;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.flamingock.internal.core.operation.execute;
package io.flamingock.internal.core.operation;

import io.flamingock.internal.common.core.error.FlamingockException;
import io.flamingock.internal.common.core.error.PendingChangesException;
import io.flamingock.internal.common.core.response.data.ErrorInfo;
import io.flamingock.internal.common.core.response.data.ExecuteResponseData;
import io.flamingock.internal.common.core.response.data.StageResult;
Expand All @@ -26,8 +27,8 @@
import io.flamingock.internal.core.event.model.impl.StageCompletedEvent;
import io.flamingock.internal.core.event.model.impl.StageFailedEvent;
import io.flamingock.internal.core.event.model.impl.StageStartedEvent;
import io.flamingock.internal.core.operation.Operation;
import io.flamingock.internal.core.operation.OperationException;
import io.flamingock.internal.core.operation.execute.ExecuteArgs;
import io.flamingock.internal.core.operation.execute.ExecuteResult;
import io.flamingock.internal.core.operation.result.ExecutionResultBuilder;
import io.flamingock.internal.core.pipeline.execution.ExecutableStage;
import io.flamingock.internal.core.pipeline.execution.ExecutionContext;
Expand All @@ -48,9 +49,9 @@
import java.util.List;

/**
* Executes the pipeline and returns structured result data.
* Common execution flow for Apply and Validate operations
*/
public class ExecuteOperation implements Operation<ExecuteArgs, ExecuteResult> {
public abstract class AbstractPipelineTraverseOperation implements Operation<ExecuteArgs, ExecuteResult> {

private static final Logger logger = FlamingockLoggerFactory.getLogger("PipelineRunner");

Expand All @@ -66,15 +67,15 @@ public class ExecuteOperation implements Operation<ExecuteArgs, ExecuteResult> {

private final OrphanExecutionContext orphanExecutionContext;

private final Runnable finalizer;
protected final Runnable finalizer;

public ExecuteOperation(RunnerId runnerId,
ExecutionPlanner executionPlanner,
StageExecutor stageExecutor,
OrphanExecutionContext orphanExecutionContext,
EventPublisher eventPublisher,
boolean throwExceptionIfCannotObtainLock,
Runnable finalizer) {
public AbstractPipelineTraverseOperation(RunnerId runnerId,
ExecutionPlanner executionPlanner,
StageExecutor stageExecutor,
OrphanExecutionContext orphanExecutionContext,
EventPublisher eventPublisher,
boolean throwExceptionIfCannotObtainLock,
Runnable finalizer) {
this.runnerId = runnerId;
this.executionPlanner = executionPlanner;
this.stageExecutor = stageExecutor;
Expand All @@ -84,23 +85,6 @@ public ExecuteOperation(RunnerId runnerId,
this.finalizer = finalizer;
}


@Override
public ExecuteResult execute(ExecuteArgs args) {
ExecuteResponseData result;
try {
result = this.execute(args.getPipeline());
} catch (OperationException operationException) {
result = operationException.getResult();
throw operationException;
} catch (Throwable throwable) {
throw processAndGetFlamingockException(throwable, null);
} finally {
finalizer.run();
}
return new ExecuteResult(result);
}

private static List<AbstractLoadedStage> validateAndGetExecutableStages(LoadedPipeline pipeline) {
pipeline.validate();
List<AbstractLoadedStage> stages = new ArrayList<>();
Expand All @@ -111,7 +95,7 @@ private static List<AbstractLoadedStage> validateAndGetExecutableStages(LoadedPi
return stages;
}

private ExecuteResponseData execute(LoadedPipeline pipeline) throws FlamingockException {
protected ExecuteResponseData execute(LoadedPipeline pipeline, Boolean validateOnly) throws FlamingockException {
List<AbstractLoadedStage> allStages = validateAndGetExecutableStages(pipeline);
int stageCount = allStages.size();
long changeCount = allStages.stream()
Expand All @@ -130,6 +114,9 @@ private ExecuteResponseData execute(LoadedPipeline pipeline) throws FlamingockEx
execution.validate();

if (execution.isExecutionRequired()) {
if (validateOnly) {
throw new PendingChangesException();
}
execution.applyOnEach((executionId, lock, executableStage) -> {
StageResult stageResult = runStage(executionId, lock, executableStage);
resultBuilder.addStage(stageResult);
Expand Down Expand Up @@ -189,7 +176,7 @@ private StageResult startStage(String executionId, Lock lock, ExecutableStage ex
return executionOutput.getResult();
}

private FlamingockException processAndGetFlamingockException(Throwable exception, ExecutionResultBuilder resultBuilder) throws FlamingockException {
protected FlamingockException processAndGetFlamingockException(Throwable exception, ExecutionResultBuilder resultBuilder) throws FlamingockException {
FlamingockException flamingockException;
if (exception instanceof OperationException) {
OperationException pipelineException = (OperationException) exception;
Expand All @@ -208,5 +195,4 @@ private FlamingockException processAndGetFlamingockException(Throwable exception
eventPublisher.publish(new PipelineFailedEvent(flamingockException));
return flamingockException;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.flamingock.internal.core.operation;

import io.flamingock.internal.common.core.context.ContextResolver;
import io.flamingock.internal.common.core.operation.OperationType;
import io.flamingock.internal.common.core.recovery.Resolution;
import io.flamingock.internal.core.builder.args.FlamingockArguments;
import io.flamingock.internal.core.configuration.core.CoreConfigurable;
Expand All @@ -28,15 +29,14 @@
import io.flamingock.internal.core.operation.audit.AuditListArgs;
import io.flamingock.internal.core.operation.audit.AuditListOperation;
import io.flamingock.internal.core.operation.audit.AuditListResult;
import io.flamingock.internal.core.operation.execute.ExecuteArgs;
import io.flamingock.internal.core.operation.execute.ExecuteOperation;
import io.flamingock.internal.core.operation.execute.ExecuteResult;
import io.flamingock.internal.core.operation.execute.*;
import io.flamingock.internal.core.operation.issue.IssueGetArgs;
import io.flamingock.internal.core.operation.issue.IssueGetOperation;
import io.flamingock.internal.core.operation.issue.IssueGetResult;
import io.flamingock.internal.core.operation.issue.IssueListArgs;
import io.flamingock.internal.core.operation.issue.IssueListOperation;
import io.flamingock.internal.core.operation.issue.IssueListResult;
import io.flamingock.internal.core.operation.validate.ValidateOperation;
import io.flamingock.internal.core.pipeline.execution.OrphanExecutionContext;
import io.flamingock.internal.core.pipeline.execution.StageExecutor;
import io.flamingock.internal.core.pipeline.loaded.LoadedPipeline;
Expand All @@ -46,7 +46,7 @@

import java.util.Set;

public class OperationFactory {
public class OperationResolver {

private static final String ARG_HISTORY = "flamingock.audit.history";
private static final String ARG_SINCE = "flamingock.audit.since";
Expand All @@ -68,7 +68,7 @@ public class OperationFactory {
private final boolean isThrowExceptionIfCannotObtainLock;
private final Runnable finalizer;

public OperationFactory(RunnerId runnerId,
public OperationResolver(RunnerId runnerId,
FlamingockArguments flamingockArgs,
LoadedPipeline pipeline,
AuditPersistence persistence,
Expand All @@ -95,9 +95,14 @@ public OperationFactory(RunnerId runnerId,
}

public RunnableOperation<?, ?> getOperation() {
switch (flamingockArgs.getOperation()) {
OperationType operationType = flamingockArgs.getOperation().orElse(
coreConfiguration.isValidationOnly() ? OperationType.VALIDATE : OperationType.EXECUTE_APPLY
);
switch (operationType) {
case EXECUTE_APPLY:
return getExecuteOperation();
return getExecuteApplyOperation();
case VALIDATE:
return getValidateOperation();
case AUDIT_LIST:
return getAuditListOperation();
case AUDIT_FIX:
Expand All @@ -107,7 +112,7 @@ public OperationFactory(RunnerId runnerId,
case ISSUE_GET:
return getIssueGetOperation();
default:
throw new UnsupportedOperationException(String.format("Operation %s not supported", flamingockArgs.getOperation()));
throw new UnsupportedOperationException(String.format("Operation %s not supported", operationType));
}
}

Expand Down Expand Up @@ -140,17 +145,30 @@ private RunnableOperation<IssueGetArgs, IssueGetResult> getIssueGetOperation() {
return new RunnableOperation<>(issueGetOperation, new IssueGetArgs(changeId, guidance));
}

private RunnableOperation<ExecuteArgs, ExecuteResult> getExecuteOperation() {
private RunnableOperation<ExecuteArgs, ExecuteResult> getExecuteApplyOperation() {
final StageExecutor stageExecutor = new StageExecutor(dependencyContext, nonGuardedTypes, persistence, targetSystemManager, null);
ExecuteOperation executeOperation = new ExecuteOperation(
runnerId,
executionPlanner,
stageExecutor,
buildExecutionContext(coreConfiguration),
eventPublisher,
isThrowExceptionIfCannotObtainLock,
finalizer);
return new RunnableOperation<>(executeOperation, new ExecuteArgs(pipeline));
ExecuteApplyOperation executeApplyOperation = new ExecuteApplyOperation(
runnerId,
executionPlanner,
stageExecutor,
buildExecutionContext(coreConfiguration),
eventPublisher,
isThrowExceptionIfCannotObtainLock,
finalizer);
return new RunnableOperation<>(executeApplyOperation, new ExecuteArgs(pipeline));
}

private RunnableOperation<ExecuteArgs, ExecuteResult> getValidateOperation() {
final StageExecutor stageExecutor = new StageExecutor(dependencyContext, nonGuardedTypes, persistence, targetSystemManager, null);
ValidateOperation validateOperation = new ValidateOperation(
runnerId,
executionPlanner,
stageExecutor,
buildExecutionContext(coreConfiguration),
eventPublisher,
isThrowExceptionIfCannotObtainLock,
finalizer);
return new RunnableOperation<>(validateOperation, new ExecuteArgs(pipeline));
}

private static OrphanExecutionContext buildExecutionContext(CoreConfigurable configuration) {
Expand Down
Loading
Loading