diff --git a/src/main/java/io/linuxserver/fleet/v2/db/DefaultImageDAO.java b/src/main/java/io/linuxserver/fleet/v2/db/DefaultImageDAO.java index 5ad686d..44518fc 100644 --- a/src/main/java/io/linuxserver/fleet/v2/db/DefaultImageDAO.java +++ b/src/main/java/io/linuxserver/fleet/v2/db/DefaultImageDAO.java @@ -27,7 +27,6 @@ import io.linuxserver.fleet.v2.types.*; import io.linuxserver.fleet.v2.types.internal.ImageOutlineRequest; import io.linuxserver.fleet.v2.types.internal.TagBranchOutlineRequest; import io.linuxserver.fleet.v2.types.meta.ItemSyncSpec; -import sun.util.locale.provider.LocaleServiceProviderPool; import java.sql.*; import java.util.ArrayList; @@ -37,17 +36,16 @@ import java.util.Set; public class DefaultImageDAO extends AbstractDAO implements ImageDAO { - private static final String GetRepository = "{CALL Repository_Get(?)}"; - private static final String GetImageKeys = "{CALL Repository_GetImageKeys(?)}"; + private static final String GetRepository = "{CALL Repository_Get(?)}"; + private static final String GetImageKeys = "{CALL Repository_GetImageKeys(?)}"; - private static final String StoreImage = "{CALL Image_Store(?,?,?,?,?,?,?,?,?,?)}"; - private static final String StoreTagBranch = "{CALL Image_StoreTagBranch(?,?,?,?)}"; - private static final String StoreTagDigest = "{CALL Image_StoreTagDigest(?,?,?,?,?)}"; - private static final String CreateImageOutline = "{CALL Image_CreateOutline(?,?,?,?)}"; - private static final String GetImage = "{CALL Image_Get(?)}"; - private static final String GetTagBranches = "{CALL Image_GetTagBranches(?)}"; - private static final String GetTagDigests = "{CALL Image_GetTagDigests(?)}"; - private static final String DeleteImage = "{CALL Image_Delete(?)}"; + private static final String StoreImage = "{CALL Image_Store(?,?,?,?,?,?,?,?,?,?)}"; + private static final String CreateTagBranchOutline = "{CALL Image_CreateTagBranchOutline(?,?)}"; + private static final String StoreTagDigest = "{CALL Image_StoreTagDigest(?,?,?,?,?)}"; + private static final String CreateImageOutline = "{CALL Image_CreateOutline(?,?,?,?)}"; + private static final String GetImage = "{CALL Image_Get(?)}"; + private static final String GetTagBranches = "{CALL Image_GetTagBranches(?)}"; + private static final String DeleteImage = "{CALL Image_Delete(?)}"; public DefaultImageDAO(final DefaultDatabaseConnection databaseConnection) { super(databaseConnection); @@ -132,32 +130,28 @@ public class DefaultImageDAO extends AbstractDAO implements ImageDAO { } @Override - public InsertUpdateResult storeTagBranchOutline(final TagBranchOutlineRequest request) { + public InsertUpdateResult createTagBranchOutline(final TagBranchOutlineRequest request) { try (final Connection connection = getConnection()) { - try (final CallableStatement call = connection.prepareCall(StoreTagBranch)) { + try (final CallableStatement call = connection.prepareCall(CreateTagBranchOutline)) { int i = 1; call.setInt(i++, request.getImageKey().getId()); - call.setString(i++, request.getBranchName()); - call.setString(i++, request.getLatestTag().getVersion()); - Utils.setNullableTimestamp(call, i, request.getLatestTag().getBuildDate()); + call.setString(i, request.getBranchName()); final ResultSet results = call.executeQuery(); if (results.next()) { - - storeTagDigests(connection, makeTagBranchKey(results, request.getImageKey()), request.getLatestTag().getDigests()); - return new InsertUpdateResult<>(makeTagBranch(results, connection, request.getImageKey())); + return new InsertUpdateResult<>(makeTagBranch(results, call, request.getImageKey())); } - return new InsertUpdateResult<>(InsertUpdateStatus.FAILED, "storeTagBranchOutline did not return anything."); + return new InsertUpdateResult<>(InsertUpdateStatus.FAILED, "createTagBranchOutline did not return anything."); } } catch (SQLException e) { - getLogger().error("Error caught when executing SQL: storeTagBranchOutline", e); + getLogger().error("Error caught when executing SQL: createTagBranchOutline", e); return new InsertUpdateResult<>(InsertUpdateStatus.FAILED, e.getMessage()); } } @@ -355,40 +349,37 @@ public class DefaultImageDAO extends AbstractDAO implements ImageDAO { final ResultSet results = call.executeQuery(); while (results.next()) { - image.addTagBranch(makeTagBranch(results, connection, image.getKey())); + image.addTagBranch(makeTagBranch(results, call, image.getKey())); } } } - private TagBranch makeTagBranch(final ResultSet results, final Connection connection, final ImageKey imageKey) throws SQLException { + private TagBranch makeTagBranch(final ResultSet results, final CallableStatement call, final ImageKey imageKey) throws SQLException { - final TagBranchKey branchKey = makeTagBranchKey(results, imageKey); - - return new TagBranch(branchKey, + return new TagBranch(makeTagBranchKey(results, imageKey), results.getString("BranchName"), - makeTag(results, connection, branchKey)); + results.getBoolean("BranchProtected"), + makeTag(results, call)); } private TagBranchKey makeTagBranchKey(ResultSet results, ImageKey imageKey) throws SQLException { return new TagBranchKey(results.getInt("BranchId"), imageKey); } - private Tag makeTag(final ResultSet results, final Connection connection, final TagBranchKey tagBranchKey) throws SQLException { + private Tag makeTag(final ResultSet results, final CallableStatement call) throws SQLException { return new Tag(results.getString("TagVersion"), results.getTimestamp("TagBuildDate").toLocalDateTime(), - makeTagDigests(tagBranchKey, connection)); + makeTagDigests(call)); } - private Set makeTagDigests(final TagBranchKey tagBranchKey, final Connection connection) throws SQLException { + private Set makeTagDigests(final CallableStatement call) throws SQLException { final Set digests = new HashSet<>(); - try (final CallableStatement call = connection.prepareCall(GetTagDigests)) { + while (call.getMoreResults() && call.getUpdateCount() == -1) { - call.setInt(1, tagBranchKey.getId()); - - final ResultSet results = call.executeQuery(); + final ResultSet results = call.getResultSet(); while (results.next()) { digests.add(makeTagDigest(results)); } diff --git a/src/main/java/io/linuxserver/fleet/v2/db/ImageDAO.java b/src/main/java/io/linuxserver/fleet/v2/db/ImageDAO.java index 6c06bc4..0760ece 100644 --- a/src/main/java/io/linuxserver/fleet/v2/db/ImageDAO.java +++ b/src/main/java/io/linuxserver/fleet/v2/db/ImageDAO.java @@ -34,7 +34,7 @@ public interface ImageDAO { InsertUpdateResult createImageOutline(final ImageOutlineRequest request); - InsertUpdateResult storeTagBranchOutline(final TagBranchOutlineRequest request); + InsertUpdateResult createTagBranchOutline(final TagBranchOutlineRequest request); void removeImage(final Image image); diff --git a/src/main/java/io/linuxserver/fleet/v2/types/TagBranch.java b/src/main/java/io/linuxserver/fleet/v2/types/TagBranch.java index 8f95c3e..22d1505 100644 --- a/src/main/java/io/linuxserver/fleet/v2/types/TagBranch.java +++ b/src/main/java/io/linuxserver/fleet/v2/types/TagBranch.java @@ -24,20 +24,26 @@ import java.util.concurrent.atomic.AtomicReference; public class TagBranch extends AbstractHasKey { - private final String branchName; - private final AtomicReference latestTag; + private final String branchName; + private final AtomicReference branchProtected; + private final AtomicReference latestTag; - public TagBranch(final TagBranchKey tagBranchKey, final String tagBranchName, final Tag latestTag) { + public TagBranch(final TagBranchKey tagBranchKey, final String tagBranchName, final boolean branchProtected, final Tag latestTag) { super(tagBranchKey); - this.branchName = tagBranchName; - this.latestTag = new AtomicReference<>(latestTag); + this.branchName = tagBranchName; + this.branchProtected = new AtomicReference<>(branchProtected); + this.latestTag = new AtomicReference<>(latestTag); } public final void updateLatestTag(final Tag latestTag) { this.latestTag.set(latestTag); } + public final void setBranchProtected(final boolean branchProtected) { + this.branchProtected.set(branchProtected); + } + public final String getBranchName() { return branchName; } @@ -49,4 +55,8 @@ public class TagBranch extends AbstractHasKey { public final boolean isNamedLatest() { return "latest".equals(getBranchName()); } + + public final boolean isBranchProtected() { + return branchProtected.get(); + } } diff --git a/src/main/java/io/linuxserver/fleet/v2/types/internal/TagBranchOutlineRequest.java b/src/main/java/io/linuxserver/fleet/v2/types/internal/TagBranchOutlineRequest.java index dff7967..6a5cd00 100644 --- a/src/main/java/io/linuxserver/fleet/v2/types/internal/TagBranchOutlineRequest.java +++ b/src/main/java/io/linuxserver/fleet/v2/types/internal/TagBranchOutlineRequest.java @@ -24,13 +24,11 @@ public class TagBranchOutlineRequest { private final ImageKey imageKey; private final String branchName; - private final Tag latestTag; public TagBranchOutlineRequest(final ImageKey imageKey, final String branchName, final Tag latestTag) { this.imageKey = imageKey; this.branchName = branchName; - this.latestTag = latestTag; } public final ImageKey getImageKey() { @@ -40,8 +38,4 @@ public class TagBranchOutlineRequest { public final String getBranchName() { return branchName; } - - public final Tag getLatestTag() { - return latestTag; - } } diff --git a/src/main/resources/db/migration/V2.0__CreateV2TablesAndSprocs.sql b/src/main/resources/db/migration/V2.0__CreateV2TablesAndSprocs.sql index e69de29..e0a389d 100644 --- a/src/main/resources/db/migration/V2.0__CreateV2TablesAndSprocs.sql +++ b/src/main/resources/db/migration/V2.0__CreateV2TablesAndSprocs.sql @@ -0,0 +1,277 @@ +DELIMITER // + +CREATE TABLE Repository ( + `id` INT NOT NULL auto_increment PRIMARY KEY, + `name` VARCHAR(255) NOT NULL, + `sync_enabled` TINYINT NOT NULL DEFAULT 1, + `version_mask` VARCHAR(255) DEFAULT NULL, + `hidden` TINYINT NOT NULL DEFAULT 0, + `stable` TINYINT NOT NULL DEFAULT 1, + `deprecated` TINYINT NOT NULL DEFAULT 0, + `modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY (`name`) +) ENGINE=InnoDB; +// + +CREATE TABLE Image ( + `id` INT NOT NULL auto_increment PRIMARY KEY, + `repository` INT NOT NULL, + `name` VARCHAR(255) NOT NULL, + `description` TEXT DEFAULT NULL, + `pulls` BIGINT DEFAULT 0, + `stars` BIGINT DEFAULT 0, + `latest_version` VARCHAR(255) DEFAULT NULL, + `sync_enabled` TINYINT NOT NULL DEFAULT 1, + `version_mask` VARCHAR(255) DEFAULT NULL, + `hidden` TINYINT NOT NULL DEFAULT 0, + `stable` TINYINT NOT NULL DEFAULT 1, + `deprecated` TINYINT NOT NULL DEFAULT 0, + `modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY (`repository`, `name`), + FOREIGN KEY (`repository`) REFERENCES Repository(`id`) ON DELETE CASCADE +) ENGINE=InnoDB; +// + +CREATE TABLE TagBranch ( + `id` INT NOT NULL auto_increment PRIMARY KEY, + `image_id` INT NOT NULL, + `name` VARCHAR(255) NOT NULL, + `latest_version` VARCHAR(255) NOT NULL DEFAULT 'Unknown', + `protected` TINYINT NOT NULL DEFAULT 0, + `build_date` TIMESTAMP DEFAULT NULL, + UNIQUE KEY (`image_id`, `name`), + FOREIGN KEY (`image_id`) REFERENCES Image(`id`) ON DELETE CASCADE +) ENGINE=InnoDB; +// + +CREATE TABLE TagDigest ( + `branch_id` INT NOT NULL, + `size` BIGINT NOT NULL, + `arch` VARCHAR(100) NOT NULL, + `digest` VARCHAR(255) NOT NULL, + `variant` VARCHAR(50) DEFAULT NULL, + UNIQUE KEY (`branch_id`, `digest`), + FOREIGN KEY (`branch_id`) REFERENCES TagBranch(`id`) +) ENGINE=InnoDB; +// + +CREATE OR REPLACE VIEW `ImageKey_View` AS ( + + SELECT + + image_view.`ImageId`, + image_view.`ImageName`, + image_view.`RepositoryId`, + image_view.`RepositoryName` + + FROM + Image_View image_view +); +// + +CREATE OR REPLACE VIEW `Image_View` AS ( + + SELECT + + -- Key + images.`id` AS `ImageId`, + images.`name` AS `ImageName`, + images.`repository` AS `RepositoryId`, + repositories.`name` AS `RepositoryName`, + + -- Counts + images.`pulls` AS `LatestPullCount`, + images.`stars` AS `LatestStarCount`, + + -- Spec + images.`sync_enabled` AS `SyncEnabled`, + images.`version_mask` AS `VersionMask`, + images.`hidden` AS `Hidden`, + images.`stable` AS `Stable`, + images.`deprecated` AS `Deprecated`, + + -- General + images.`description` AS `Description`, + images.`modified` AS `LastUpdated` + + FROM + Image images + JOIN + Repository repositories ON repositories.`id` = images.`repository` +); +// + +CREATE OR REPLACE VIEW `TagBranch_View` AS ( + + SELECT + `image_id` AS `ImageId`, + `id` AS `BranchId`, + `name` AS `BranchName`, + `latest_version` AS `TagVersion`, + `protected` AS `BranchProtected`, + `build_date` AS `TagBuildDate` + FROM + TagBranch +); +// + +CREATE OR REPLACE VIEW `TagDigest_View` AS ( + + SELECT + tag_branch.`image_id` AS `ImageId`, + tag_branch.`branch_id AS `BranchId`, + tag_digest.`size` AS `DigestSize`, + tag_digest.`digest` AS `DigestSha`, + tag_digest.`arch` AS `DigestArch`, + tag_digest.`variant` AS `DigestVariant`, + FROM + TagDigest tag_digest + JOIN + TagBranch tag_branch ON tag_branch.`id` = tag_digest.`branch_id` +); +// + +CREATE OR REPLACE PROCEDURE `Image_GetTagBranches` +( + in_image_id INT +) +BEGIN + + -- Top level branch info + SELECT * + FROM + TagBranch_View + WHERE + `image_id` = in_image_id; + + -- All digests for the given tag in the branch. + SELECT * + FROM + TagDigest_View tag_digest + WHERE + tag_digest.`ImageId` = in_image_id; + +END; +// + +CREATE OR REPLACE PROCEDURE `Image_CreateTagBranchOutline` +( + in_image_id INT, + in_branch_name VARCHAR(255) +) +BEGIN + + INSERT INTO TagBranch (`image_id`, `name`) + VALUES + ( + in_image_id, + in_branch_name + ); + + -- Top level branch info + SELECT * + FROM + TagBranch_View + WHERE + `BranchId` = LAST_INSERT_ID(); + +END; +// + +CREATE OR REPLACE PROCEDURE `Image_StoreTagDigest` +( + in_branch_id INT, + in_size BIGINT, + in_digest VARCHAR(255) + in_arch VARCHAR(255), + in_variant VARCHAR(100), + + OUT out_status enum('Inserted', 'NoChange') +) +BEGIN + + IF EXISTS(SELECT `branch_id` FROM TagDigest WHERE `branch_id` = in_branch_id AND `digest` = in_digest) THEN + + -- Digests are immutable and cannot be changed. Only new digest can be added. + SET out_status = 'NoChange'; + + ELSE + + INSERT INTO TagDigest (`branch_id`, `size`, `digest`, `arch`, `variant`) + VALUES + ( + in_branch_id, + in_size, + in_digest, + in_arch, + in_variant + ); + + SET out_status = 'Inserted'; + + END IF; + +END; +// + +CREATE OR REPLACE PROCEDURE `Image_Get` +( + in_id INT +) +BEGIN + + SELECT * FROM Image_View image_view WHERE image_view.`ImageId` = in_id; + +END; +// + +CREATE OR REPLACE PROCEDURE `Image_Delete` +( + in_id INT, + + OUT out_status enum('Updated', 'NoChange') +) +BEGIN + + IF EXISTS(SELECT `id` FROM ImagesV2 WHERE `id` = in_id`) THEN + + DELETE FROM ImagesV2 WHERE `id` = in_id; + SET out_status = 'Updated'; + + ELSE + SET out_status = 'NoChange'; + END IF; + +END; +// + +CREATE OR REPLACE PROCEDURE `Image_CreateOutline` +( + in_repository INT, + in_name VARCHAR(255), + in_description TEXT, + in_modified TIMESTAMP, + + OUT out_status enum('Inserted', 'NoChange', 'Exists') +) +BEGIN + + IF EXISTS(SELECT `id` FROM ImagesV2 WHERE `id` = in_id`) THEN + SET out_status = 'Exists'; + ELSE + + INSERT INTO ImagesV2 (`repository`, `name`, `description`, `modified`) + VALUES + ( + in_repository, + in_name, + in_description, + in_modified + ); + + SELECT * FROM ImageKey_View image_key WHERE image_key.`ImageId` = LAST_INSERT_ID(); + + END IF; + +END; +// \ No newline at end of file