Skip to content

Commit

Permalink
Add validation for configuration and manifests (#271)
Browse files Browse the repository at this point in the history
- Adds validation dependencies
- Adds validators to BackupJobConfiguration
- Adds validators to AppVersion
- Adds validators to BackupIncrementManifest
- Adds validators to FileMetadata
- Adds validators to ArchivedFileMetadata
- Calls validators when BackupIncrementManifest is created
- Validates CLI parameters
- Adds new test cases

Resolves #270
{minor}

Signed-off-by: Esta Nagy <[email protected]>
  • Loading branch information
nagyesta authored Jun 23, 2024
1 parent 6956ea7 commit 586f360
Show file tree
Hide file tree
Showing 30 changed files with 727 additions and 428 deletions.
2 changes: 2 additions & 0 deletions file-barj-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dependencies {
compileOnly(libs.jetbrains.annotations)
implementation(project(":file-barj-stream-io"))
implementation(libs.slf4j.api)
implementation(libs.bundles.validation)
implementation(libs.bundles.jackson)
implementation(libs.commons.compress)
implementation(libs.commons.io)
Expand All @@ -43,6 +44,7 @@ licensee {
allow("MIT")
allow("Apache-2.0")
allow("BSD-2-Clause")
allow("GPL-2.0-with-classpath-exception")
allowUrl("https://www.bouncycastle.org/licence.html")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ public boolean isFromLastIncrement(
return filesFromManifests.get(filesFromManifests.lastKey()).containsKey(fileMetadata.getId());
}

@SuppressWarnings("checkstyle:TodoComment")
@Nullable
@Override
public FileMetadata findMostRelevantPreviousVersion(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
import com.github.nagyesta.filebarj.core.model.enums.OperatingSystem;
import com.github.nagyesta.filebarj.core.util.LogUtil;
import com.github.nagyesta.filebarj.core.util.OsUtil;
import jakarta.validation.Validation;
import jakarta.validation.ValidationException;
import jakarta.validation.Validator;
import jakarta.validation.groups.Default;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -36,6 +40,7 @@ public class ManifestManagerImpl implements ManifestManager {
private static final String HISTORY_FOLDER = ".history";
private static final String MANIFEST_JSON_GZ = ".manifest.json.gz";
private final ObjectMapper mapper = new ObjectMapper();
private final Validator validator = createValidator();

@Override
public BackupIncrementManifest generateManifest(
Expand All @@ -57,6 +62,7 @@ public BackupIncrementManifest generateManifest(
.build();
Optional.ofNullable(jobConfiguration.getEncryptionKey())
.ifPresent(manifest::generateDataEncryptionKeys);
validate(manifest, ValidationRules.Created.class);
return manifest;
}

Expand Down Expand Up @@ -197,12 +203,19 @@ public RestoreManifest mergeForRestore(
.build();
}

@SuppressWarnings("checkstyle:TodoComment")
@Override
public void validate(
@NonNull final BackupIncrementManifest manifest,
@NonNull final Class<? extends ValidationRules> forAction) {
//TODO: implement validation

final var violations = validator.validate(manifest, forAction, Default.class);
if (!violations.isEmpty()) {
final var violationsMessage = violations.stream()
.map(v -> v.getPropertyPath().toString() + ": " + v.getMessage() + " (found: " + v.getInvalidValue() + ")")
.collect(Collectors.joining("\n\t"));
log.error("Manifest validation failed for {} action:\n\t{}", forAction.getSimpleName(), violationsMessage);
throw new ValidationException("The manifest is invalid!");
}
}

@Override
Expand All @@ -214,6 +227,12 @@ public void deleteIncrement(
deleteManifestAndArchiveFilesFromBackupDirectory(backupDirectory, fileNamePrefix);
}

private static Validator createValidator() {
try (var validatorFactory = Validation.buildDefaultValidatorFactory()) {
return validatorFactory.getValidator();
}
}

@NotNull
private SortedMap<Integer, BackupIncrementManifest> loadManifests(
@NotNull final List<Path> manifestFiles,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
import com.github.nagyesta.filebarj.core.json.PublicKeyDeserializer;
import com.github.nagyesta.filebarj.core.json.PublicKeySerializer;
import com.github.nagyesta.filebarj.core.model.enums.BackupType;
import com.github.nagyesta.filebarj.core.validation.FileNamePrefix;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
Expand All @@ -26,9 +30,7 @@
@EqualsAndHashCode
@Builder
@Jacksonized
@SuppressWarnings("checkstyle:TodoComment")
public class BackupJobConfiguration {
//TODO: validate the whole configuration
private static final int ONE_HUNDRED_GIBIBYTE = 100 * 1024;
/**
* The desired backup type which should be used when the job is executed.
Expand Down Expand Up @@ -87,6 +89,7 @@ public class BackupJobConfiguration {
* <br/><br/>
* NOTE: Using 0 means that the archive won't be chunked.
*/
@Positive
@Builder.Default
@EqualsAndHashCode.Exclude
@JsonProperty("chunk_size_mebibyte")
Expand All @@ -98,6 +101,7 @@ public class BackupJobConfiguration {
* increments cannot use a different duplicate handling strategy.
*/
@NonNull
@FileNamePrefix
@JsonProperty("file_name_prefix")
private final String fileNamePrefix;
/**
Expand All @@ -112,6 +116,8 @@ public class BackupJobConfiguration {
/**
* The source files we want to archive.
*/
@Valid
@Size(min = 1)
@NonNull
@EqualsAndHashCode.Exclude
@JsonProperty("sources")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.nagyesta.filebarj.core.model.BackupPath;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
Expand Down Expand Up @@ -34,6 +37,7 @@ public class BackupSource {
/**
* The path we want to back up. Can be file or directory.
*/
@Valid
@NonNull
@JsonProperty("path")
private final BackupPath path;
Expand All @@ -42,13 +46,13 @@ public class BackupSource {
* with "glob" syntax relative to the value of the path field.
*/
@JsonProperty("include_patterns")
private final Set<String> includePatterns;
private final Set<@NotNull @NotBlank String> includePatterns;
/**
* Optional exclude patterns for filtering the contents. Uses {@link java.nio.file.PathMatcher}
* with "glob" syntax relative to the value of the path field.
*/
@JsonProperty("exclude_patterns")
private final Set<String> excludePatterns;
private final Set<@NotNull @NotBlank String> excludePatterns;

/**
* Lists the matching {@link Path} entries.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.NonNull;
import org.jetbrains.annotations.NotNull;

Expand All @@ -18,7 +19,8 @@
* @param minor Minor version component
* @param patch Patch version component
*/
public record AppVersion(int major, int minor, int patch) implements Comparable<AppVersion> {
public record AppVersion(
@PositiveOrZero int major, @PositiveOrZero int minor, @PositiveOrZero int patch) implements Comparable<AppVersion> {

/**
* The version of the currently used file-barj component.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
Expand All @@ -27,6 +28,7 @@ public final class ArchiveEntryLocator {
/**
* The backup increment containing the entry.
*/
@PositiveOrZero
@JsonProperty("backup_increment")
private final int backupIncrement;
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
Expand All @@ -29,6 +31,7 @@ public class ArchivedFileMetadata {
/**
* The location where the archived file contents are stored.
*/
@Valid
@NonNull
@JsonProperty("archive_location")
private final ArchiveEntryLocator archiveLocation;
Expand All @@ -46,6 +49,7 @@ public class ArchivedFileMetadata {
* The Ids of the original files which are archived by the current entry. If multiple Ids are
* listed, then duplicates where eliminated.
*/
@Size(min = 1)
@NonNull
@JsonProperty("files")
private Set<UUID> files;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.nagyesta.filebarj.core.config.BackupJobConfiguration;
import com.github.nagyesta.filebarj.core.model.enums.BackupType;
import com.github.nagyesta.filebarj.core.validation.FileNamePrefix;
import com.github.nagyesta.filebarj.core.validation.PastOrPresentEpochSeconds;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
Expand All @@ -27,19 +31,23 @@ public class BackupIncrementManifest extends EncryptionKeyStore {
/**
* The version number of the app that generated the manifest.
*/
@Valid
@NonNull
@JsonProperty("app_version")
private AppVersion appVersion;
/**
* The time when the backup process was started in UTC epoch
* seconds.
*/
@Positive
@PastOrPresentEpochSeconds
@JsonProperty("start_time_utc_epoch_seconds")
private long startTimeUtcEpochSeconds;
/**
* The file name prefix used by the backup archives.
*/
@NonNull
@FileNamePrefix
@JsonProperty("file_name_prefix")
private String fileNamePrefix;
/**
Expand All @@ -51,32 +59,46 @@ public class BackupIncrementManifest extends EncryptionKeyStore {
/**
* The OS of the backup.
*/
@NotNull(groups = ValidationRules.Created.class)
@NotBlank(groups = ValidationRules.Created.class)
@JsonProperty("operating_system")
private String operatingSystem;
/**
* The snapshot of the backup configuration at the time of backup.
*/
@Valid
@NonNull
@JsonProperty("job_configuration")
private BackupJobConfiguration configuration;
/**
* The map of matching files identified during backup keyed by Id.
*/
@Valid
@Size(max = 0, groups = ValidationRules.Created.class)
@Size(min = 1, groups = ValidationRules.Persisted.class)
@JsonProperty("files")
private Map<UUID, FileMetadata> files;
/**
* The map of archive entries saved during backup keyed by Id.
*/
@Valid
@Size(max = 0, groups = ValidationRules.Created.class)
@JsonProperty("archive_entries")
private Map<UUID, ArchivedFileMetadata> archivedEntries;
/**
* The name of the index file.
*/
@Null(groups = ValidationRules.Created.class)
@NotNull(groups = ValidationRules.Persisted.class)
@NotBlank(groups = ValidationRules.Persisted.class)
@JsonProperty("index_file_name")
private String indexFileName;
/**
* The names of the data files.
*/
@Null(groups = ValidationRules.Created.class)
@NotNull(groups = ValidationRules.Persisted.class)
@Size(min = 1, groups = ValidationRules.Persisted.class)
@JsonProperty("data_file_names")
private List<String> dataFileNames;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import jakarta.validation.constraints.NotBlank;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import org.apache.commons.io.FilenameUtils;
Expand Down Expand Up @@ -31,6 +32,7 @@ public final class BackupPath implements Comparable<BackupPath> {
private static final Pattern UNIX_FILE_SCHEME = Pattern
.compile("^" + FILE_SCHEME_DOUBLE_SLASH + "(?<" + PATH_GROUP + ">/[^:]*)$");
private static final Set<Pattern> PATTERNS = Set.of(WINDOWS_FILE_SCHEME, UNIX_FILE_SCHEME);
@NotBlank
private final String path;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.nagyesta.filebarj.io.stream.crypto.EncryptionUtil;
import jakarta.validation.Valid;
import jakarta.validation.constraints.PositiveOrZero;
import jakarta.validation.constraints.Size;
import lombok.*;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
Expand Down Expand Up @@ -41,9 +44,11 @@ public class EncryptionKeyStore {
* by 1. A manifest can contain more numbers if the backup increments were merged (consolidated)
* into a single archive.
*/
@Valid
@Size(min = 1)
@NonNull
@JsonProperty("backup_versions")
private SortedSet<Integer> versions;
private SortedSet<@PositiveOrZero Integer> versions;
/**
* The byte arrays containing the data encryption keys (DEK) encrypted with the key encryption
* key (KEK).
Expand Down
Loading

0 comments on commit 586f360

Please sign in to comment.