Skip to content
Open
Show file tree
Hide file tree
Changes from all 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,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should rename this, because we have two levels of CLI commands (execute apply, execute rollback, etc). @dieppa how are we launching this operation from CLI? execute validate?

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 @@ -35,25 +35,22 @@ public class FlamingockArguments {

private final boolean cliMode;
private final OperationType operation;
private final boolean operationProvided;
private final String outputFile;
private final Map<String, String> remainingArgs;

private FlamingockArguments(boolean cliMode,
OperationType operation,
boolean operationProvided,
String outputFile,
Map<String, String> remainingArgs) {
this.cliMode = cliMode;
this.operation = operation;
this.operationProvided = operationProvided;
this.outputFile = outputFile;
this.remainingArgs = Collections.unmodifiableMap(remainingArgs);
}

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, null, Collections.emptyMap());
}

boolean cliMode = false;
Expand Down Expand Up @@ -105,8 +102,8 @@ public static FlamingockArguments parse(String[] args) {
}
}

OperationType effectiveOperation = operationProvided ? operation : OperationType.EXECUTE_APPLY;
return new FlamingockArguments(cliMode, effectiveOperation, operationProvided, outputFile, remaining);
OperationType effectiveOperation = operationProvided ? operation : null;
return new FlamingockArguments(cliMode, effectiveOperation, outputFile, remaining);
}

private static boolean parseBoolean(String key, String value) {
Expand Down Expand Up @@ -150,12 +147,8 @@ public boolean isCliMode() {
return cliMode;
}

public OperationType getOperation() {
return operation;
}

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

public Optional<String> getOutputFile() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import io.flamingock.internal.common.core.response.ResponseChannel;
import io.flamingock.internal.common.core.response.ResponseEnvelope;
import io.flamingock.internal.common.core.response.ResponseError;
import io.flamingock.internal.core.builder.args.FlamingockArguments;
import io.flamingock.internal.core.operation.AbstractOperationResult;
import io.flamingock.internal.common.core.operation.OperationType;
import io.flamingock.internal.core.operation.RunnableOperation;
import io.flamingock.internal.util.log.FlamingockLoggerFactory;
import org.slf4j.Logger;
Expand All @@ -35,16 +35,16 @@ public class CliRunner implements Runner {
private final RunnableOperation<?, ?> operation;
private final Runnable finalizer;
private final ResponseChannel channel;
private final OperationType operationType;
private final FlamingockArguments flamingockArgs;

public CliRunner(RunnableOperation<?, ?> operation,
Runnable finalizer,
ResponseChannel channel,
OperationType operationType) {
FlamingockArguments flamingockArgs) {
this.operation = operation;
this.finalizer = finalizer;
this.channel = channel;
this.operationType = operationType;
this.flamingockArgs = flamingockArgs;
}

@Override
Expand Down Expand Up @@ -92,7 +92,9 @@ public void run() {

private void writeResponse(AbstractOperationResult result, Throwable error, long durationMs) {
ResponseEnvelope envelope;
String operationName = operationType.name();
String operationName = flamingockArgs.getOperation()
.map(Enum::name)
.orElse("unknown");

if (error != null) {
envelope = ResponseEnvelope.failure(operationName, ResponseError.from(error), durationMs);
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);
}

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();

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,19 +85,19 @@ public ExecuteOperation(RunnerId runnerId,
this.finalizer = finalizer;
}

protected abstract boolean validateOnlyMode();

@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();
this.finalizer.run();
}
return new ExecuteResult(result);
}
Expand Down Expand Up @@ -130,6 +131,9 @@ private ExecuteResponseData execute(LoadedPipeline pipeline) throws FlamingockEx
execution.validate();

if (execution.isExecutionRequired()) {
if (validateOnlyMode()) {
throw new PendingChangesException();
}
execution.applyOnEach((executionId, lock, executableStage) -> {
StageResult stageResult = runStage(executionId, lock, executableStage);
resultBuilder.addStage(stageResult);
Expand Down Expand Up @@ -208,5 +212,4 @@ private FlamingockException processAndGetFlamingockException(Throwable exception
eventPublisher.publish(new PipelineFailedEvent(flamingockException));
return flamingockException;
}

}
Loading
Loading