diff --git a/.gitignore b/.gitignore
index 524f096..69a95bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,7 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
+
+# Idea
+
+.idea/
diff --git a/LICENSE b/LICENSE
index b737d16..6689009 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024 Yaroslav Svitlytskyi
+Copyright (c) 2022 YarikRevich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4e699bd
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,135 @@
+dev := $(or $(dev), 'false')
+
+ifneq (,$(wildcard .env))
+include .env
+export
+endif
+
+.PHONY: help
+.DEFAULT_GOAL := help
+help:
+ @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
+
+.PHONY: clean
+clean: ## Clean project area
+ @mvn clean
+
+.PHONY: prepare
+prepare: ## Install prerequisites
+ @mvn org.apache.maven.plugins:maven-dependency-plugin:3.6.0:tree -Dverbose=true
+
+.PHONY: test
+test: clean ## Run both unit and integration tests
+ @mvn test
+ @mvn verify
+
+.PHONY: lint
+lint: ## Run Apache Spotless linter
+ @mvn spotless:apply
+
+.PHONY: create-local-client
+create-local-client: ## Create ResourceTracker local directory for client
+ @mkdir -p $(HOME)/.repoachiever/config
+
+.PHONY: create-local-api-server
+create-local-api-server: ## Create ResourceTracker local directory for API Server
+ @mkdir -p $(HOME)/.repoachiever/config
+ @mkdir -p $(HOME)/.repoachiever/diagnostics/prometheus/internal
+ @mkdir -p $(HOME)/.repoachiever/diagnostics/prometheus/config
+ @mkdir -p $(HOME)/.repoachiever/diagnostics/grafana/internal
+ @mkdir -p $(HOME)/.repoachiever/diagnostics/grafana/config/dashboards
+ @mkdir -p $(HOME)/.repoachiever/diagnostics/grafana/config/datasources
+ @mkdir -p $(HOME)/.repoachiever/workspace
+ @mkdir -p $(HOME)/.repoachiever/internal/database
+ @mkdir -p $(HOME)/.repoachiever/internal/state
+
+.PHONY: clone-client-config
+clone-client-config: ## Clone configuration files to local directory
+ @cp -r ./samples/config/client/user.yaml $(HOME)/.repoachiever/config
+
+.PHONY: clone-api-server-config
+clone-api-server-config: ## Clone RepoAchiever API Server configuration files to local directory
+ @cp -r ./config/grafana/dashboards/dashboard.yml $(HOME)/.repoachiever/diagnostics/grafana/config/dashboards
+ @cp -r ./config/grafana/dashboards/diagnostics.tmpl $(HOME)/.repoachiever/diagnostics/grafana/config/dashboards
+ @cp -r ./config/grafana/datasources/datasource.tmpl $(HOME)/.repoachiever/diagnostics/grafana/config/datasources
+ @cp -r ./config/prometheus/prometheus.tmpl $(HOME)/.repoachiever/diagnostics/prometheus/config
+ @cp -r ./samples/config/api-server/api-server.yaml $(HOME)/.repoachiever/config
+
+.PHONY: clone-worker
+clone-worker: ## Clone Worker JAR into a RepoAchiever local directory
+ifeq (,$(wildcard $(HOME)/.repoachiever/bin/worker))
+ @mkdir -p $(HOME)/.repoachiever/bin
+endif
+ @cp -r ./bin/worker $(HOME)/.repoachiever/bin/
+
+.PHONY: clone-cluster
+clone-cluster: ## Clone Cluster JAR into a RepoAchiever local directory
+ifeq (,$(wildcard $(HOME)/.repoachiever/bin/cluster))
+ @mkdir -p $(HOME)/.repoachiever/bin
+endif
+ @cp -r ./bin/cluster $(HOME)/.repoachiever/bin/
+
+.PHONY: clone-api-server
+clone-api-server: ## Clone API Server JAR into a RepoAchiever local directory
+ifeq (,$(wildcard $(HOME)/.repoachiever/bin/api-server))
+ @mkdir -p $(HOME)/.repoachiever/bin
+endif
+ @cp -r ./bin/api-server $(HOME)/.repoachiever/bin/
+
+.PHONY: build-worker
+build-worker: clean ## Build Worker application
+ifneq (,$(wildcard ./bin/worker))
+ @rm -r ./bin/worker
+endif
+ifeq ($(dev), 'false')
+ @mvn -pl worker -T10 install
+else
+ @mvn -P dev -pl worker -T10 install
+endif
+ $(MAKE) clone-worker
+
+.PHONY: build-cluster
+build-cluster: clean ## Build Cluster application
+ifneq (,$(wildcard ./bin/cluster))
+ @rm -r ./bin/cluster
+endif
+ifeq ($(dev), 'false')
+ @mvn -pl cluster -T10 install
+else
+ @mvn -P dev -pl cluster -T10 install
+endif
+ $(MAKE) clone-cluster
+
+.PHONY: build-api-server
+build-api-server: clean create-local-api-server clone-api-server-config ## Build API Server application
+ifneq (,$(wildcard ./bin/api-server))
+ @rm -r ./bin/api-server
+endif
+ifeq ($(dev), 'false')
+ @mvn -pl api-server -T10 install
+else
+ @mvn -P dev -pl api-server -T10 install
+endif
+ $(MAKE) clone-api-server
+
+.PHONY: build-cli
+build-cli: clean create-local-client clone-client-config ## Build CLI application
+ifneq (,$(wildcard ./bin/cli))
+ @rm -r ./bin/cli
+endif
+ifeq ($(dev), 'false')
+ @mvn -pl cli -T10 install
+else
+ @mvn -P dev -pl cli -T10 install
+endif
+
+.PHONY: build-gui
+build-gui: clean create-local-client clone-client-config create-local-api-server build-api-server ## Build GUI application
+ifneq (,$(wildcard ./bin/gui))
+ @rm -r ./bin/gui
+endif
+ifeq ($(dev), 'false')
+ @mvn -pl gui -T10 install
+else
+ @mvn -P dev -pl gui -T10 install
+endif
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2d3476b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,88 @@
+# RepoAchiever
+
+[![Build](https://github.com/YarikRevich/ResourceTracker/actions/workflows/build.yml/badge.svg)](https://github.com/YarikRevich/ResourceTracker/actions/workflows/build.yml)
+![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black)
+![MacOS](https://img.shields.io/badge/MacOS-8773f5?style=for-the-badge&logo=macos&logoColor=black)
+[![StandWithUkraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)
+
+## General Information
+
+A tool used for remote Git repositories achieving operations.
+
+![](./docs/high-level-design.png)
+
+![](./docs/detailed-design.png)
+
+![](./docs/workspace-design.png)
+
+RepoAchiever uses server-side data validation. It means, that on client side no data transformation or further analysis are made.
+Only API Server calls are on client side responsibility.
+
+## Setup
+
+All setup related operations are processed via **Makefile** placed in the root directory.
+
+### CLI
+
+In order to build CLI it's required to execute the following command. Initially it cleans the environment and builds Java project using **Maven**
+```shell
+make build-cli
+```
+
+After the execution of command given above the executable will be generated and placed into **bin** folder in the root directory of the project
+
+**CLI** build automatically places default **user.yaml** configuration file into ~/.resourcetracker/config directory.
+
+### GUI
+
+In order to build GUI it's required to execute the following command. Initially it cleans the environment and build Java project using **Maven**
+```shell
+make build-gui
+```
+
+After the execution of command given above the executable will be generated and placed into **bin** folder in the root directory of the project
+
+**GUI** build automatically compiles **API Server** and places both executable JAR and other dependencies into **~/.resourcetracker/bin/api-server** directory
+
+It's highly recommended not to move **API Server** files from the default local directory
+
+### API Server
+
+In order to build **API Server** it's required to execute the following command. Initially it cleans the environment and build Java project using **Maven**
+```shell
+make build-api-server
+```
+
+After the execution of command given above the executable will be generated and placed into **bin** folder in the root directory of the project
+
+## Use cases
+
+For both **CLI** and **GUI** examples, there was used the following user configuration file:
+```yaml
+requests:
+ - name: "first"
+ frequency: "10 * * * * *"
+ file: "/Volumes/Files/first.sh"
+cloud:
+ provider: "aws"
+ credentials:
+ file: "/Volumes/Files/aws.csv"
+ region: "us-west-2"
+api-server:
+ host: "http://localhost:8080"
+```
+
+And the following request script file:
+```shell
+#!/bin/bash
+
+echo "Hello world!"
+```
+
+### CLI
+
+![cli](./docs/example/cli.gif)
+
+### GUI
+
+![gui](./docs/example/gui.gif)
\ No newline at end of file
diff --git a/api-server/api-server.iml b/api-server/api-server.iml
new file mode 100644
index 0000000..258b38a
--- /dev/null
+++ b/api-server/api-server.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/api-server/pom.xml b/api-server/pom.xml
new file mode 100644
index 0000000..ee77cba
--- /dev/null
+++ b/api-server/pom.xml
@@ -0,0 +1,317 @@
+
+
+ 4.0.0
+ api-server
+ 1.0-SNAPSHOT
+ api-server
+ API Server for RepoAchiever
+
+
+ com.repoachiever
+ base
+ 1.0-SNAPSHOT
+
+
+
+
+
+ Shell-Command-Executor-Lib
+ Shell-Command-Executor-Lib
+ system
+ ${basedir}/../lib/Shell-Command-Executor-Lib-0.5.0-SNAPSHOT.jar
+
+
+
+
+ io.quarkus
+ quarkus-rest-client-jackson
+
+
+ io.quarkus
+ quarkus-rest-client
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-jackson
+
+
+ io.quarkus
+ quarkus-micrometer-registry-prometheus
+
+
+ io.quarkus
+ quarkus-vertx-http
+
+
+ io.quarkus
+ quarkus-vertx-http-deployment
+
+
+ io.quarkus
+ quarkus-smallrye-openapi
+
+
+ io.quarkus
+ quarkus-smallrye-health
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-junit5
+
+
+ io.quarkus
+ quarkus-hibernate-validator
+
+
+ io.quarkus
+ quarkus-liquibase
+
+
+ io.quarkus
+ quarkus-hibernate-orm-panache
+
+
+ io.quarkus
+ quarkus-agroal
+
+
+ io.quarkiverse.jdbc
+ quarkus-jdbc-sqlite
+
+
+
+
+ org.springframework
+ spring-core
+
+
+
+
+ io.pebbletemplates
+ pebble
+
+
+ org.jboss
+ jandex
+
+
+ org.jboss.logging
+ jboss-logging
+
+
+ jakarta.json
+ jakarta.json-api
+
+
+ jakarta.activation
+ jakarta.activation-api
+
+
+ jakarta.transaction
+ jakarta.transaction-api
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+
+
+ jakarta.validation
+ jakarta.validation-api
+
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+
+
+ org.projectlombok
+ lombok
+
+
+ org.osgi
+ org.osgi.annotation
+
+
+ org.yaml
+ snakeyaml
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+
+
+ commons-io
+ commons-io
+
+
+ com.opencsv
+ opencsv
+
+
+ org.jetbrains
+ annotations
+
+
+
+
+ org.freemarker
+ freemarker
+
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+ org.hibernate.validator
+ hibernate-validator
+
+
+
+
+ api-server
+
+
+ io.quarkus.platform
+ quarkus-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+
+
+ org.openapitools
+ openapi-generator-maven-plugin
+
+
+
+ generate
+
+
+ ${project.basedir}/../api-server/src/main/openapi/openapi.yml
+ jaxrs-spec
+ quarkus
+ ${default.package}.api
+ ${default.package}.model
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+
+ @lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+ src/main/java
+ true
+ true
+ true
+ true
+ true
+ false
+ true
+ java8
+ true
+ true
+
+
+
+
+
+
+ com.coderplus.maven.plugins
+ copy-rename-maven-plugin
+
+
+
+ ${basedir}/target/quarkus-app/quarkus-run.jar
+ ${main.basedir}/../bin/api-server/api-server.jar
+
+
+
+
+
+ pl.project13.maven
+ git-commit-id-plugin
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+ copy-quarkus-lib
+ package
+
+ exec
+
+
+ cp
+
+ -r
+ ${basedir}/target/quarkus-app/lib
+ ${main.basedir}/../bin/api-server/lib
+
+
+
+
+ copy-quarkus-quarkus
+ package
+
+ exec
+
+
+ cp
+
+ -r
+ ${basedir}/target/quarkus-app/quarkus
+ ${main.basedir}/../bin/api-server/quarkus
+
+
+
+
+ copy-quarkus-app
+ package
+
+ exec
+
+
+ cp
+
+ -r
+ ${basedir}/target/quarkus-app/app
+ ${main.basedir}/../bin/api-server/app
+
+
+
+
+
+
+
+
diff --git a/api-server/src/main/java/com/repoachiever/converter/ClusterContextToJsonConverter.java b/api-server/src/main/java/com/repoachiever/converter/ClusterContextToJsonConverter.java
new file mode 100644
index 0000000..dee85cc
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/converter/ClusterContextToJsonConverter.java
@@ -0,0 +1,31 @@
+package com.repoachiever.converter;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.repoachiever.entity.common.ClusterContextEntity;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Represents RepoAchiever Cluster context entity to JSON converter.
+ */
+public class ClusterContextToJsonConverter {
+ private static final Logger logger = LogManager.getLogger(ClusterContextToJsonConverter.class);
+
+ public static String convert(ClusterContextEntity content) {
+ ObjectMapper mapper =
+ new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, true)
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
+ .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
+
+ try {
+ return mapper.writeValueAsString(content);
+ } catch (JsonProcessingException e) {
+ logger.fatal(e.getMessage());
+ }
+
+ return null;
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/converter/ContentCredentialsToClusterContextCredentialsConverter.java b/api-server/src/main/java/com/repoachiever/converter/ContentCredentialsToClusterContextCredentialsConverter.java
new file mode 100644
index 0000000..ced666d
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/converter/ContentCredentialsToClusterContextCredentialsConverter.java
@@ -0,0 +1,18 @@
+package com.repoachiever.converter;
+
+import com.repoachiever.model.Provider;
+import com.repoachiever.model.CredentialsFieldsExternal;
+import com.repoachiever.entity.common.ClusterContextEntity;
+
+/**
+ * Represents RepoAchiever Cluster content credentials to context credentials converter.
+ */
+public class ContentCredentialsToClusterContextCredentialsConverter {
+ public static ClusterContextEntity.Service.Credentials convert(
+ Provider provider, CredentialsFieldsExternal credentialsFieldsExternal) {
+ return switch (provider) {
+ case LOCAL -> null;
+ case GITHUB -> ClusterContextEntity.Service.Credentials.of(credentialsFieldsExternal.getToken());
+ };
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/converter/ContentProviderToClusterContextProviderConverter.java b/api-server/src/main/java/com/repoachiever/converter/ContentProviderToClusterContextProviderConverter.java
new file mode 100644
index 0000000..cf7c977
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/converter/ContentProviderToClusterContextProviderConverter.java
@@ -0,0 +1,24 @@
+package com.repoachiever.converter;
+
+import com.repoachiever.entity.common.ClusterContextEntity;
+import com.repoachiever.model.Provider;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Represents RepoAchiever Cluster content provider to context provider converter.
+ */
+public class ContentProviderToClusterContextProviderConverter {
+ public static ClusterContextEntity.Service.Provider convert(Provider contentProvider) {
+ return ClusterContextEntity.Service.Provider.valueOf(
+ Arrays.stream(
+ ClusterContextEntity.Service.Provider.values())
+ .toList()
+ .stream()
+ .filter(element -> Objects.equals(element.toString(), contentProvider.toString()))
+ .map(Enum::name)
+ .toList()
+ .get(0));
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/converter/HealthCheckResponseToReadinessCheckResult.java b/api-server/src/main/java/com/repoachiever/converter/HealthCheckResponseToReadinessCheckResult.java
new file mode 100644
index 0000000..5619e82
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/converter/HealthCheckResponseToReadinessCheckResult.java
@@ -0,0 +1,44 @@
+package com.repoachiever.converter;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.repoachiever.model.ReadinessCheckResult;
+import java.io.IOException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.eclipse.microprofile.health.HealthCheckResponse;
+
+/**
+ * Represents health check response to readiness result converter.
+ */
+public class HealthCheckResponseToReadinessCheckResult {
+ private static final Logger logger =
+ LogManager.getLogger(HealthCheckResponseToReadinessCheckResult.class);
+
+ /**
+ * Converts given health check response to readiness check result entity.
+ *
+ * @param content given content to be converted.
+ * @return converted readiness check result entity.
+ */
+ public static ReadinessCheckResult convert(HealthCheckResponse content) {
+ ObjectMapper mapper = new ObjectMapper();
+
+ String contentRaw = null;
+ try {
+ contentRaw = mapper.writeValueAsString(content);
+ } catch (IOException e) {
+ logger.fatal(e.getMessage());
+ }
+
+ ObjectReader reader = mapper.reader().forType(new TypeReference() {});
+ try {
+ return reader.readValues(contentRaw).readAll().getFirst();
+ } catch (IOException e) {
+ logger.fatal(e.getMessage());
+ }
+
+ return null;
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/dto/ClusterAllocationDto.java b/api-server/src/main/java/com/repoachiever/dto/ClusterAllocationDto.java
new file mode 100644
index 0000000..9fb898d
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/dto/ClusterAllocationDto.java
@@ -0,0 +1,31 @@
+package com.repoachiever.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Represents RepoAchiever Cluster allocation.
+ */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class ClusterAllocationDto {
+ /**
+ * Represents name of RepoAchiever Cluster allocation.
+ */
+ private String name;
+
+ /**
+ * Represents process identificator of RepoAchiever Cluster allocation.
+ */
+ private Integer pid;
+
+ /**
+ * Represents context used for RepoAchiever Cluster configuration.
+ */
+ private String context;
+
+ /**
+ * Represents workspace unit key used to target RepoAchiever Cluster results.
+ */
+ private String workspaceUnitKey;
+}
diff --git a/api-server/src/main/java/com/repoachiever/dto/CommandExecutorOutputDto.java b/api-server/src/main/java/com/repoachiever/dto/CommandExecutorOutputDto.java
new file mode 100644
index 0000000..870ca95
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/dto/CommandExecutorOutputDto.java
@@ -0,0 +1,15 @@
+package com.repoachiever.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Represents command executor output.
+ */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class CommandExecutorOutputDto {
+ private String normalOutput;
+
+ private String errorOutput;
+}
diff --git a/api-server/src/main/java/com/repoachiever/dto/RepositoryContentUnitDto.java b/api-server/src/main/java/com/repoachiever/dto/RepositoryContentUnitDto.java
new file mode 100644
index 0000000..dd9633f
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/dto/RepositoryContentUnitDto.java
@@ -0,0 +1,20 @@
+package com.repoachiever.dto;
+
+import com.repoachiever.entity.common.ClusterContextEntity;
+import com.repoachiever.model.CredentialsFieldsFull;
+import com.repoachiever.model.Provider;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Represents repository content unit.
+ */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class RepositoryContentUnitDto {
+ private String location;
+
+ private Provider provider;
+
+ private CredentialsFieldsFull credentials;
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/entity/common/ClusterContextEntity.java b/api-server/src/main/java/com/repoachiever/entity/common/ClusterContextEntity.java
new file mode 100644
index 0000000..7a8d1aa
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/entity/common/ClusterContextEntity.java
@@ -0,0 +1,145 @@
+package com.repoachiever.entity.common;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Represents context sent to RepoAchiever Cluster as a variable during startup operation.
+ */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class ClusterContextEntity {
+ /**
+ * Contains metadata for a specific RepoAchiever Cluster allocation.
+ */
+ @AllArgsConstructor(staticName = "of")
+ public static class Metadata {
+ @JsonProperty("name")
+ public String name;
+
+ @JsonProperty("workspace_unit_key")
+ public String workspaceUnitKey;
+ }
+
+ @JsonProperty("metadata")
+ public Metadata metadata;
+
+ /**
+ * Represents filter section elected for a specific RepoAchiever Cluster allocation.
+ */
+ @AllArgsConstructor(staticName = "of")
+ public static class Filter {
+ @JsonProperty("locations")
+ public List locations;
+ }
+
+ @JsonProperty("filter")
+ public Filter filter;
+
+ /**
+ * Represents external service configurations for RepoAchiever Cluster allocation used to retrieve content.
+ */
+ @AllArgsConstructor(staticName = "of")
+ public static class Service {
+ /**
+ * Represents all supported service providers, which can be used by RepoAchiever Cluster allocation.
+ */
+ public enum Provider {
+ LOCAL("git-local"),
+ GITHUB("git-github");
+
+ private final String value;
+
+ Provider(String value) {
+ this.value = value;
+ }
+
+ public String toString() {
+ return value;
+ }
+ }
+
+ @JsonProperty("provider")
+ public Provider provider;
+
+ /**
+ * Represents credentials used for external service communication by RepoAchiever Cluster allocation.
+ */
+ @AllArgsConstructor(staticName = "of")
+ public static class Credentials {
+ @JsonProperty("token")
+ public String token;
+ }
+
+ @JsonProperty("credentials")
+ public Credentials credentials;
+ }
+
+ @JsonProperty("service")
+ public Service service;
+
+ /**
+ * Represents RepoAchiever Cluster configuration used for internal communication infrastructure setup.
+ */
+ @AllArgsConstructor(staticName = "of")
+ public static class Communication {
+ @NotNull
+ @JsonProperty("api_server_name")
+ public String apiServerName;
+
+ @JsonProperty("port")
+ public Integer port;
+ }
+
+ @JsonProperty("communication")
+ public Communication communication;
+
+ /**
+ * Represents RepoAchiever Cluster configuration used for content management.
+ */
+ @AllArgsConstructor(staticName = "of")
+ public static class Content {
+ @JsonProperty("format")
+ public String format;
+ }
+
+ @JsonProperty("content")
+ public Content content;
+
+ /**
+ * Represents RepoAchiever API Server resources configuration section.
+ */
+ @AllArgsConstructor(staticName = "of")
+ public static class Resource {
+ /**
+ * Represents RepoAchiever API Server configuration used for RepoAchiever Cluster.
+ */
+ @AllArgsConstructor(staticName = "of")
+ public static class Cluster {
+ @JsonProperty("max-workers")
+ public Integer maxWorkers;
+ }
+
+ @JsonProperty("cluster")
+ public Cluster cluster;
+
+ /**
+ * Represents RepoAchiever API Server configuration used for RepoAchiever Worker.
+ */
+ @AllArgsConstructor(staticName = "of")
+ public static class Worker {
+ @JsonProperty("frequency")
+ public String frequency;
+ }
+
+ @JsonProperty("worker")
+ public Worker worker;
+ }
+
+ @JsonProperty("resource")
+ public Resource resource;
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/entity/common/ConfigEntity.java b/api-server/src/main/java/com/repoachiever/entity/common/ConfigEntity.java
new file mode 100644
index 0000000..2410b66
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/entity/common/ConfigEntity.java
@@ -0,0 +1,197 @@
+package com.repoachiever.entity.common;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import lombok.Getter;
+
+/**
+ * Represents configuration model used for RepoAchiever API Server operations.
+ */
+@Getter
+@ApplicationScoped
+public class ConfigEntity {
+ /**
+ * Represents RepoAchiever API Server configuration used for RepoAchiever API Server instance setup.
+ */
+ @Getter
+ public static class Connection {
+ @NotNull
+ @JsonProperty("port")
+ public Integer port;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("connection")
+ public Connection connection;
+
+ /**
+ * Represents RepoAchiever API Server configuration used for internal communication infrastructure setup.
+ */
+ @Getter
+ public static class Communication {
+ @NotNull
+ @JsonProperty("port")
+ public Integer port;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("communication")
+ public Communication communication;
+
+ /**
+ * Represents RepoAchiever API Server configuration used for content management.
+ */
+ @Getter
+ public static class Content {
+ @NotNull
+ @Pattern(regexp = "(^zip$)|(^tar$)")
+ @JsonProperty("format")
+ public String format;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("content")
+ public Content content;
+
+ /**
+ * Represents RepoAchiever API Server configuration used for database setup.
+ */
+ @Getter
+ public static class Database {
+ @NotNull
+ @JsonProperty("re-init")
+ public Boolean reInit;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("database")
+ public Database database;
+
+ /**
+ * Represents RepoAchiever API Server configuration used for diagnostics.
+ */
+ @Getter
+ public static class Diagnostics {
+ @NotNull
+ @JsonProperty("enabled")
+ public Boolean enabled;
+
+ /**
+ * Represents RepoAchiever API Server metrics configuration setup.
+ */
+ @Getter
+ public static class Metrics {
+ @NotNull
+ @JsonProperty("port")
+ public Integer port;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("metrics")
+ public Metrics metrics;
+
+ /**
+ * Represents RepoAchiever API Server configuration used for Grafana instance setup.
+ */
+ @Getter
+ public static class Grafana {
+ @NotNull
+ @JsonProperty("port")
+ public Integer port;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("grafana")
+ public Grafana grafana;
+
+ /**
+ * Represents RepoAchiever API Server configuration used for Prometheus instance setup.
+ */
+ @Getter
+ public static class Prometheus {
+ @NotNull
+ @JsonProperty("port")
+ public Integer port;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("prometheus")
+ public Prometheus prometheus;
+
+ /**
+ * Represents RepoAchiever API Server configuration used for Prometheus Node Exporter instance setup.
+ */
+ @Getter
+ public static class NodeExporter {
+ @NotNull
+ @JsonProperty("port")
+ public Integer port;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("node-exporter")
+ public NodeExporter nodeExporter;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("diagnostics")
+ public Diagnostics diagnostics;
+
+ /**
+ * Represents RepoAchiever API Server resources configuration section.
+ */
+ @Getter
+ public static class Resource {
+ /**
+ * Represents RepoAchiever API Server configuration used for RepoAchiever Cluster.
+ */
+ @Getter
+ public static class Cluster {
+ @NotNull
+ @JsonProperty("max-workers")
+ public Integer maxWorkers;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("cluster")
+ public Cluster cluster;
+
+ /**
+ * Represents RepoAchiever API Server configuration used for RepoAchiever Worker.
+ */
+ @Getter
+ public static class Worker {
+ @NotNull
+ @Pattern(
+ regexp =
+ "(((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)|(([\\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))|([\\?])|([\\*]))[\\s](((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)|(([\\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))|([\\?])|([\\*]))[\\s](((([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?,)*([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?)|(([\\*]|[0-9]|[0-1][0-9]|[2][0-3])/([0-9]|[0-1][0-9]|[2][0-3]))|([\\?])|([\\*]))[\\s](((([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?,)*([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?(C)?)|(([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])/([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(C)?)|(L(-[0-9])?)|(L(-[1-2][0-9])?)|(L(-[3][0-1])?)|(LW)|([1-9]W)|([1-3][0-9]W)|([\\?])|([\\*]))[\\s](((([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?,)*([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?)|(([1-9]|0[1-9]|1[0-2])/([1-9]|0[1-9]|1[0-2]))|(((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?,)*(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)|((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)/(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))|([\\?])|([\\*]))[\\s]((([1-7](-([1-7]))?,)*([1-7])(-([1-7]))?)|([1-7]/([1-7]))|(((MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?,)*(MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?(C)?)|((MON|TUE|WED|THU|FRI|SAT|SUN)/(MON|TUE|WED|THU|FRI|SAT|SUN)(C)?)|(([1-7]|(MON|TUE|WED|THU|FRI|SAT|SUN))?(L|LW)?)|(([1-7]|MON|TUE|WED|THU|FRI|SAT|SUN)#([1-7])?)|([\\?])|([\\*]))([\\s]?(([\\*])?|(19[7-9][0-9])|(20[0-9][0-9]))?|"
+ + " (((19[7-9][0-9])|(20[0-9][0-9]))/((19[7-9][0-9])|(20[0-9][0-9])))?|"
+ + " ((((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?,)*((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?)?)")
+ @JsonProperty("frequency")
+ public String frequency;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("worker")
+ public Worker worker;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("resource")
+ public Resource resource;
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/entity/common/MetadataFileEntity.java b/api-server/src/main/java/com/repoachiever/entity/common/MetadataFileEntity.java
new file mode 100644
index 0000000..330715c
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/entity/common/MetadataFileEntity.java
@@ -0,0 +1,20 @@
+package com.repoachiever.entity.common;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+/**
+ * Represents metadata file, including pull requests, issues and releases, which are pulled from selected resource provider.
+ */
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor(staticName = "of")
+public class MetadataFileEntity {
+ @JsonProperty("data")
+ private String data;
+
+ @JsonProperty("hash")
+ private String hash;
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/entity/common/PropertiesEntity.java b/api-server/src/main/java/com/repoachiever/entity/common/PropertiesEntity.java
new file mode 100644
index 0000000..24bbb98
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/entity/common/PropertiesEntity.java
@@ -0,0 +1,166 @@
+package com.repoachiever.entity.common;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import lombok.Getter;
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+/**
+ * Exposes access to properties setup to be used for further configuration.
+ */
+@Getter
+@ApplicationScoped
+public class PropertiesEntity {
+ @ConfigProperty(name = "quarkus.application.version")
+ String applicationVersion;
+
+ @ConfigProperty(name = "quarkus.http.host")
+ String applicationHost;
+
+ @ConfigProperty(name = "quarkus.http.port")
+ Integer applicationPort;
+
+ @ConfigProperty(name = "state.location")
+ String stateLocation;
+
+ @ConfigProperty(name = "state.running.name")
+ String stateRunningName;
+
+ @ConfigProperty(name = "database.tables.config.name")
+ String databaseConfigTableName;
+
+ @ConfigProperty(name = "database.tables.content.name")
+ String databaseContentTableName;
+
+ @ConfigProperty(name = "database.tables.provider.name")
+ String databaseProviderTableName;
+
+ @ConfigProperty(name = "database.tables.secret.name")
+ String databaseSecretTableName;
+
+ @ConfigProperty(name = "database.statement.close-delay")
+ Integer databaseStatementCloseDelay;
+
+ @ConfigProperty(name = "bin.directory")
+ String binDirectory;
+
+ @ConfigProperty(name = "bin.cluster.location")
+ String binClusterLocation;
+
+ @ConfigProperty(name = "config.directory")
+ String configDirectory;
+
+ @ConfigProperty(name = "config.name")
+ String configName;
+
+ @ConfigProperty(name = "workspace.directory")
+ String workspaceDirectory;
+
+ @ConfigProperty(name = "workspace.content.directory")
+ String workspaceContentDirectory;
+
+ @ConfigProperty(name = "workspace.content.version-amount")
+ Integer workspaceContentVersionAmount;
+
+ @ConfigProperty(name = "workspace.metadata.directory")
+ String workspaceMetadataDirectory;
+
+ @ConfigProperty(name = "workspace.prs-metadata-file.name")
+ String workspacePRsMetadataFileName;
+
+ @ConfigProperty(name = "workspace.issues-metadata-file.name")
+ String workspaceIssuesMetadataFileName;
+
+ @ConfigProperty(name = "workspace.releases-metadata-file.name")
+ String workspaceReleasesMetadataFileName;
+
+ @ConfigProperty(name = "repoachiever-cluster.context.alias")
+ String clusterContextAlias;
+
+ @ConfigProperty(name = "communication.api-server.name")
+ String communicationApiServerName;
+
+ @ConfigProperty(name = "communication.cluster.base")
+ String communicationClusterBase;
+
+ @ConfigProperty(name = "communication.cluster.startup-await-frequency")
+ Integer communicationClusterStartupAwaitFrequency;
+
+ @ConfigProperty(name = "communication.cluster.startup-timeout")
+ Integer communicationClusterStartupTimeout;
+
+ @ConfigProperty(name = "communication.cluster.health-check-frequency")
+ Integer communicationClusterHealthCheckFrequency;
+
+ @ConfigProperty(name = "diagnostics.common.docker.network.name")
+ String diagnosticsCommonDockerNetworkName;
+
+ @ConfigProperty(name = "diagnostics.grafana.config.location")
+ String diagnosticsGrafanaConfigLocation;
+
+ @ConfigProperty(name = "diagnostics.grafana.datasources.location")
+ String diagnosticsGrafanaDatasourcesLocation;
+
+ @ConfigProperty(name = "diagnostics.grafana.datasources.template")
+ String diagnosticsGrafanaDatasourcesTemplate;
+
+ @ConfigProperty(name = "diagnostics.grafana.datasources.output")
+ String diagnosticsGrafanaDatasourcesOutput;
+
+ @ConfigProperty(name = "diagnostics.grafana.dashboards.location")
+ String diagnosticsGrafanaDashboardsLocation;
+
+ @ConfigProperty(name = "diagnostics.grafana.dashboards.diagnostics.template")
+ String diagnosticsGrafanaDashboardsDiagnosticsTemplate;
+
+ @ConfigProperty(name = "diagnostics.grafana.dashboards.diagnostics.output")
+ String diagnosticsGrafanaDashboardsDiagnosticsOutput;
+
+ @ConfigProperty(name = "diagnostics.grafana.internal.location")
+ String diagnosticsGrafanaInternalLocation;
+
+ @ConfigProperty(name = "diagnostics.grafana.docker.name")
+ String diagnosticsGrafanaDockerName;
+
+ @ConfigProperty(name = "diagnostics.grafana.docker.image")
+ String diagnosticsGrafanaDockerImage;
+
+ @ConfigProperty(name = "diagnostics.prometheus.config.location")
+ String diagnosticsPrometheusConfigLocation;
+
+ @ConfigProperty(name = "diagnostics.prometheus.config.template")
+ String diagnosticsPrometheusConfigTemplate;
+
+ @ConfigProperty(name = "diagnostics.prometheus.config.output")
+ String diagnosticsPrometheusConfigOutput;
+
+ @ConfigProperty(name = "diagnostics.prometheus.internal.location")
+ String diagnosticsPrometheusInternalLocation;
+
+ @ConfigProperty(name = "diagnostics.prometheus.docker.name")
+ String diagnosticsPrometheusDockerName;
+
+ @ConfigProperty(name = "diagnostics.prometheus.docker.image")
+ String diagnosticsPrometheusDockerImage;
+
+ @ConfigProperty(name = "diagnostics.prometheus.node-exporter.docker.name")
+ String diagnosticsPrometheusNodeExporterDockerName;
+
+ @ConfigProperty(name = "diagnostics.prometheus.node-exporter.docker.image")
+ String diagnosticsPrometheusNodeExporterDockerImage;
+
+ @ConfigProperty(name = "diagnostics.metrics.connection.timeout")
+ Integer diagnosticsMetricsConnectionTimeout;
+
+ @ConfigProperty(name = "git.commit.id.abbrev")
+ String gitCommitId;
+
+ /**
+ * Removes the last symbol in git commit id of the repository.
+ *
+ * @return chopped repository git commit id.
+ */
+ public String getGitCommitId() {
+ return StringUtils.chop(gitCommitId);
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/entity/repository/ConfigEntity.java b/api-server/src/main/java/com/repoachiever/entity/repository/ConfigEntity.java
new file mode 100644
index 0000000..f615fdb
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/entity/repository/ConfigEntity.java
@@ -0,0 +1,22 @@
+package com.repoachiever.entity.repository;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.Id;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Represents entity used to describe added configuration.
+ */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class ConfigEntity {
+ private Integer id;
+
+ private String name;
+
+ private String hash;
+}
diff --git a/api-server/src/main/java/com/repoachiever/entity/repository/ContentEntity.java b/api-server/src/main/java/com/repoachiever/entity/repository/ContentEntity.java
new file mode 100644
index 0000000..df8acf5
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/entity/repository/ContentEntity.java
@@ -0,0 +1,20 @@
+package com.repoachiever.entity.repository;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Represents entity used to describe registered locations.
+ */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class ContentEntity {
+ private Integer id;
+
+ private String location;
+
+ private Integer provider;
+
+ private Integer secret;
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/entity/repository/ProviderEntity.java b/api-server/src/main/java/com/repoachiever/entity/repository/ProviderEntity.java
new file mode 100644
index 0000000..45b69e1
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/entity/repository/ProviderEntity.java
@@ -0,0 +1,15 @@
+package com.repoachiever.entity.repository;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Represents entity used to describe available providers.
+ */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class ProviderEntity {
+ private Integer id;
+
+ private String name;
+}
diff --git a/api-server/src/main/java/com/repoachiever/entity/repository/SecretEntity.java b/api-server/src/main/java/com/repoachiever/entity/repository/SecretEntity.java
new file mode 100644
index 0000000..1848a3f
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/entity/repository/SecretEntity.java
@@ -0,0 +1,23 @@
+package com.repoachiever.entity.repository;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.Id;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Optional;
+
+/**
+ * Represents entity used to describe registered secrets.
+ */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class SecretEntity {
+ private Integer id;
+
+ private Integer session;
+
+ private Optional credentials;
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/ApiServerInstanceIsAlreadyRunningException.java b/api-server/src/main/java/com/repoachiever/exception/ApiServerInstanceIsAlreadyRunningException.java
new file mode 100644
index 0000000..af511da
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/ApiServerInstanceIsAlreadyRunningException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when RepoAchiever API Server is already running.
+ */
+public class ApiServerInstanceIsAlreadyRunningException extends IOException {
+ public ApiServerInstanceIsAlreadyRunningException() {
+ this("");
+ }
+
+ public ApiServerInstanceIsAlreadyRunningException(Object... message) {
+ super(
+ new Formatter()
+ .format("RepoAchiever API Server instance is already running: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/ClusterApplicationFailureException.java b/api-server/src/main/java/com/repoachiever/exception/ClusterApplicationFailureException.java
new file mode 100644
index 0000000..61026f4
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/ClusterApplicationFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when RepoAchiever Cluster application fails.
+ */
+public class ClusterApplicationFailureException extends IOException {
+ public ClusterApplicationFailureException() {
+ this("");
+ }
+
+ public ClusterApplicationFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("RepoAchiever Cluster application failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/ClusterApplicationTimeoutException.java b/api-server/src/main/java/com/repoachiever/exception/ClusterApplicationTimeoutException.java
new file mode 100644
index 0000000..c85b75c
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/ClusterApplicationTimeoutException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when RepoAchiever Cluster application received timeout.
+ */
+public class ClusterApplicationTimeoutException extends IOException {
+ public ClusterApplicationTimeoutException() {
+ this("");
+ }
+
+ public ClusterApplicationTimeoutException(Object... message) {
+ super(
+ new Formatter()
+ .format("RepoAchiever Cluster application received timeout: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/ClusterDeploymentFailureException.java b/api-server/src/main/java/com/repoachiever/exception/ClusterDeploymentFailureException.java
new file mode 100644
index 0000000..ed00608
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/ClusterDeploymentFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when RepoAchiever Cluster deployment fails.
+ */
+public class ClusterDeploymentFailureException extends IOException {
+ public ClusterDeploymentFailureException() {
+ this("");
+ }
+
+ public ClusterDeploymentFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("RepoAchiever Cluster deployment failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/ClusterDestructionFailureException.java b/api-server/src/main/java/com/repoachiever/exception/ClusterDestructionFailureException.java
new file mode 100644
index 0000000..bb184e8
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/ClusterDestructionFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when RepoAchiever Cluster destruction fails.
+ */
+public class ClusterDestructionFailureException extends IOException {
+ public ClusterDestructionFailureException() {
+ this("");
+ }
+
+ public ClusterDestructionFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("RepoAchiever Cluster destruction failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/ClusterFullDestructionFailureException.java b/api-server/src/main/java/com/repoachiever/exception/ClusterFullDestructionFailureException.java
new file mode 100644
index 0000000..6e0e55d
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/ClusterFullDestructionFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when RepoAchiever Cluster full destruction fails.
+ */
+public class ClusterFullDestructionFailureException extends IOException {
+ public ClusterFullDestructionFailureException() {
+ this("");
+ }
+
+ public ClusterFullDestructionFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("RepoAchiever Cluster full destruction failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/ClusterOperationFailureException.java b/api-server/src/main/java/com/repoachiever/exception/ClusterOperationFailureException.java
new file mode 100644
index 0000000..773ece1
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/ClusterOperationFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when RepoAchiever Cluster operation fails.
+ */
+public class ClusterOperationFailureException extends IOException {
+ public ClusterOperationFailureException() {
+ this("");
+ }
+
+ public ClusterOperationFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("RepoAchiever Cluster operation failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/ClusterRecreationFailureException.java b/api-server/src/main/java/com/repoachiever/exception/ClusterRecreationFailureException.java
new file mode 100644
index 0000000..af0c319
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/ClusterRecreationFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when RepoAchiever Cluster recreation operation fails.
+ */
+public class ClusterRecreationFailureException extends IOException {
+ public ClusterRecreationFailureException() {
+ this("");
+ }
+
+ public ClusterRecreationFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("RepoAchiever Cluster recreation operation failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/ClusterUnhealthyReapplicationFailureException.java b/api-server/src/main/java/com/repoachiever/exception/ClusterUnhealthyReapplicationFailureException.java
new file mode 100644
index 0000000..07556be
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/ClusterUnhealthyReapplicationFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when RepoAchiever Cluster unhealthy allocation reapplication fails.
+ */
+public class ClusterUnhealthyReapplicationFailureException extends IOException {
+ public ClusterUnhealthyReapplicationFailureException() {
+ this("");
+ }
+
+ public ClusterUnhealthyReapplicationFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("RepoAchiever Cluster unhealthy allocation reapplication failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/ClusterWithdrawalFailureException.java b/api-server/src/main/java/com/repoachiever/exception/ClusterWithdrawalFailureException.java
new file mode 100644
index 0000000..ce699d5
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/ClusterWithdrawalFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when RepoAchiever Cluster withdrawal fails.
+ */
+public class ClusterWithdrawalFailureException extends IOException {
+ public ClusterWithdrawalFailureException() {
+ this("");
+ }
+
+ public ClusterWithdrawalFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("RepoAchiever Cluster withdrawal failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/CommandExecutorException.java b/api-server/src/main/java/com/repoachiever/exception/CommandExecutorException.java
new file mode 100644
index 0000000..6cf46ee
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/CommandExecutorException.java
@@ -0,0 +1,17 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when command execution process fails.
+ */
+public class CommandExecutorException extends IOException {
+ public CommandExecutorException(Object... message) {
+ super(
+ new Formatter()
+ .format("Invalid command executor behaviour: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/CommunicationConfigurationFailureException.java b/api-server/src/main/java/com/repoachiever/exception/CommunicationConfigurationFailureException.java
new file mode 100644
index 0000000..1f09d8e
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/CommunicationConfigurationFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when communication configuration process fails.
+ */
+public class CommunicationConfigurationFailureException extends IOException {
+ public CommunicationConfigurationFailureException() {
+ this("");
+ }
+
+ public CommunicationConfigurationFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Communication configuration operation failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/exception/ConfigValidationException.java b/api-server/src/main/java/com/repoachiever/exception/ConfigValidationException.java
new file mode 100644
index 0000000..a8ffa9d
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/ConfigValidationException.java
@@ -0,0 +1,17 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when configuration file validation process fails.
+ */
+public class ConfigValidationException extends IOException {
+ public ConfigValidationException(Object... message) {
+ super(
+ new Formatter()
+ .format("Config file content is not valid: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/ContentApplicationRetrievalFailureException.java b/api-server/src/main/java/com/repoachiever/exception/ContentApplicationRetrievalFailureException.java
new file mode 100644
index 0000000..79961d4
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/ContentApplicationRetrievalFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when content application retrieval process fails.
+ */
+public class ContentApplicationRetrievalFailureException extends IOException {
+ public ContentApplicationRetrievalFailureException() {
+ this("");
+ }
+
+ public ContentApplicationRetrievalFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Content application retrieval failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/ContentFileNotFoundException.java b/api-server/src/main/java/com/repoachiever/exception/ContentFileNotFoundException.java
new file mode 100644
index 0000000..290cd6d
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/ContentFileNotFoundException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when configuration file content was not found.
+ */
+public class ContentFileNotFoundException extends IOException {
+ public ContentFileNotFoundException() {
+ this("");
+ }
+
+ public ContentFileNotFoundException(Object... message) {
+ super(
+ new Formatter()
+ .format("Content file is not found: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/ContentFileRemovalFailureException.java b/api-server/src/main/java/com/repoachiever/exception/ContentFileRemovalFailureException.java
new file mode 100644
index 0000000..576f488
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/ContentFileRemovalFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when configuration file content removal operation fails.
+ */
+public class ContentFileRemovalFailureException extends IOException {
+ public ContentFileRemovalFailureException() {
+ this("");
+ }
+
+ public ContentFileRemovalFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Content file removal failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/exception/ContentFileWriteFailureException.java b/api-server/src/main/java/com/repoachiever/exception/ContentFileWriteFailureException.java
new file mode 100644
index 0000000..d447974
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/ContentFileWriteFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when content file write operation fails.
+ */
+public class ContentFileWriteFailureException extends IOException {
+ public ContentFileWriteFailureException() {
+ this("");
+ }
+
+ public ContentFileWriteFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Content file write failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/CredentialsAreNotValidException.java b/api-server/src/main/java/com/repoachiever/exception/CredentialsAreNotValidException.java
new file mode 100644
index 0000000..b414463
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/CredentialsAreNotValidException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when given credentials are not valid.
+ */
+public class CredentialsAreNotValidException extends IOException {
+ public CredentialsAreNotValidException() {
+ this("");
+ }
+
+ public CredentialsAreNotValidException(Object... message) {
+ super(
+ new Formatter()
+ .format("Credentials are not valid: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/CredentialsConversionException.java b/api-server/src/main/java/com/repoachiever/exception/CredentialsConversionException.java
new file mode 100644
index 0000000..791a3f1
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/CredentialsConversionException.java
@@ -0,0 +1,17 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when credentials conversion process fails.
+ */
+public class CredentialsConversionException extends IOException {
+ public CredentialsConversionException(Object... message) {
+ super(
+ new Formatter()
+ .format("Given credentials are invalid: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/CredentialsFieldIsNotValidException.java b/api-server/src/main/java/com/repoachiever/exception/CredentialsFieldIsNotValidException.java
new file mode 100644
index 0000000..84cef1e
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/CredentialsFieldIsNotValidException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when credentials field is not valid.
+ */
+public class CredentialsFieldIsNotValidException extends IOException {
+ public CredentialsFieldIsNotValidException() {
+ this("");
+ }
+
+ public CredentialsFieldIsNotValidException(Object... message) {
+ super(
+ new Formatter()
+ .format("Credentials field is not valid: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/DiagnosticsTemplateProcessingFailureException.java b/api-server/src/main/java/com/repoachiever/exception/DiagnosticsTemplateProcessingFailureException.java
new file mode 100644
index 0000000..1691337
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/DiagnosticsTemplateProcessingFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when diagnostics template processing fails.
+ */
+public class DiagnosticsTemplateProcessingFailureException extends IOException {
+ public DiagnosticsTemplateProcessingFailureException() {
+ this("");
+ }
+
+ public DiagnosticsTemplateProcessingFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Diagnostics template processing failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/DockerInspectRemovalFailureException.java b/api-server/src/main/java/com/repoachiever/exception/DockerInspectRemovalFailureException.java
new file mode 100644
index 0000000..dda5a43
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/DockerInspectRemovalFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when Docker inspection removal process fails.
+ */
+public class DockerInspectRemovalFailureException extends IOException {
+ public DockerInspectRemovalFailureException() {
+ this("");
+ }
+
+ public DockerInspectRemovalFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Docker inspect container removal failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/DockerIsNotAvailableException.java b/api-server/src/main/java/com/repoachiever/exception/DockerIsNotAvailableException.java
new file mode 100644
index 0000000..c98a154
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/DockerIsNotAvailableException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when Docker availability check fails.
+ */
+public class DockerIsNotAvailableException extends IOException {
+ public DockerIsNotAvailableException() {
+ this("");
+ }
+
+ public DockerIsNotAvailableException(Object... message) {
+ super(
+ new Formatter()
+ .format("Docker instance is not available: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/DockerNetworkCreateFailureException.java b/api-server/src/main/java/com/repoachiever/exception/DockerNetworkCreateFailureException.java
new file mode 100644
index 0000000..21640c9
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/DockerNetworkCreateFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when Docker network creation fails.
+ */
+public class DockerNetworkCreateFailureException extends IOException {
+ public DockerNetworkCreateFailureException() {
+ this("");
+ }
+
+ public DockerNetworkCreateFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Docker network creation failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/DockerNetworkRemoveFailureException.java b/api-server/src/main/java/com/repoachiever/exception/DockerNetworkRemoveFailureException.java
new file mode 100644
index 0000000..cf55e3a
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/DockerNetworkRemoveFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when Docker network removal fails.
+ */
+public class DockerNetworkRemoveFailureException extends IOException {
+ public DockerNetworkRemoveFailureException() {
+ this("");
+ }
+
+ public DockerNetworkRemoveFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Docker network removal failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/exception/LocationsFieldIsNotValidException.java b/api-server/src/main/java/com/repoachiever/exception/LocationsFieldIsNotValidException.java
new file mode 100644
index 0000000..1807859
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/LocationsFieldIsNotValidException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when locations field is not valid.
+ */
+public class LocationsFieldIsNotValidException extends IOException {
+ public LocationsFieldIsNotValidException() {
+ this("");
+ }
+
+ public LocationsFieldIsNotValidException(Object... message) {
+ super(
+ new Formatter()
+ .format("Locations field is not valid: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/exception/MetadataFileNotFoundException.java b/api-server/src/main/java/com/repoachiever/exception/MetadataFileNotFoundException.java
new file mode 100644
index 0000000..069b3fe
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/MetadataFileNotFoundException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when metadata file was not found.
+ */
+public class MetadataFileNotFoundException extends IOException {
+ public MetadataFileNotFoundException() {
+ this("");
+ }
+
+ public MetadataFileNotFoundException(Object... message) {
+ super(
+ new Formatter()
+ .format("Metadata file is not found: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/MetadataFileWriteFailureException.java b/api-server/src/main/java/com/repoachiever/exception/MetadataFileWriteFailureException.java
new file mode 100644
index 0000000..4747254
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/MetadataFileWriteFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when metadata file write operation fails.
+ */
+public class MetadataFileWriteFailureException extends IOException {
+ public MetadataFileWriteFailureException() {
+ this("");
+ }
+
+ public MetadataFileWriteFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Metadata file write failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/NodeExporterDeploymentFailureException.java b/api-server/src/main/java/com/repoachiever/exception/NodeExporterDeploymentFailureException.java
new file mode 100644
index 0000000..d1c82cd
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/NodeExporterDeploymentFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when Prometheus Node Exporter deployment operation fails.
+ */
+public class NodeExporterDeploymentFailureException extends IOException {
+ public NodeExporterDeploymentFailureException() {
+ this("");
+ }
+
+ public NodeExporterDeploymentFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Prometheus node exporter deployment failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/PrometheusDeploymentFailureException.java b/api-server/src/main/java/com/repoachiever/exception/PrometheusDeploymentFailureException.java
new file mode 100644
index 0000000..2e34f8c
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/PrometheusDeploymentFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when Prometheus deployment operation fails.
+ */
+public class PrometheusDeploymentFailureException extends IOException {
+ public PrometheusDeploymentFailureException() {
+ this("");
+ }
+
+ public PrometheusDeploymentFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Prometheus deployment failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/exception/QueryEmptyResultException.java b/api-server/src/main/java/com/repoachiever/exception/QueryEmptyResultException.java
new file mode 100644
index 0000000..4f0bb31
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/QueryEmptyResultException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when database query returns empty result.
+ */
+public class QueryEmptyResultException extends IOException {
+ public QueryEmptyResultException() {
+ this("");
+ }
+
+ public QueryEmptyResultException(Object... message) {
+ super(
+ new Formatter()
+ .format("Query result is empty: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/QueryExecutionFailureException.java b/api-server/src/main/java/com/repoachiever/exception/QueryExecutionFailureException.java
new file mode 100644
index 0000000..7d90486
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/QueryExecutionFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when database query execution operation fails.
+ */
+public class QueryExecutionFailureException extends IOException {
+ public QueryExecutionFailureException() {
+ this("");
+ }
+
+ public QueryExecutionFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Query execution failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/RepositoryContentApplicationFailureException.java b/api-server/src/main/java/com/repoachiever/exception/RepositoryContentApplicationFailureException.java
new file mode 100644
index 0000000..fc73002
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/RepositoryContentApplicationFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when repository content application operation fails.
+ */
+public class RepositoryContentApplicationFailureException extends IOException {
+ public RepositoryContentApplicationFailureException() {
+ this("");
+ }
+
+ public RepositoryContentApplicationFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("RepoAchiever Cluster repository content application failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/RepositoryContentDestructionFailureException.java b/api-server/src/main/java/com/repoachiever/exception/RepositoryContentDestructionFailureException.java
new file mode 100644
index 0000000..4987054
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/RepositoryContentDestructionFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when repository content destruction operation fails.
+ */
+public class RepositoryContentDestructionFailureException extends IOException {
+ public RepositoryContentDestructionFailureException() {
+ this("");
+ }
+
+ public RepositoryContentDestructionFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("RepoAchiever Cluster repository content destruction failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/RepositoryOperationFailureException.java b/api-server/src/main/java/com/repoachiever/exception/RepositoryOperationFailureException.java
new file mode 100644
index 0000000..e43e3cf
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/RepositoryOperationFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when repository operation fails.
+ */
+public class RepositoryOperationFailureException extends IOException {
+ public RepositoryOperationFailureException() {
+ this("");
+ }
+
+ public RepositoryOperationFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Repository operation failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/exception/TelemetryOperationFailureException.java b/api-server/src/main/java/com/repoachiever/exception/TelemetryOperationFailureException.java
new file mode 100644
index 0000000..3345723
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/TelemetryOperationFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when telemetry operation fails.
+ */
+public class TelemetryOperationFailureException extends IOException {
+ public TelemetryOperationFailureException() {
+ this("");
+ }
+
+ public TelemetryOperationFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Telemetry operation failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/exception/WorkspaceContentDirectoryCreationFailureException.java b/api-server/src/main/java/com/repoachiever/exception/WorkspaceContentDirectoryCreationFailureException.java
new file mode 100644
index 0000000..5cca97b
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/WorkspaceContentDirectoryCreationFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when workspace content directory creation operation fails.
+ */
+public class WorkspaceContentDirectoryCreationFailureException extends IOException {
+ public WorkspaceContentDirectoryCreationFailureException() {
+ this("");
+ }
+
+ public WorkspaceContentDirectoryCreationFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Workspace content directory creation failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryCreationFailureException.java b/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryCreationFailureException.java
new file mode 100644
index 0000000..18f1c49
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryCreationFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when workspace unit directory creation operation fails.
+ */
+public class WorkspaceUnitDirectoryCreationFailureException extends IOException {
+ public WorkspaceUnitDirectoryCreationFailureException() {
+ this("");
+ }
+
+ public WorkspaceUnitDirectoryCreationFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Workspace unit directory creation failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryNotFoundException.java b/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryNotFoundException.java
new file mode 100644
index 0000000..e9502b5
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryNotFoundException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.nio.file.NoSuchFileException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when workspace unit directory was not found.
+ */
+public class WorkspaceUnitDirectoryNotFoundException extends NoSuchFileException {
+ public WorkspaceUnitDirectoryNotFoundException() {
+ this("");
+ }
+
+ public WorkspaceUnitDirectoryNotFoundException(Object... message) {
+ super(
+ new Formatter()
+ .format("Workspace unit is not found: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryPresentException.java b/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryPresentException.java
new file mode 100644
index 0000000..0a07207
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryPresentException.java
@@ -0,0 +1,23 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when workspace unit directory is already present.
+ */
+public class WorkspaceUnitDirectoryPresentException extends IOException {
+ public WorkspaceUnitDirectoryPresentException() {
+ this("");
+ }
+
+ public WorkspaceUnitDirectoryPresentException(Object... message) {
+ super(
+ new Formatter()
+ .format(
+ "Workspace unit is already present, please stop current deployment first: %s",
+ Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryRemovalFailureException.java b/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryRemovalFailureException.java
new file mode 100644
index 0000000..6ee978a
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryRemovalFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when workspace unit directory removal operation fails.
+ */
+public class WorkspaceUnitDirectoryRemovalFailureException extends IOException {
+ public WorkspaceUnitDirectoryRemovalFailureException() {
+ this("");
+ }
+
+ public WorkspaceUnitDirectoryRemovalFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Workspace unit directory removal failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/logging/FatalAppender.java b/api-server/src/main/java/com/repoachiever/logging/FatalAppender.java
new file mode 100644
index 0000000..a88883f
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/logging/FatalAppender.java
@@ -0,0 +1,36 @@
+package com.repoachiever.logging;
+
+import io.quarkus.runtime.Quarkus;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * Service used for logging fatal level application state changes.
+ */
+@Plugin(name = "FatalAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
+public class FatalAppender extends AbstractAppender {
+ protected FatalAppender(String name, Filter filter) {
+ super(name, filter, null, false, null);
+ }
+
+ @PluginFactory
+ public static FatalAppender createAppender(
+ @PluginAttribute("name") String name, @PluginElement("Filter") Filter filter) {
+ return new FatalAppender(name, filter);
+ }
+
+ @Override
+ public void append(LogEvent event) {
+ if (event.getLevel().equals(Level.FATAL)) {
+ Quarkus.asyncExit(1);
+ }
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/mapping/ClusterApplicationFailureExceptionMapper.java b/api-server/src/main/java/com/repoachiever/mapping/ClusterApplicationFailureExceptionMapper.java
new file mode 100644
index 0000000..bbebf78
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/mapping/ClusterApplicationFailureExceptionMapper.java
@@ -0,0 +1,20 @@
+package com.repoachiever.mapping;
+
+import com.repoachiever.exception.ClusterApplicationFailureException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+
+/**
+ * Represents mapper for ClusterApplicationFailureException exception.
+ */
+@Provider
+public class ClusterApplicationFailureExceptionMapper
+ implements ExceptionMapper {
+ @Override
+ public Response toResponse(ClusterApplicationFailureException e) {
+ return Response.status(Response.Status.BAD_REQUEST.getStatusCode())
+ .entity(e.getMessage())
+ .build();
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/mapping/ClusterWithdrawalFailureExceptionMapper.java b/api-server/src/main/java/com/repoachiever/mapping/ClusterWithdrawalFailureExceptionMapper.java
new file mode 100644
index 0000000..616f84a
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/mapping/ClusterWithdrawalFailureExceptionMapper.java
@@ -0,0 +1,20 @@
+package com.repoachiever.mapping;
+
+import com.repoachiever.exception.ClusterWithdrawalFailureException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+
+/**
+ * Represents mapper for ClusterWithdrawalFailureExceptionMapper exception.
+ */
+@Provider
+public class ClusterWithdrawalFailureExceptionMapper
+ implements ExceptionMapper {
+ @Override
+ public Response toResponse(ClusterWithdrawalFailureException e) {
+ return Response.status(Response.Status.BAD_REQUEST.getStatusCode())
+ .entity(e.getMessage())
+ .build();
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/mapping/CredentialsAreNotValidExceptionMapper.java b/api-server/src/main/java/com/repoachiever/mapping/CredentialsAreNotValidExceptionMapper.java
new file mode 100644
index 0000000..20e15ae
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/mapping/CredentialsAreNotValidExceptionMapper.java
@@ -0,0 +1,18 @@
+package com.repoachiever.mapping;
+
+import com.repoachiever.exception.CredentialsAreNotValidException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+
+/** Represents mapper for CredentialsAreNotValidException exception. */
+@Provider
+public class CredentialsAreNotValidExceptionMapper
+ implements ExceptionMapper {
+ @Override
+ public Response toResponse(CredentialsAreNotValidException e) {
+ return Response.status(Response.Status.BAD_REQUEST.getStatusCode())
+ .entity(e.getMessage())
+ .build();
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/mapping/CredentialsFieldIsNotValidExceptionMapper.java b/api-server/src/main/java/com/repoachiever/mapping/CredentialsFieldIsNotValidExceptionMapper.java
new file mode 100644
index 0000000..ae816bd
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/mapping/CredentialsFieldIsNotValidExceptionMapper.java
@@ -0,0 +1,19 @@
+package com.repoachiever.mapping;
+
+import com.repoachiever.exception.CredentialsFieldIsNotValidException;
+import com.repoachiever.exception.WorkspaceUnitDirectoryNotFoundException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+
+/** Represents mapper for CredentialsFieldIsNotValidException exception. */
+@Provider
+public class CredentialsFieldIsNotValidExceptionMapper
+ implements ExceptionMapper {
+ @Override
+ public Response toResponse(CredentialsFieldIsNotValidException e) {
+ return Response.status(Response.Status.BAD_REQUEST.getStatusCode())
+ .entity(e.getMessage())
+ .build();
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/mapping/LocationsFieldIsNotValidExceptionMapper.java b/api-server/src/main/java/com/repoachiever/mapping/LocationsFieldIsNotValidExceptionMapper.java
new file mode 100644
index 0000000..1e55c5f
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/mapping/LocationsFieldIsNotValidExceptionMapper.java
@@ -0,0 +1,18 @@
+package com.repoachiever.mapping;
+
+import com.repoachiever.exception.LocationsFieldIsNotValidException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+
+/** Represents mapper for LocationsFieldIsNotValidException exception. */
+@Provider
+public class LocationsFieldIsNotValidExceptionMapper
+ implements ExceptionMapper {
+ @Override
+ public Response toResponse(LocationsFieldIsNotValidException e) {
+ return Response.status(Response.Status.BAD_REQUEST.getStatusCode())
+ .entity(e.getMessage())
+ .build();
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/mapping/RepositoryContentApplicationFailureExceptionMapper.java b/api-server/src/main/java/com/repoachiever/mapping/RepositoryContentApplicationFailureExceptionMapper.java
new file mode 100644
index 0000000..abea333
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/mapping/RepositoryContentApplicationFailureExceptionMapper.java
@@ -0,0 +1,20 @@
+package com.repoachiever.mapping;
+
+import com.repoachiever.exception.RepositoryContentApplicationFailureException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+
+/**
+ * Represents mapper for RepositoryContentApplicationFailureExceptionMapper exception.
+ */
+@Provider
+public class RepositoryContentApplicationFailureExceptionMapper
+ implements ExceptionMapper {
+ @Override
+ public Response toResponse(RepositoryContentApplicationFailureException e) {
+ return Response.status(Response.Status.BAD_REQUEST.getStatusCode())
+ .entity(e.getMessage())
+ .build();
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/mapping/RepositoryContentDestructionFailureExceptionMapper.java b/api-server/src/main/java/com/repoachiever/mapping/RepositoryContentDestructionFailureExceptionMapper.java
new file mode 100644
index 0000000..18911cd
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/mapping/RepositoryContentDestructionFailureExceptionMapper.java
@@ -0,0 +1,20 @@
+package com.repoachiever.mapping;
+
+import com.repoachiever.exception.RepositoryContentDestructionFailureException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+
+/**
+ * Represents mapper for RepositoryContentDestructionFailureExceptionMapper exception.
+ */
+@Provider
+public class RepositoryContentDestructionFailureExceptionMapper
+ implements ExceptionMapper {
+ @Override
+ public Response toResponse(RepositoryContentDestructionFailureException e) {
+ return Response.status(Response.Status.BAD_REQUEST.getStatusCode())
+ .entity(e.getMessage())
+ .build();
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/mapping/WorkspaceUnitDirectoryNotFoundExceptionMapper.java b/api-server/src/main/java/com/repoachiever/mapping/WorkspaceUnitDirectoryNotFoundExceptionMapper.java
new file mode 100644
index 0000000..36617a4
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/mapping/WorkspaceUnitDirectoryNotFoundExceptionMapper.java
@@ -0,0 +1,18 @@
+package com.repoachiever.mapping;
+
+import com.repoachiever.exception.WorkspaceUnitDirectoryNotFoundException;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.Provider;
+
+/** Represents mapper for WorkspaceUnitDirectoryNotFoundException exception. */
+@Provider
+public class WorkspaceUnitDirectoryNotFoundExceptionMapper
+ implements ExceptionMapper {
+ @Override
+ public Response toResponse(WorkspaceUnitDirectoryNotFoundException e) {
+ return Response.status(Response.Status.BAD_REQUEST.getStatusCode())
+ .entity(e.getMessage())
+ .build();
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/repository/ConfigRepository.java b/api-server/src/main/java/com/repoachiever/repository/ConfigRepository.java
new file mode 100644
index 0000000..cca8115
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/repository/ConfigRepository.java
@@ -0,0 +1,123 @@
+package com.repoachiever.repository;
+
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.entity.repository.ConfigEntity;
+import com.repoachiever.exception.QueryEmptyResultException;
+import com.repoachiever.exception.QueryExecutionFailureException;
+import com.repoachiever.exception.RepositoryOperationFailureException;
+import com.repoachiever.repository.executor.RepositoryExecutor;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import javax.sql.DataSource;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * Represents repository implementation to handle config table.
+ */
+@ApplicationScoped
+public class ConfigRepository {
+ @Inject
+ PropertiesEntity properties;
+
+ @Inject
+ RepositoryExecutor repositoryExecutor;
+
+ /**
+ * Inserts given values into the config table.
+ *
+ * @param name given name of the configuration.
+ * @param hash given hash of the configuration.
+ * @throws RepositoryOperationFailureException if operation execution fails.
+ */
+ public void insert(String name, String hash) throws RepositoryOperationFailureException {
+ try {
+ repositoryExecutor.performQuery(
+ String.format(
+ "INSERT INTO %s (name, hash) VALUES ('%s', '%s')",
+ properties.getDatabaseConfigTableName(),
+ name,
+ hash));
+
+ } catch (QueryExecutionFailureException | QueryEmptyResultException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Checks if config entity with the given name is present.
+ *
+ * @param name given name of the configuration.
+ * @return result of the check.
+ * @throws RepositoryOperationFailureException if repository operation fails.
+ */
+ public Boolean isPresentByName(String name) throws RepositoryOperationFailureException {
+ try {
+ ResultSet resultSet = repositoryExecutor.performQueryWithResult(
+ String.format(
+ "SELECT t.id, t.hash FROM %s as t WHERE t.name = '%s'",
+ properties.getDatabaseConfigTableName(),
+ name));
+
+ try {
+ resultSet.close();
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+ } catch (QueryEmptyResultException e) {
+ return false;
+ } catch (QueryExecutionFailureException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Attempts to retrieve config entity by the given name.
+ *
+ * @param name given name of the configuration.
+ * @return retrieved config entity.
+ * @throws RepositoryOperationFailureException if repository operation fails.
+ */
+ public ConfigEntity findByName(String name) throws RepositoryOperationFailureException {
+ ResultSet resultSet;
+
+ try {
+ resultSet =
+ repositoryExecutor.performQueryWithResult(
+ String.format(
+ "SELECT t.id, t.hash FROM %s as t WHERE t.name = '%s'",
+ properties.getDatabaseConfigTableName(),
+ name));
+
+ } catch (QueryExecutionFailureException | QueryEmptyResultException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ Integer id;
+
+ try {
+ id = resultSet.getInt("id");
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ String hash;
+
+ try {
+ hash = resultSet.getString("hash");
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ try {
+ resultSet.close();
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ return ConfigEntity.of(id, name, hash);
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/repository/ContentRepository.java b/api-server/src/main/java/com/repoachiever/repository/ContentRepository.java
new file mode 100644
index 0000000..ccd04d4
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/repository/ContentRepository.java
@@ -0,0 +1,149 @@
+package com.repoachiever.repository;
+
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.entity.repository.ContentEntity;
+import com.repoachiever.entity.repository.ProviderEntity;
+import com.repoachiever.exception.QueryEmptyResultException;
+import com.repoachiever.exception.QueryExecutionFailureException;
+import com.repoachiever.exception.RepositoryOperationFailureException;
+import com.repoachiever.repository.executor.RepositoryExecutor;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import javax.sql.DataSource;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents repository implementation to handle content table.
+ */
+@ApplicationScoped
+public class ContentRepository {
+ @Inject
+ PropertiesEntity properties;
+
+ @Inject
+ RepositoryExecutor repositoryExecutor;
+
+ /**
+ * Inserts given values into the content table.
+ *
+ * @param location given content location.
+ * @param provider given provider used for content retrieval.
+ * @param secret given secret, which allows content retrieval.
+ * @throws RepositoryOperationFailureException if operation execution fails.
+ */
+ public void insert(String location, Integer provider, Integer secret) throws RepositoryOperationFailureException {
+ try {
+ repositoryExecutor.performQuery(
+ String.format(
+ "INSERT INTO %s (location, provider, secret) VALUES ('%s', %d, %d)",
+ properties.getDatabaseContentTableName(),
+ location,
+ provider,
+ secret));
+
+ } catch (QueryExecutionFailureException | QueryEmptyResultException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Checks if content entity with the given location is present.
+ *
+ * @param location given location of the content.
+ * @return result of the check.
+ * @throws RepositoryOperationFailureException if repository operation fails.
+ */
+ public Boolean isPresentByLocation(String location) throws RepositoryOperationFailureException {
+ try {
+ ResultSet resultSet = repositoryExecutor.performQueryWithResult(
+ String.format(
+ "SELECT t.id FROM %s as t WHERE t.location = '%s'",
+ properties.getDatabaseContentTableName(),
+ location));
+
+ try {
+ resultSet.close();
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+ } catch (QueryEmptyResultException e) {
+ return false;
+ } catch (QueryExecutionFailureException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Retrieves all the persisted content entities.
+ *
+ * @return retrieved content entities.
+ * @throws RepositoryOperationFailureException if repository operation fails.
+ */
+ public List findAll() throws RepositoryOperationFailureException {
+ ResultSet resultSet;
+
+ try {
+ resultSet =
+ repositoryExecutor.performQueryWithResult(
+ String.format(
+ "SELECT t.id, t.location, t.provider, t.secret FROM %s as t",
+ properties.getDatabaseContentTableName()));
+
+ } catch (QueryExecutionFailureException | QueryEmptyResultException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ List result = new ArrayList<>();
+
+ Integer id;
+ String location;
+ Integer provider;
+ Integer secret;
+
+ try {
+ while (resultSet.next()) {
+ id = resultSet.getInt("id");
+ location = resultSet.getString("location");
+ provider = resultSet.getInt("provider");
+ secret = resultSet.getInt("secret");
+
+ result.add(ContentEntity.of(id, location, provider, secret));
+ }
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ try {
+ resultSet.close();
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ return result;
+ }
+
+ /**
+ * Deletes all entities with the given secret from content table.
+ *
+ * @param secret given secret, which allows content retrieval.
+ * @throws RepositoryOperationFailureException if operation execution fails.
+ */
+ public void deleteBySecret(Integer secret) throws RepositoryOperationFailureException {
+ try {
+ repositoryExecutor.performQuery(
+ String.format(
+ "DELETE FROM %s as t WHERE t.secret = %d",
+ properties.getDatabaseContentTableName(),
+ secret));
+
+ } catch (QueryExecutionFailureException | QueryEmptyResultException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/repository/ProviderRepository.java b/api-server/src/main/java/com/repoachiever/repository/ProviderRepository.java
new file mode 100644
index 0000000..8ab911c
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/repository/ProviderRepository.java
@@ -0,0 +1,153 @@
+package com.repoachiever.repository;
+
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.entity.repository.ConfigEntity;
+import com.repoachiever.entity.repository.ProviderEntity;
+import com.repoachiever.exception.QueryEmptyResultException;
+import com.repoachiever.exception.QueryExecutionFailureException;
+import com.repoachiever.exception.RepositoryOperationFailureException;
+import com.repoachiever.repository.executor.RepositoryExecutor;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import javax.sql.DataSource;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * Represents repository implementation to handle provider table.
+ */
+@ApplicationScoped
+public class ProviderRepository {
+ @Inject
+ PropertiesEntity properties;
+
+ @Inject
+ RepositoryExecutor repositoryExecutor;
+
+ /**
+ * Inserts given values into the provider table.
+ *
+ * @param name given provider name.
+ * @throws RepositoryOperationFailureException if operation execution fails.
+ */
+ public void insert(String name) throws RepositoryOperationFailureException {
+ try {
+ repositoryExecutor.performQuery(
+ String.format(
+ "INSERT INTO %s (name) VALUES ('%s')",
+ properties.getDatabaseProviderTableName(),
+ name));
+
+ } catch (QueryExecutionFailureException | QueryEmptyResultException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Checks if provider entity with the given name is present.
+ *
+ * @param name given name of the provider.
+ * @return result of the check.
+ * @throws RepositoryOperationFailureException if repository operation fails.
+ */
+ public Boolean isPresentByName(String name) throws RepositoryOperationFailureException {
+ try {
+ ResultSet resultSet = repositoryExecutor.performQueryWithResult(
+ String.format(
+ "SELECT t.id FROM %s as t WHERE t.name = '%s'",
+ properties.getDatabaseProviderTableName(),
+ name));
+
+ try {
+ resultSet.close();
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+ } catch (QueryEmptyResultException e) {
+ return false;
+ } catch (QueryExecutionFailureException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Attempts to retrieve provider entity by the given name.
+ *
+ * @param name given name of the configuration.
+ * @return retrieved config entity.
+ * @throws RepositoryOperationFailureException if repository operation fails.
+ */
+ public ProviderEntity findByName(String name) throws RepositoryOperationFailureException {
+ ResultSet resultSet;
+
+ try {
+ resultSet =
+ repositoryExecutor.performQueryWithResult(
+ String.format(
+ "SELECT t.id FROM %s as t WHERE t.name = '%s'",
+ properties.getDatabaseProviderTableName(),
+ name));
+
+ } catch (QueryExecutionFailureException | QueryEmptyResultException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ Integer id;
+
+ try {
+ id = resultSet.getInt("id");
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ try {
+ resultSet.close();
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ return ProviderEntity.of(id, name);
+ }
+
+ /**
+ * Attempts to retrieve provider entity by the given identificator.
+ *
+ * @param id given identificator of the configuration.
+ * @return retrieved config entity.
+ * @throws RepositoryOperationFailureException if repository operation fails.
+ */
+ public ProviderEntity findById(Integer id) throws RepositoryOperationFailureException {
+ ResultSet resultSet;
+
+ try {
+ resultSet =
+ repositoryExecutor.performQueryWithResult(
+ String.format(
+ "SELECT t.name FROM %s as t WHERE t.id = '%s'",
+ properties.getDatabaseProviderTableName(),
+ id));
+
+ } catch (QueryExecutionFailureException | QueryEmptyResultException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ String name;
+
+ try {
+ name = resultSet.getString("name");
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ try {
+ resultSet.close();
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ return ProviderEntity.of(id, name);
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/repository/SecretRepository.java b/api-server/src/main/java/com/repoachiever/repository/SecretRepository.java
new file mode 100644
index 0000000..9448bd9
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/repository/SecretRepository.java
@@ -0,0 +1,194 @@
+package com.repoachiever.repository;
+
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.entity.repository.ProviderEntity;
+import com.repoachiever.entity.repository.SecretEntity;
+import com.repoachiever.exception.QueryEmptyResultException;
+import com.repoachiever.exception.QueryExecutionFailureException;
+import com.repoachiever.exception.RepositoryOperationFailureException;
+import com.repoachiever.repository.executor.RepositoryExecutor;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import javax.sql.DataSource;
+import java.sql.*;
+import java.util.Optional;
+
+/**
+ * Represents repository implementation to handle secret table.
+ */
+@ApplicationScoped
+public class SecretRepository {
+ @Inject
+ PropertiesEntity properties;
+
+ @Inject
+ RepositoryExecutor repositoryExecutor;
+
+ /**
+ * Inserts given values into the provider table.
+ *
+ * @param session given internal secret.
+ * @param credentials given optional external credentials.
+ * @throws RepositoryOperationFailureException if operation execution fails.
+ */
+ public void insert(Integer session, Optional credentials) throws RepositoryOperationFailureException {
+ String query;
+
+ if (credentials.isPresent()) {
+ query = String.format(
+ "INSERT INTO %s (session, credentials) VALUES (%d, '%s')",
+ properties.getDatabaseSecretTableName(),
+ session,
+ credentials.get());
+ } else {
+ query = String.format(
+ "INSERT INTO %s (session) VALUES (%d)",
+ properties.getDatabaseSecretTableName(),
+ session);
+ }
+
+ try {
+ repositoryExecutor.performQuery(query);
+ } catch (QueryExecutionFailureException | QueryEmptyResultException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Checks if secret entity with the given session and credentials is present.
+ *
+ * @param session given session of the secrets set.
+ * @param credentials given optional external credentials.
+ * @return result of the check.
+ * @throws RepositoryOperationFailureException if repository operation fails.
+ */
+ public Boolean isPresentBySessionAndCredentials(Integer session, Optional credentials) throws RepositoryOperationFailureException {
+ String query;
+
+ if (credentials.isPresent()) {
+ query = String.format(
+ "SELECT t.id FROM %s as t WHERE t.session = %d AND t.credentials = '%s'",
+ properties.getDatabaseSecretTableName(),
+ session,
+ credentials.get());
+ } else {
+ query = String.format(
+ "SELECT t.id FROM %s as t WHERE t.session = %d",
+ properties.getDatabaseSecretTableName(),
+ session);
+ }
+
+ try {
+ ResultSet resultSet = repositoryExecutor.performQueryWithResult(query);
+
+ try {
+ resultSet.close();
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+ } catch (QueryEmptyResultException e) {
+ return false;
+ } catch (QueryExecutionFailureException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Attempts to retrieve secret entity by the given session and credentials.
+ *
+ * @param session given session of the secrets set.
+ * @param credentials given optional external credentials.
+ * @return retrieved secret entity.
+ * @throws RepositoryOperationFailureException if repository operation fails.
+ */
+ public SecretEntity findBySessionAndCredentials(Integer session, Optional credentials) throws RepositoryOperationFailureException {
+ String query;
+
+ if (credentials.isPresent()) {
+ query = String.format(
+ "SELECT t.id FROM %s as t WHERE t.session = %d AND t.credentials = '%s'",
+ properties.getDatabaseSecretTableName(),
+ session,
+ credentials.get());
+ } else {
+ query = String.format(
+ "SELECT t.id FROM %s as t WHERE t.session = %d",
+ properties.getDatabaseSecretTableName(),
+ session);
+ }
+
+ ResultSet resultSet;
+
+ try {
+ resultSet = repositoryExecutor.performQueryWithResult(query);
+ } catch (QueryExecutionFailureException | QueryEmptyResultException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ Integer id;
+
+ try {
+ id = resultSet.getInt("id");
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ try {
+ resultSet.close();
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ return SecretEntity.of(id, session, credentials);
+ }
+
+ /**
+ * Attempts to retrieve secret entity by the given identificator.
+ *
+ * @param id given identificator of the secrets set.
+ * @return retrieved secret entity.
+ * @throws RepositoryOperationFailureException if repository operation fails.
+ */
+ public SecretEntity findById(Integer id) throws RepositoryOperationFailureException {
+ ResultSet resultSet;
+
+ try {
+ resultSet = repositoryExecutor.performQueryWithResult(String.format(
+ "SELECT t.session, t.credentials FROM %s as t WHERE t.id = %d",
+ properties.getDatabaseSecretTableName(),
+ id));
+
+ } catch (QueryExecutionFailureException | QueryEmptyResultException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ Integer session;
+
+ try {
+ session = resultSet.getInt("session");
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ String credentials;
+
+ try {
+ credentials = resultSet.getString("credentials");
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ try {
+ resultSet.close();
+ } catch (SQLException e) {
+ throw new RepositoryOperationFailureException(e.getMessage());
+ }
+
+ return SecretEntity.of(id, session, Optional.ofNullable(credentials));
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/repository/common/RepositoryConfigurationHelper.java b/api-server/src/main/java/com/repoachiever/repository/common/RepositoryConfigurationHelper.java
new file mode 100644
index 0000000..0112a8e
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/repository/common/RepositoryConfigurationHelper.java
@@ -0,0 +1,57 @@
+package com.repoachiever.repository.common;
+
+import com.repoachiever.model.CredentialsFieldsExternal;
+import com.repoachiever.model.CredentialsFieldsFull;
+import com.repoachiever.model.CredentialsFieldsInternal;
+import com.repoachiever.model.Provider;
+import java.util.Optional;
+
+/**
+ * Contains helpful tools used for repository configuration.
+ */
+public class RepositoryConfigurationHelper {
+ /**
+ * Extracts external credentials from the given credentials field as optional .
+ *
+ * @param provider given vendor provider.
+ * @param credentialsFieldExternal given credentials field.
+ * @return extracted external credentials as optional.
+ */
+ public static Optional getExternalCredentials(
+ Provider provider, CredentialsFieldsExternal credentialsFieldExternal) {
+ return switch (provider) {
+ case LOCAL -> Optional.empty();
+ case GITHUB -> Optional.ofNullable(credentialsFieldExternal.getToken());
+ };
+ }
+
+ /**
+ * Converts given raw provider to content provider.
+ *
+ * @param value given raw provider.
+ * @return converted content provider.
+ */
+ public static Provider convertRawProviderToContentProvider(String value) {
+ return Provider.fromString(value);
+ }
+
+ /**
+ * Converts given raw secrets to common credentials according to the given provider.
+ *
+ * @param provider given provider.
+ * @param session given session identificator.
+ * @param credentials given raw credentials.
+ * @return converted common credentials.
+ */
+ public static CredentialsFieldsFull convertRawSecretsToContentCredentials(
+ Provider provider, Integer session, Optional credentials) {
+ return switch (provider) {
+ case LOCAL -> CredentialsFieldsFull.of(
+ CredentialsFieldsInternal.of(session),
+ null);
+ case GITHUB -> CredentialsFieldsFull.of(
+ CredentialsFieldsInternal.of(session),
+ CredentialsFieldsExternal.of(credentials.get()));
+ };
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/repository/executor/RepositoryExecutor.java b/api-server/src/main/java/com/repoachiever/repository/executor/RepositoryExecutor.java
new file mode 100644
index 0000000..d818419
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/repository/executor/RepositoryExecutor.java
@@ -0,0 +1,154 @@
+package com.repoachiever.repository.executor;
+
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.exception.QueryEmptyResultException;
+import com.repoachiever.exception.QueryExecutionFailureException;
+import com.repoachiever.service.cluster.resource.ClusterCommunicationResource;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Service used to perform low-level database related operations.
+ */
+@ApplicationScoped
+public class RepositoryExecutor {
+ private static final Logger logger = LogManager.getLogger(ClusterCommunicationResource.class);
+
+ @Inject
+ PropertiesEntity properties;
+
+ @Inject
+ DataSource dataSource;
+
+ private Connection connection;
+
+ private final List statements = new ArrayList<>();
+
+ private final ScheduledExecutorService scheduledExecutorService =
+ Executors.newSingleThreadScheduledExecutor();
+
+ @PostConstruct
+ public void configure() {
+ try {
+ this.connection = dataSource.getConnection();
+ } catch (SQLException e) {
+ logger.fatal(new QueryExecutionFailureException(e.getMessage()).getMessage());
+ }
+ }
+
+ /**
+ * Performs given SQL query without result.
+ *
+ * @param query given SQL query to be executed.
+ * @throws QueryExecutionFailureException if query execution is interrupted by failure.
+ * @throws QueryEmptyResultException if result is empty.
+ */
+ public void performQuery(String query) throws QueryExecutionFailureException, QueryEmptyResultException {
+ Statement statement;
+
+ try {
+ statement = this.connection.createStatement();
+ } catch (SQLException e) {
+ throw new QueryExecutionFailureException(e.getMessage());
+ }
+
+ try {
+ statement.executeUpdate(query);
+ } catch (SQLException e) {
+ throw new QueryExecutionFailureException(e.getMessage());
+ }
+
+ statements.add(statement);
+
+ scheduledExecutorService.schedule(() -> {
+ try {
+ statement.close();
+ } catch (SQLException e) {
+ logger.fatal(new QueryExecutionFailureException(e.getMessage()).getMessage());
+ }
+ }, properties.getDatabaseStatementCloseDelay(), TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Performs given SQL query and returns raw result.
+ *
+ * @param query given SQL query to be executed.
+ * @return retrieved raw result.
+ * @throws QueryExecutionFailureException if query execution is interrupted by failure.
+ * @throws QueryEmptyResultException if result is empty.
+ */
+ public ResultSet performQueryWithResult(String query) throws QueryExecutionFailureException, QueryEmptyResultException {
+ Statement statement;
+
+ try {
+ statement = this.connection.createStatement();
+ } catch (SQLException e) {
+ throw new QueryExecutionFailureException(e.getMessage());
+ }
+
+ ResultSet resultSet;
+
+ try {
+ resultSet = statement.executeQuery(query);
+ } catch (SQLException e) {
+ throw new QueryExecutionFailureException(e.getMessage());
+ }
+
+ statements.add(statement);
+
+ scheduledExecutorService.schedule(() -> {
+ try {
+ statement.close();
+ } catch (SQLException e) {
+ logger.fatal(new QueryExecutionFailureException(e.getMessage()).getMessage());
+ }
+ }, properties.getDatabaseStatementCloseDelay(), TimeUnit.MILLISECONDS);
+
+ try {
+ if (!resultSet.isBeforeFirst()) {
+ throw new QueryEmptyResultException();
+ }
+ } catch (SQLException e) {
+ throw new QueryExecutionFailureException(e.getMessage());
+ }
+
+ return resultSet;
+ }
+
+ /**
+ * Closes opened database connection.
+ */
+ @PreDestroy
+ private void close() {
+ statements.forEach(element -> {
+ try {
+ if (!element.isClosed()) {
+ element.close();
+ }
+ } catch (SQLException e) {
+ logger.fatal(new QueryExecutionFailureException(e.getMessage()).getMessage());
+ }
+ });
+
+ try {
+ this.connection.close();
+ } catch (SQLException e) {
+ logger.fatal(e.getMessage());
+ }
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/repository/facade/RepositoryFacade.java b/api-server/src/main/java/com/repoachiever/repository/facade/RepositoryFacade.java
new file mode 100644
index 0000000..a77ec68
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/repository/facade/RepositoryFacade.java
@@ -0,0 +1,212 @@
+package com.repoachiever.repository.facade;
+
+import com.repoachiever.dto.RepositoryContentUnitDto;
+import com.repoachiever.entity.repository.ContentEntity;
+import com.repoachiever.entity.repository.ProviderEntity;
+import com.repoachiever.entity.repository.SecretEntity;
+import com.repoachiever.exception.ContentApplicationRetrievalFailureException;
+import com.repoachiever.exception.RepositoryContentApplicationFailureException;
+import com.repoachiever.exception.RepositoryContentDestructionFailureException;
+import com.repoachiever.exception.RepositoryOperationFailureException;
+import com.repoachiever.model.*;
+import com.repoachiever.repository.ConfigRepository;
+import com.repoachiever.repository.ContentRepository;
+import com.repoachiever.repository.ProviderRepository;
+import com.repoachiever.repository.SecretRepository;
+import com.repoachiever.repository.common.RepositoryConfigurationHelper;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static java.util.stream.Collectors.groupingBy;
+
+/**
+ * Represents facade for repository implementations used to handle tables.
+ */
+@ApplicationScoped
+public class RepositoryFacade {
+ @Inject
+ ConfigRepository configRepository;
+
+ @Inject
+ ContentRepository contentRepository;
+
+ @Inject
+ ProviderRepository providerRepository;
+
+ @Inject
+ SecretRepository secretRepository;
+
+ /**
+ * Retrieves content state for the given configuration properties.
+ *
+ * @param contentStateApplication given content state retrieve application.
+ * @return retrieve content state hash.
+ */
+ public String retrieveContentState(ContentStateApplication contentStateApplication) {
+ return "";
+ }
+
+ /**
+ * Retrieves all the available locations for the given configuration properties.
+ *
+ * @param contentRetrievalApplication given content retrieval application.
+ * @return retrieved locations for the given configuration properties.
+ */
+ public List retrieveLocations(ContentRetrievalApplication contentRetrievalApplication) {
+ return null;
+ }
+
+ /**
+ * Retrieves all the data from content repository in a form of content applications.
+ *
+ * @return retrieved set of content applications.
+ * @throws ContentApplicationRetrievalFailureException if content application retrieval fails.
+ */
+ public List retrieveContentApplication() throws ContentApplicationRetrievalFailureException {
+ List result = new ArrayList<>();
+
+ List units = new ArrayList<>();
+
+ List contents;
+
+ try {
+ contents = contentRepository.findAll();
+ } catch (RepositoryOperationFailureException e) {
+ throw new ContentApplicationRetrievalFailureException(e.getMessage());
+ }
+
+ for (ContentEntity content : contents) {
+ ProviderEntity rawProvider;
+
+ try {
+ rawProvider = providerRepository.findById(content.getProvider());
+ } catch (RepositoryOperationFailureException e) {
+ throw new ContentApplicationRetrievalFailureException(e.getMessage());
+ }
+
+ SecretEntity rawSecret;
+
+ try {
+ rawSecret = secretRepository.findById(content.getSecret());
+ } catch (RepositoryOperationFailureException e) {
+ throw new ContentApplicationRetrievalFailureException(e.getMessage());
+ }
+
+ Provider provider =
+ RepositoryConfigurationHelper.convertRawProviderToContentProvider(
+ rawProvider.getName());
+
+ CredentialsFieldsFull credentials =
+ RepositoryConfigurationHelper.convertRawSecretsToContentCredentials(
+ provider, rawSecret.getSession(), rawSecret.getCredentials());
+
+ units.add(RepositoryContentUnitDto.of(
+ content.getLocation(),
+ provider,
+ credentials));
+ }
+
+ Map>> groups =
+ units
+ .stream()
+ .collect(
+ groupingBy(
+ RepositoryContentUnitDto::getCredentials,
+ groupingBy(RepositoryContentUnitDto::getProvider)));
+
+ groups
+ .forEach((key1, value1) -> {
+ value1
+ .forEach((key2, value2) -> {
+ result.add(
+ ContentApplication.of(value2.stream().map(RepositoryContentUnitDto::getLocation).toList(), key2, key1));
+ });
+ });
+
+ return result;
+ }
+
+ /**
+ * Applies given content application, updating previous state.
+ *
+ * @param contentApplication given content application used for topology configuration.
+ * @throws RepositoryContentApplicationFailureException if RepoAchiever Cluster repository content application failed.
+ */
+ public void apply(ContentApplication contentApplication) throws RepositoryContentApplicationFailureException {
+ ProviderEntity provider;
+
+ try {
+ provider = providerRepository.findByName(contentApplication.getProvider().toString());
+ } catch (RepositoryOperationFailureException e) {
+ throw new RepositoryContentApplicationFailureException(e.getMessage());
+ }
+
+ Optional credentials = RepositoryConfigurationHelper.getExternalCredentials(
+ contentApplication.getProvider(), contentApplication.getCredentials().getExternal());
+
+ try {
+ if (!secretRepository.isPresentBySessionAndCredentials(
+ contentApplication.getCredentials().getInternal().getId(), credentials)) {
+ secretRepository.insert(
+ contentApplication.getCredentials().getInternal().getId(),
+ credentials);
+ }
+ } catch (RepositoryOperationFailureException e) {
+ throw new RepositoryContentApplicationFailureException(e.getMessage());
+ }
+
+ SecretEntity secret;
+
+ try {
+ secret = secretRepository.findBySessionAndCredentials(
+ contentApplication.getCredentials().getInternal().getId(),
+ credentials);
+ } catch (RepositoryOperationFailureException e) {
+ throw new RepositoryContentApplicationFailureException(e.getMessage());
+ }
+
+ try {
+ contentRepository.deleteBySecret(secret.getId());
+ } catch (RepositoryOperationFailureException e) {
+ throw new RepositoryContentApplicationFailureException(e.getMessage());
+ }
+
+ for (String location : contentApplication.getLocations()) {
+ try {
+ contentRepository.insert(location, provider.getId(), secret.getId());
+ } catch (RepositoryOperationFailureException e) {
+ throw new RepositoryContentApplicationFailureException(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Applies given content withdrawal, removing previous state.
+ *
+ * @param contentWithdrawal given content application used for topology configuration.
+ * @throws RepositoryContentDestructionFailureException if RepoAchiever Cluster repository content destruction failed.
+ */
+ public void destroy(ContentWithdrawal contentWithdrawal) throws RepositoryContentDestructionFailureException {
+ Optional credentials = RepositoryConfigurationHelper.getExternalCredentials(
+ contentWithdrawal.getProvider(), contentWithdrawal.getCredentials().getExternal());
+
+ SecretEntity secret;
+
+ try {
+ secret = secretRepository.findBySessionAndCredentials(
+ contentWithdrawal.getCredentials().getInternal().getId(),
+ credentials);
+ } catch (RepositoryOperationFailureException e) {
+ throw new RepositoryContentDestructionFailureException(e.getMessage());
+ }
+
+ try {
+ contentRepository.deleteBySecret(secret.getId());
+ } catch (RepositoryOperationFailureException e) {
+ throw new RepositoryContentDestructionFailureException(e.getMessage());
+ }
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/resource/ContentResource.java b/api-server/src/main/java/com/repoachiever/resource/ContentResource.java
new file mode 100644
index 0000000..01019b5
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/resource/ContentResource.java
@@ -0,0 +1,128 @@
+package com.repoachiever.resource;
+
+import com.repoachiever.api.ContentResourceApi;
+import com.repoachiever.exception.CredentialsAreNotValidException;
+import com.repoachiever.exception.CredentialsFieldIsNotValidException;
+import com.repoachiever.exception.LocationsFieldIsNotValidException;
+import com.repoachiever.model.*;
+import com.repoachiever.repository.facade.RepositoryFacade;
+import com.repoachiever.resource.common.ResourceConfigurationHelper;
+import com.repoachiever.service.cluster.facade.ClusterFacade;
+import com.repoachiever.service.vendor.VendorFacade;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import jakarta.ws.rs.BadRequestException;
+import lombok.SneakyThrows;
+
+import java.io.File;
+import java.util.Objects;
+
+/** Contains implementation of ContentResource. */
+@ApplicationScoped
+public class ContentResource implements ContentResourceApi {
+ @Inject
+ RepositoryFacade repositoryFacade;
+
+ @Inject
+ ClusterFacade clusterFacade;
+
+ @Inject
+ VendorFacade vendorFacade;
+
+ /**
+ * Implementation for declared in OpenAPI configuration v1ContentPost method.
+ *
+ * @param contentRetrievalApplication content retrieval application.
+ * @return retrieved content result.
+ */
+ @Override
+ public ContentRetrievalResult v1ContentPost(ContentRetrievalApplication contentRetrievalApplication) {
+ if (Objects.isNull(contentRetrievalApplication)) {
+ throw new BadRequestException();
+ }
+
+ return ContentRetrievalResult.of(
+ repositoryFacade.retrieveLocations(contentRetrievalApplication));
+ }
+
+ /**
+ * Implementation for declared in OpenAPI configuration v1ContentApplyPost method.
+ *
+ * @param contentApplication content configuration application.
+ */
+ @Override
+ @SneakyThrows
+ public void v1ContentApplyPost(ContentApplication contentApplication) {
+ if (Objects.isNull(contentApplication)) {
+ throw new BadRequestException();
+ }
+
+ if (!ResourceConfigurationHelper.isExternalCredentialsFieldValid(
+ contentApplication.getProvider(), contentApplication.getCredentials().getExternal())) {
+ throw new CredentialsFieldIsNotValidException();
+ }
+
+ if (!ResourceConfigurationHelper.isLocationsDuplicate(contentApplication.getLocations())) {
+ throw new LocationsFieldIsNotValidException();
+ }
+
+ if (!vendorFacade.isExternalCredentialsValid(
+ contentApplication.getProvider(), contentApplication.getCredentials().getExternal())) {
+ throw new CredentialsAreNotValidException();
+ }
+
+ clusterFacade.apply(contentApplication);
+
+ repositoryFacade.apply(contentApplication);
+ }
+
+ /**
+ * Implementation for declared in OpenAPI configuration v1ContentWithdrawDelete method.
+ *
+ * @param contentWithdrawal content withdrawal application.
+ */
+ @Override
+ @SneakyThrows
+ public void v1ContentWithdrawDelete(ContentWithdrawal contentWithdrawal) {
+ if (Objects.isNull(contentWithdrawal)) {
+ throw new BadRequestException();
+ }
+
+ if (!ResourceConfigurationHelper.isExternalCredentialsFieldValid(
+ contentWithdrawal.getProvider(), contentWithdrawal.getCredentials().getExternal())) {
+ throw new CredentialsFieldIsNotValidException();
+ }
+
+ if (!vendorFacade.isExternalCredentialsValid(
+ contentWithdrawal.getProvider(), contentWithdrawal.getCredentials().getExternal())) {
+ throw new CredentialsAreNotValidException();
+ }
+
+ clusterFacade.destroy(contentWithdrawal);
+
+ repositoryFacade.destroy(contentWithdrawal);
+ }
+
+ /**
+ * Implementation for declared in OpenAPI configuration v1ContentDownloadGet method.
+ *
+ * @param location name of content location to be downloaded.
+ * @return downloaded content result.
+ */
+ @Override
+ public File v1ContentDownloadGet(String location) {
+
+ return null;
+ }
+
+ /**
+ * Implementation for declared in OpenAPI configuration v1ContentCleanPost method.
+ *
+ * @param contentCleanup content cleanup application.
+ */
+ @Override
+ public void v1ContentCleanPost(ContentCleanup contentCleanup) {
+
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/resource/HealthResource.java b/api-server/src/main/java/com/repoachiever/resource/HealthResource.java
new file mode 100644
index 0000000..30b0666
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/resource/HealthResource.java
@@ -0,0 +1,65 @@
+package com.repoachiever.resource;
+
+import com.repoachiever.api.HealthResourceApi;
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.model.HealthCheckResult;
+import com.repoachiever.model.ReadinessCheckApplication;
+import com.repoachiever.model.ReadinessCheckResult;
+import com.repoachiever.service.client.smallrye.ISmallRyeHealthCheckClientService;
+import com.repoachiever.service.workspace.WorkspaceService;
+import com.repoachiever.service.workspace.facade.WorkspaceFacade;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.BadRequestException;
+import jakarta.ws.rs.WebApplicationException;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+
+import java.util.Objects;
+
+/**
+ * Contains implementation of HealthResource.
+ */
+@ApplicationScoped
+public class HealthResource implements HealthResourceApi {
+ @Inject
+ PropertiesEntity properties;
+
+ @Inject
+ WorkspaceFacade workspaceFacade;
+
+ @Inject
+ WorkspaceService workspaceService;
+
+ @Inject
+ @RestClient
+ ISmallRyeHealthCheckClientService smallRyeHealthCheckClientService;
+
+ /**
+ * Implementation for declared in OpenAPI configuration v1HealthGet method.
+ *
+ * @return health check result.
+ */
+ @Override
+ public HealthCheckResult v1HealthGet() {
+ try {
+ return smallRyeHealthCheckClientService.qHealthGet();
+ } catch (WebApplicationException e) {
+ return e.getResponse().readEntity(HealthCheckResult.class);
+ }
+ }
+
+ /**
+ * Implementation for declared in OpenAPI configuration v1ReadinessPost method.
+ *
+ * @param readinessCheckApplication application used to perform application readiness check.
+ * @return readiness check result.
+ */
+ @Override
+ public ReadinessCheckResult v1ReadinessPost(ReadinessCheckApplication readinessCheckApplication) {
+ if (Objects.isNull(readinessCheckApplication)) {
+ throw new BadRequestException();
+ }
+
+ return null;
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/resource/InfoResource.java b/api-server/src/main/java/com/repoachiever/resource/InfoResource.java
new file mode 100644
index 0000000..a648576
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/resource/InfoResource.java
@@ -0,0 +1,56 @@
+package com.repoachiever.resource;
+
+import com.repoachiever.api.InfoResourceApi;
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.model.ClusterInfoUnit;
+import com.repoachiever.model.VersionExternalApiInfoResult;
+import com.repoachiever.model.VersionInfoResult;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import java.util.List;
+
+/**
+ * Contains implementation of InfoResource.
+ */
+@ApplicationScoped
+public class InfoResource implements InfoResourceApi {
+ @Inject
+ PropertiesEntity properties;
+
+ /**
+ * Implementation for declared in OpenAPI configuration v1InfoVersionGet method.
+ *
+ * @return version information result.
+ */
+ @Override
+ public VersionInfoResult v1InfoVersionGet() {
+ return VersionInfoResult.of(
+ VersionExternalApiInfoResult.of(
+ properties.getApplicationVersion(), properties.getGitCommitId()));
+ }
+
+ /**
+ * Implementation for declared in OpenAPI configuration v1InfoClusterGet method.
+ *
+ * @return cluster information result.
+ */
+ @Override
+ public List v1InfoClusterGet() {
+ // TODO: call cluster service to retrieve data from clusters.
+
+ return null;
+ }
+
+ /**
+ * Implementation for declared in OpenAPI configuration v1InfoTelemetryGet method.
+ *
+ * @return telemetry information result.
+ */
+ @Override
+ public String v1InfoTelemetryGet() {
+ // TODO: call telemetry service to retrieve data.
+
+ return null;
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/resource/StateResource.java b/api-server/src/main/java/com/repoachiever/resource/StateResource.java
new file mode 100644
index 0000000..eff6f61
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/resource/StateResource.java
@@ -0,0 +1,28 @@
+package com.repoachiever.resource;
+
+import com.repoachiever.api.StateResourceApi;
+import com.repoachiever.model.ContentStateApplication;
+import com.repoachiever.model.ContentStateApplicationResult;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.ws.rs.BadRequestException;
+
+import java.util.Objects;
+
+/** Contains implementation of StateResource. */
+@ApplicationScoped
+public class StateResource implements StateResourceApi {
+ /**
+ * Implementation for declared in OpenAPI configuration v1StateContentPost method.
+ *
+ * @param contentStateApplication application used to perform content state retrieval.
+ * @return retrieved state content hash.
+ */
+ @Override
+ public ContentStateApplicationResult v1StateContentPost(ContentStateApplication contentStateApplication) {
+ if (Objects.isNull(contentStateApplication)) {
+ throw new BadRequestException();
+ }
+
+ return null;
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/resource/common/ResourceConfigurationHelper.java b/api-server/src/main/java/com/repoachiever/resource/common/ResourceConfigurationHelper.java
new file mode 100644
index 0000000..e5303a5
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/resource/common/ResourceConfigurationHelper.java
@@ -0,0 +1,37 @@
+package com.repoachiever.resource.common;
+
+import com.repoachiever.model.CredentialsFieldsExternal;
+import com.repoachiever.model.Provider;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Contains helpful tools used for resource configuration.
+ */
+public class ResourceConfigurationHelper {
+ /**
+ * Checks if the given external credentials field is valid according to the used provider.
+ *
+ * @param provider given vendor provider.
+ * @param credentialsFieldExternal given credentials field.
+ * @return result of the check.
+ */
+ public static Boolean isExternalCredentialsFieldValid(
+ Provider provider, CredentialsFieldsExternal credentialsFieldExternal) {
+ return switch (provider) {
+ case LOCAL -> true;
+ case GITHUB -> Objects.nonNull(credentialsFieldExternal);
+ };
+ }
+
+ /**
+ * Checks if the given locations have duplicates.
+ *
+ * @param locations given locations.
+ * @return result of the check.
+ */
+ public static Boolean isLocationsDuplicate(List locations) {
+ return locations.stream().distinct().count() == locations.size();
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/resource/communication/ApiServerCommunicationResource.java b/api-server/src/main/java/com/repoachiever/resource/communication/ApiServerCommunicationResource.java
new file mode 100644
index 0000000..e044d4b
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/resource/communication/ApiServerCommunicationResource.java
@@ -0,0 +1,56 @@
+package com.repoachiever.resource.communication;
+
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.service.communication.apiserver.IApiServerCommunicationService;
+import com.repoachiever.service.integration.diagnostics.DiagnosticsConfigService;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.InputStream;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+/**
+ * Contains implementation of communication provider for RepoAchiever API Server.
+ */
+public class ApiServerCommunicationResource extends UnicastRemoteObject implements IApiServerCommunicationService {
+ private static final Logger logger = LogManager.getLogger(ApiServerCommunicationResource.class);
+
+ private final PropertiesEntity properties;
+
+ public ApiServerCommunicationResource(PropertiesEntity properties) throws RemoteException {
+ this.properties = properties;
+ }
+
+ /**
+ * @see IApiServerCommunicationService
+ */
+ @Override
+ public void performRawContentUpload(String workspaceUnitKey, InputStream content) throws RemoteException {
+
+ }
+
+ /**
+ * @see IApiServerCommunicationService
+ */
+ @Override
+ public void performAdditionalContentUpload(String workspaceUnitKey, String content) throws RemoteException {
+
+ }
+
+ /**
+ * @see IApiServerCommunicationService
+ */
+ @Override
+ public void performLogsTransfer(String name, String message) throws RemoteException {
+ logger.info(String.format("Transferred logs(instance: %s): %s", name, message));
+ }
+
+ /**
+ * @see IApiServerCommunicationService
+ */
+ @Override
+ public Boolean retrieveHealthCheck() throws RemoteException {
+ return true;
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/client/github/IGitHubClientService.java b/api-server/src/main/java/com/repoachiever/service/client/github/IGitHubClientService.java
new file mode 100644
index 0000000..d7f0afa
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/client/github/IGitHubClientService.java
@@ -0,0 +1,19 @@
+package com.repoachiever.service.client.github;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.HttpHeaders;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import org.jboss.resteasy.annotations.jaxrs.HeaderParam;
+
+/** Represents client for GitHub remote API. */
+@RegisterRestClient(configKey = "github")
+public interface IGitHubClientService {
+ @GET
+ @Path("/octocat")
+ @Produces(MediaType.APPLICATION_JSON)
+ Response getOctocat(@HeaderParam(HttpHeaders.AUTHORIZATION) String token);
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/client/smallrye/ISmallRyeHealthCheckClientService.java b/api-server/src/main/java/com/repoachiever/service/client/smallrye/ISmallRyeHealthCheckClientService.java
new file mode 100644
index 0000000..ece7ff1
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/client/smallrye/ISmallRyeHealthCheckClientService.java
@@ -0,0 +1,18 @@
+package com.repoachiever.service.client.smallrye;
+
+import com.repoachiever.model.HealthCheckResult;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+/** Represents client for SmallRye health check endpoints. */
+@Path("/q")
+@RegisterRestClient(configKey = "small-rye-health-check")
+public interface ISmallRyeHealthCheckClientService {
+ @GET
+ @Path("/health")
+ @Produces(MediaType.APPLICATION_JSON)
+ HealthCheckResult qHealthGet();
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/cluster/ClusterService.java b/api-server/src/main/java/com/repoachiever/service/cluster/ClusterService.java
new file mode 100644
index 0000000..6d2005b
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/cluster/ClusterService.java
@@ -0,0 +1,174 @@
+package com.repoachiever.service.cluster;
+
+import com.repoachiever.dto.CommandExecutorOutputDto;
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.exception.*;
+import com.repoachiever.service.cluster.common.ClusterConfigurationHelper;
+import com.repoachiever.service.cluster.resource.ClusterCommunicationResource;
+import com.repoachiever.service.command.cluster.deploy.ClusterDeployCommandService;
+import com.repoachiever.service.command.cluster.destroy.ClusterDestroyCommandService;
+import com.repoachiever.service.executor.CommandExecutorService;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Service used for cluster deployment management, including distribution process.
+ */
+@ApplicationScoped
+public class ClusterService {
+ @Inject
+ PropertiesEntity properties;
+
+ @Inject
+ ClusterCommunicationResource clusterCommunicationResource;
+
+ @Inject
+ CommandExecutorService commandExecutorService;
+
+ /**
+ * Perform segregation of the given content locations according to the given segregation limitations.
+ *
+ * @param locations given content locations.
+ * @param separator given content location segregation separator.
+ * @return segregated content locations.
+ */
+ public List> performContentLocationsSegregation(List locations, Integer separator) {
+ List> result = new ArrayList<>();
+
+ List temp = new ArrayList<>();
+
+ Integer counter = 0;
+
+ for (Integer i = 0; i < locations.size(); i++) {
+ temp.add(locations.get(0));
+
+ if (counter.equals(separator - 1)) {
+ result.add(new ArrayList<>(temp));
+
+ temp.clear();
+
+ counter = 0;
+ } else {
+ counter++;
+ }
+ }
+
+ if (!temp.isEmpty()) {
+ result.add(new ArrayList<>(temp));
+ }
+
+ return result;
+ }
+
+ /**
+ * Performs deployment of RepoAchiever Cluster allocation.
+ *
+ * @param name given RepoAchiever Cluster allocation name.
+ * @param clusterContext given RepoAchiever Cluster context.
+ * @return process identificator of the deployed RepoAchiever Cluster instance.
+ * @throws ClusterDeploymentFailureException if deployment operation failed.
+ */
+ public Integer deploy(String name, String clusterContext) throws ClusterDeploymentFailureException {
+ ClusterDeployCommandService clusterDeployCommandService =
+ new ClusterDeployCommandService(
+ clusterContext,
+ properties.getBinDirectory(),
+ properties.getBinClusterLocation());
+
+ CommandExecutorOutputDto clusterDeployCommandOutput;
+
+ try {
+ clusterDeployCommandOutput =
+ commandExecutorService.executeCommand(clusterDeployCommandService);
+ } catch (CommandExecutorException e) {
+ throw new ClusterDeploymentFailureException(e.getMessage());
+ }
+
+ String clusterDeployCommandErrorOutput = clusterDeployCommandOutput.getErrorOutput();
+
+ if (Objects.nonNull(clusterDeployCommandErrorOutput) && !clusterDeployCommandErrorOutput.isEmpty()) {
+ throw new ClusterDeploymentFailureException();
+ }
+
+ Integer result = Integer.parseInt(
+ clusterDeployCommandOutput.
+ getNormalOutput().
+ replaceAll("\n", ""));
+
+ if (!ClusterConfigurationHelper.waitForStart(() -> {
+ try {
+ if (clusterCommunicationResource.retrieveHealthCheck(name)) {
+ return true;
+ }
+ } catch (ClusterOperationFailureException e) {
+ return false;
+ }
+
+ return false;
+ },
+ properties.getCommunicationClusterStartupAwaitFrequency(),
+ properties.getCommunicationClusterStartupTimeout())) {
+ throw new ClusterDeploymentFailureException(new ClusterApplicationTimeoutException().getMessage());
+ }
+
+ return result;
+ }
+
+ /**
+ * Performs destruction of RepoAchiever Cluster allocation.
+ *
+ * @param pid given RepoAchiever Cluster allocation process id.
+ * @throws ClusterDestructionFailureException if destruction operation failed.
+ */
+ public void destroy(Integer pid) throws ClusterDestructionFailureException {
+ ClusterDestroyCommandService clusterDestroyCommandService = new ClusterDestroyCommandService(pid);
+
+ CommandExecutorOutputDto clusterDestroyCommandOutput;
+
+ try {
+ clusterDestroyCommandOutput =
+ commandExecutorService.executeCommand(clusterDestroyCommandService);
+ } catch (CommandExecutorException e) {
+ throw new ClusterDestructionFailureException(e.getMessage());
+ }
+
+ String clusterDestroyCommandErrorOutput = clusterDestroyCommandOutput.getErrorOutput();
+
+ if (Objects.nonNull(clusterDestroyCommandErrorOutput) && !clusterDestroyCommandErrorOutput.isEmpty()) {
+ throw new ClusterDestructionFailureException();
+ }
+ }
+
+ /**
+ * Performs recreation of RepoAchiever Cluster allocation.
+ *
+ * @param pid given process identificator of the allocation RepoAchiever Cluster to be removed.
+ * @param name given RepoAchiever Cluster allocation name.
+ * @param clusterContext given RepoAchiever Cluster context used for the new allocation.
+ * @throws ClusterRecreationFailureException if recreation operation failed.
+ */
+ public Integer recreate(Integer pid, String name, String clusterContext) throws ClusterRecreationFailureException {
+ try {
+ destroy(pid);
+ } catch (ClusterDestructionFailureException e) {
+ throw new ClusterRecreationFailureException(e.getMessage());
+ }
+
+ try {
+ return deploy(name, clusterContext);
+ } catch (ClusterDeploymentFailureException e) {
+ throw new ClusterRecreationFailureException(e.getMessage());
+ }
+ }
+}
+
+
+// TODO: make assignment of random identificators
+
+// TODO: probably move this logic to ClusterService
+
+// TODO: should regenerate topology after each location added
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/service/cluster/common/ClusterConfigurationHelper.java b/api-server/src/main/java/com/repoachiever/service/cluster/common/ClusterConfigurationHelper.java
new file mode 100644
index 0000000..08d86cd
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/cluster/common/ClusterConfigurationHelper.java
@@ -0,0 +1,70 @@
+package com.repoachiever.service.cluster.common;
+
+import java.util.UUID;
+import java.util.concurrent.*;
+
+/**
+ * Contains helpful tools used for RepoAchiever Cluster configuration.
+ */
+public class ClusterConfigurationHelper {
+ private final static ScheduledExecutorService scheduledExecutorService =
+ Executors.newScheduledThreadPool(2);
+
+ /**
+ * Composes name for RepoAchiever Cluster using pre-defined prefix and UUID.
+ *
+ * @param prefix given name prefix.
+ * @return composed RepoAchiever Cluster name.
+ */
+ public static String getName(String prefix) {
+ return String.format("%s-%s", prefix, UUID.randomUUID());
+ }
+
+ /**
+ * Waits till the given callback execution succeeds.
+ *
+ * @param callback given callback.
+ * @param frequency given callback execution check frequency.
+ * @param timeout given callback execution timeout.
+ * @return result of the execution.
+ */
+ public static Boolean waitForStart(Callable callback, Integer frequency, Integer timeout) {
+ CountDownLatch waiter = new CountDownLatch(1);
+
+ ScheduledFuture> awaitTask = scheduledExecutorService.scheduleAtFixedRate(() -> {
+ try {
+ if (callback.call()) {
+ waiter.countDown();
+ }
+ } catch (Exception ignore) {
+ }
+ }, 0, frequency, TimeUnit.MILLISECONDS);
+
+ ScheduledFuture> timeoutTask = scheduledExecutorService.schedule(() -> {
+ if (!awaitTask.isCancelled()) {
+ awaitTask.cancel(true);
+
+ waiter.countDown();
+ }
+ }, timeout, TimeUnit.MILLISECONDS);
+
+
+ try {
+ waiter.await();
+ } catch (InterruptedException e) {
+ return false;
+ }
+
+ if (!awaitTask.isCancelled()) {
+ awaitTask.cancel(true);
+ } else {
+ return false;
+ }
+
+ if (!timeoutTask.isDone()) {
+ timeoutTask.cancel(true);
+ }
+
+ return true;
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/cluster/facade/ClusterFacade.java b/api-server/src/main/java/com/repoachiever/service/cluster/facade/ClusterFacade.java
new file mode 100644
index 0000000..0a28aae
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/cluster/facade/ClusterFacade.java
@@ -0,0 +1,288 @@
+package com.repoachiever.service.cluster.facade;
+
+import com.repoachiever.converter.ClusterContextToJsonConverter;
+import com.repoachiever.converter.ContentCredentialsToClusterContextCredentialsConverter;
+import com.repoachiever.converter.ContentProviderToClusterContextProviderConverter;
+import com.repoachiever.dto.ClusterAllocationDto;
+import com.repoachiever.entity.common.ClusterContextEntity;
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.exception.*;
+import com.repoachiever.model.ContentApplication;
+import com.repoachiever.model.ContentWithdrawal;
+import com.repoachiever.service.cluster.ClusterService;
+import com.repoachiever.service.cluster.common.ClusterConfigurationHelper;
+import com.repoachiever.service.cluster.resource.ClusterCommunicationResource;
+import com.repoachiever.service.config.ConfigService;
+import com.repoachiever.service.state.StateService;
+import com.repoachiever.service.workspace.facade.WorkspaceFacade;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides high-level access to RepoAchiever Cluster related operations.
+ */
+@ApplicationScoped
+public class ClusterFacade {
+ private static final Logger logger = LogManager.getLogger(ClusterFacade.class);
+
+ @Inject
+ PropertiesEntity properties;
+
+ @Inject
+ WorkspaceFacade workspaceFacade;
+
+ @Inject
+ ConfigService configService;
+
+ @Inject
+ ClusterService clusterService;
+
+ @Inject
+ ClusterCommunicationResource clusterCommunicationResource;
+
+ /**
+ * Applies given content application, removing previous topology and deploying new one with up-to-date configuration.
+ *
+ * @param contentApplication given content application used for topology configuration.
+ * @throws ClusterApplicationFailureException if RepoAchiever Cluster application failed.
+ */
+ public void apply(ContentApplication contentApplication) throws ClusterApplicationFailureException {
+ StateService.getTopologyStateGuard().lock();
+
+ String workspaceUnitKey =
+ workspaceFacade.createUnitKey(
+ contentApplication.getProvider(), contentApplication.getCredentials());
+
+ List suspends = new ArrayList<>();
+
+ for (ClusterAllocationDto clusterAllocation : StateService.
+ getClusterAllocationsByWorkspaceUnitKey(workspaceUnitKey)) {
+ logger.info(
+ String.format(
+ "Setting RepoAchiever Cluster allocation to suspend state: %s",
+ clusterAllocation.getName()));
+
+ try {
+ clusterCommunicationResource.performSuspend(clusterAllocation.getName());
+
+ } catch (ClusterOperationFailureException e) {
+ logger.fatal(new ClusterApplicationFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ suspends.add(clusterAllocation);
+ }
+
+ List> segregation = clusterService.performContentLocationsSegregation(
+ contentApplication.getLocations(),
+ configService.getConfig().getResource().getCluster().getMaxWorkers());
+
+ List candidates = new ArrayList<>();
+
+ for (List locations : segregation) {
+ String name = ClusterConfigurationHelper.getName(properties.getCommunicationClusterBase());
+
+ String context = ClusterContextToJsonConverter.convert(
+ ClusterContextEntity.of(
+ ClusterContextEntity.Metadata.of(name, workspaceUnitKey),
+ ClusterContextEntity.Filter.of(locations),
+ ClusterContextEntity.Service.of(
+ ContentProviderToClusterContextProviderConverter.convert(
+ contentApplication.getProvider()),
+ ContentCredentialsToClusterContextCredentialsConverter.convert(
+ contentApplication.getProvider(),
+ contentApplication.getCredentials().getExternal())),
+ ClusterContextEntity.Communication.of(
+ properties.getCommunicationApiServerName(),
+ configService.getConfig().getCommunication().getPort()),
+ ClusterContextEntity.Content.of(
+ configService.getConfig().getContent().getFormat()),
+ ClusterContextEntity.Resource.of(
+ ClusterContextEntity.Resource.Cluster.of(
+ configService.getConfig().getResource().getCluster().getMaxWorkers()),
+ ClusterContextEntity.Resource.Worker.of(
+ configService.getConfig().getResource().getWorker().getFrequency()))));
+
+ logger.info(
+ String.format("Deploying RepoAchiever Cluster new allocation: %s", name));
+
+ Integer pid;
+
+ try {
+ pid = clusterService.deploy(name, context);
+ } catch (ClusterDeploymentFailureException e1) {
+ for (ClusterAllocationDto candidate : candidates) {
+ logger.info(
+ String.format("Removing RepoAchiever Cluster candidate allocation: %s", candidate.getName()));
+
+ try {
+ clusterService.destroy(candidate.getPid());
+ } catch (ClusterDestructionFailureException e2) {
+ throw new ClusterApplicationFailureException(e1.getMessage(), e2.getMessage());
+ }
+ }
+
+ for (ClusterAllocationDto suspended : suspends) {
+ logger.info(
+ String.format("Setting RepoAchiever Cluster suspended allocation to serve state: %s", suspended.getName()));
+
+ try {
+ clusterCommunicationResource.performServe(suspended.getName());
+ } catch (ClusterOperationFailureException e2) {
+ logger.fatal(new ClusterApplicationFailureException(e1.getMessage(), e2.getMessage()).getMessage());
+ return;
+ }
+ }
+
+ throw new ClusterApplicationFailureException(e1.getMessage());
+ }
+
+ candidates.add(ClusterAllocationDto.of(name, pid, context, workspaceUnitKey));
+ }
+
+ for (ClusterAllocationDto candidate : candidates) {
+ logger.info(
+ String.format(
+ "Setting RepoAchiever Cluster candidate allocation to serve state: %s",
+ candidate.getName()));
+
+ try {
+ clusterCommunicationResource.performServe(candidate.getName());
+ } catch (ClusterOperationFailureException e1) {
+ for (ClusterAllocationDto suspended : suspends) {
+ logger.info(
+ String.format(
+ "Setting RepoAchiever Cluster suspended allocation to serve state: %s",
+ suspended.getName()));
+
+ try {
+ clusterCommunicationResource.performServe(suspended.getName());
+ } catch (ClusterOperationFailureException e2) {
+ logger.fatal(new ClusterApplicationFailureException(
+ e1.getMessage(), e2.getMessage()).getMessage());
+ return;
+ }
+ }
+
+ throw new ClusterApplicationFailureException(e1.getMessage());
+ }
+ }
+
+ for (ClusterAllocationDto suspended : suspends) {
+ logger.info(
+ String.format("Removing RepoAchiever Cluster suspended allocation: %s", suspended.getName()));
+
+ try {
+ clusterService.destroy(suspended.getPid());
+ } catch (ClusterDestructionFailureException e) {
+ throw new ClusterApplicationFailureException(e.getMessage());
+ }
+ }
+
+ StateService.addClusterAllocations(candidates);
+
+ StateService.removeClusterAllocationByNames(
+ suspends.stream().map(ClusterAllocationDto::getName).toList());
+
+ StateService.getTopologyStateGuard().unlock();
+ }
+
+ /**
+ * Applies given content withdrawal, removing existing content configuration with the given properties.
+ *
+ * @param contentWithdrawal given content application used for topology configuration.
+ * @throws ClusterWithdrawalFailureException if RepoAchiever Cluster withdrawal failed.
+ */
+ public void destroy(ContentWithdrawal contentWithdrawal) throws ClusterWithdrawalFailureException {
+ StateService.getTopologyStateGuard().lock();
+
+ String workspaceUnitKey =
+ workspaceFacade.createUnitKey(
+ contentWithdrawal.getProvider(), contentWithdrawal.getCredentials());
+
+ List clusterAllocations =
+ StateService.getClusterAllocationsByWorkspaceUnitKey(workspaceUnitKey);
+
+ for (ClusterAllocationDto clusterAllocation : clusterAllocations) {
+ logger.info(
+ String.format("Removing RepoAchiever Cluster allocation: %s", clusterAllocation.getName()));
+
+ try {
+ clusterService.destroy(clusterAllocation.getPid());
+ } catch (ClusterDestructionFailureException e) {
+ throw new ClusterWithdrawalFailureException(e.getMessage());
+ }
+ }
+
+ StateService.removeClusterAllocationByNames(
+ clusterAllocations.stream().map(ClusterAllocationDto::getName).toList());
+
+ StateService.getTopologyStateGuard().unlock();
+ }
+
+ /**
+ * Destroys all the created RepoAchiever Cluster allocations.
+ *
+ * @throws ClusterFullDestructionFailureException if RepoAchiever Cluster full destruction failed.
+ */
+ public void destroyAll() throws ClusterFullDestructionFailureException {
+ StateService.getTopologyStateGuard().lock();
+
+ for (ClusterAllocationDto clusterAllocation : StateService.getClusterAllocations()) {
+ logger.info(
+ String.format("Removing RepoAchiever Cluster allocation: %s", clusterAllocation.getName()));
+
+ try {
+ clusterService.destroy(clusterAllocation.getPid());
+ } catch (ClusterDestructionFailureException e) {
+ throw new ClusterFullDestructionFailureException(e.getMessage());
+ }
+ }
+
+ StateService.getTopologyStateGuard().unlock();
+ }
+
+ /**
+ * Reapplies all unhealthy RepoAchiever Cluster allocations, which healthcheck operation failed for, recreating them.
+ *
+ * @throws ClusterUnhealthyReapplicationFailureException if RepoAchiever Cluster unhealthy allocation reapplication fails.
+ */
+ public void reapplyUnhealthy() throws ClusterUnhealthyReapplicationFailureException {
+ StateService.getTopologyStateGuard().lock();
+
+
+ List updates = new ArrayList<>();
+ List removable = new ArrayList<>();
+
+ for (ClusterAllocationDto clusterAllocation : StateService.getClusterAllocations()) {
+ try {
+ clusterService.destroy(clusterAllocation.getPid());
+ } catch (ClusterDestructionFailureException ignored) {
+ }
+
+ Integer pid;
+
+ try {
+ pid = clusterService.deploy(clusterAllocation.getName(), clusterAllocation.getContext());
+ } catch (ClusterDeploymentFailureException e) {
+ throw new ClusterUnhealthyReapplicationFailureException(e.getMessage());
+ }
+
+ removable.add(clusterAllocation.getName());
+//
+// updates.add(ClusterAllocationDto.of(
+// clusterAllocation.getName(), pid, clusterAllocation.getContext()));
+ }
+
+ StateService.removeClusterAllocationByNames(removable);
+
+// updates.forEach(StateService::addClusterAllocation);
+
+ StateService.getTopologyStateGuard().unlock();
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/service/cluster/resource/ClusterCommunicationResource.java b/api-server/src/main/java/com/repoachiever/service/cluster/resource/ClusterCommunicationResource.java
new file mode 100644
index 0000000..86c8a13
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/cluster/resource/ClusterCommunicationResource.java
@@ -0,0 +1,142 @@
+package com.repoachiever.service.cluster.resource;
+
+import com.repoachiever.exception.ClusterOperationFailureException;
+import com.repoachiever.exception.CommunicationConfigurationFailureException;
+import com.repoachiever.service.communication.common.CommunicationProviderConfigurationHelper;
+import com.repoachiever.service.config.ConfigService;
+import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.ApplicationScoped;
+import com.repoachiever.service.communication.cluster.IClusterCommunicationService;
+import jakarta.inject.Inject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.rmi.NotBoundException;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+
+/**
+ * Represents implementation for RepoAchiever Cluster remote API.
+ */
+@ApplicationScoped
+public class ClusterCommunicationResource {
+ private static final Logger logger = LogManager.getLogger(ClusterCommunicationResource.class);
+
+ @Inject
+ ConfigService configService;
+
+ private Registry registry;
+
+ @PostConstruct
+ private void configure() {
+ try {
+ this.registry = LocateRegistry.getRegistry(
+ configService.getConfig().getCommunication().getPort());
+ } catch (RemoteException e) {
+ logger.fatal(new CommunicationConfigurationFailureException(e.getMessage()).getMessage());
+ }
+ }
+
+ /**
+ * Retrieves remote RepoAchiever Cluster allocation with the given name.
+ *
+ * @param name given RepoAchiever Cluster allocation name.
+ * @return retrieved RepoAchiever Cluster allocation.
+ * @throws ClusterOperationFailureException if RepoAchiever Cluster operation fails.
+ */
+ private IClusterCommunicationService retrieveAllocation(String name) throws ClusterOperationFailureException {
+ try {
+ return (IClusterCommunicationService) registry.lookup(
+ CommunicationProviderConfigurationHelper.getBindName(
+ configService.getConfig().getCommunication().getPort(),
+ name));
+ } catch (RemoteException | NotBoundException e) {
+ throw new ClusterOperationFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Performs RepoAchiever Cluster suspend operation. Has no effect if RepoAchiever Cluster was already suspended
+ * previously.
+ *
+ * @param name given name of RepoAchiever Cluster.
+ * @throws ClusterOperationFailureException if RepoAchiever Cluster operation fails.
+ */
+ public void performSuspend(String name) throws ClusterOperationFailureException {
+ IClusterCommunicationService allocation = retrieveAllocation(name);
+
+ try {
+ allocation.performSuspend();
+ } catch (RemoteException e) {
+ throw new ClusterOperationFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Performs RepoAchiever Cluster serve operation. Has no effect if RepoAchiever Cluster was not suspended previously.
+ *
+ * @param name given name of RepoAchiever Cluster.
+ * @throws ClusterOperationFailureException if RepoAchiever Cluster operation fails.
+ */
+ public void performServe(String name) throws ClusterOperationFailureException {
+ IClusterCommunicationService allocation = retrieveAllocation(name);
+
+ try {
+ allocation.performServe();
+ } catch (RemoteException e) {
+ throw new ClusterOperationFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Retrieves health check status of the RepoAchiever Cluster with the given name.
+ *
+ * @param name given name of RepoAchiever Cluster.
+ * @return result of the check.
+ * @throws ClusterOperationFailureException if RepoAchiever Cluster operation fails.
+ */
+ public Boolean retrieveHealthCheck(String name) throws ClusterOperationFailureException {
+ IClusterCommunicationService allocation = retrieveAllocation(name);
+
+ try {
+ return allocation.retrieveHealthCheck();
+ } catch (RemoteException e) {
+ throw new ClusterOperationFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Retrieves version of the RepoAchiever Cluster with the given name.
+ *
+ * @param name given name of RepoAchiever Cluster.
+ * @return retrieved version of RepoAchiever Cluster.
+ * @throws ClusterOperationFailureException if RepoAchiever Cluster operation fails.
+ */
+ public String retrieveVersion(String name) throws ClusterOperationFailureException {
+ IClusterCommunicationService allocation = retrieveAllocation(name);
+
+ try {
+ return allocation.retrieveVersion();
+ } catch (RemoteException e) {
+ throw new ClusterOperationFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Retrieves amount of RepoAchiever Worker owned by RepoAchiever Cluster with the given name.
+ *
+ * @param name given name of RepoAchiever Cluster.
+ * @return retrieved amount of RepoAchiever Worker owned by RepoAchiever Cluster allocation.
+ * @throws ClusterOperationFailureException if RepoAchiever Cluster operation fails.
+ */
+ public Integer retrieveWorkerAmount(String name) throws ClusterOperationFailureException {
+ IClusterCommunicationService allocation = retrieveAllocation(name);
+
+ try {
+ return allocation.retrieveWorkerAmount();
+ } catch (RemoteException e) {
+ throw new ClusterOperationFailureException(e.getMessage());
+ }
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/command/cluster/common/ClusterConfigurationHelper.java b/api-server/src/main/java/com/repoachiever/service/command/cluster/common/ClusterConfigurationHelper.java
new file mode 100644
index 0000000..0f1fd0f
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/command/cluster/common/ClusterConfigurationHelper.java
@@ -0,0 +1,25 @@
+package com.repoachiever.service.command.cluster.common;
+
+import com.repoachiever.service.command.common.CommandConfigurationHelper;
+
+import java.util.HashMap;
+
+/**
+ * Contains helpful tools used for Grafana deployment configuration.
+ */
+public class ClusterConfigurationHelper {
+ /**
+ * Composes environment variables for Grafana deployment.
+ *
+ * @param clusterContext RepoAchiever Cluster context used for cluster configuration.
+ * @return composed environment variables.
+ */
+ public static String getEnvironmentVariables(String clusterContext) {
+ return CommandConfigurationHelper.getEnvironmentVariables(
+ new HashMap<>() {
+ {
+ put("REPOACHIEVER_CLUSTER_CONTEXT", clusterContext);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/service/command/cluster/deploy/ClusterDeployCommandService.java b/api-server/src/main/java/com/repoachiever/service/command/cluster/deploy/ClusterDeployCommandService.java
new file mode 100644
index 0000000..249c0a3
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/command/cluster/deploy/ClusterDeployCommandService.java
@@ -0,0 +1,38 @@
+package com.repoachiever.service.command.cluster.deploy;
+
+import com.repoachiever.service.command.cluster.common.ClusterConfigurationHelper;
+import process.SProcess;
+import process.SProcessExecutor;
+
+import java.nio.file.Path;
+
+/**
+ * Represents RepoAchiever Cluster deployment command.
+ */
+public class ClusterDeployCommandService extends SProcess {
+ private final String command;
+ private final SProcessExecutor.OS osType;
+
+ public ClusterDeployCommandService(
+ String clusterContext, String binDirectory, String binClusterLocation) {
+ this.osType = SProcessExecutor.getCommandExecutor().getOSType();
+
+ this.command = switch (osType) {
+ case WINDOWS -> null;
+ case UNIX, MAC, ANY -> String.format(
+ "%s java -jar %s & echo $!",
+ ClusterConfigurationHelper.getEnvironmentVariables(clusterContext),
+ Path.of(binDirectory, binClusterLocation));
+ };
+ }
+
+ @Override
+ public String getCommand() {
+ return command;
+ }
+
+ @Override
+ public SProcessExecutor.OS getOSType() {
+ return osType;
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/command/cluster/destroy/ClusterDestroyCommandService.java b/api-server/src/main/java/com/repoachiever/service/command/cluster/destroy/ClusterDestroyCommandService.java
new file mode 100644
index 0000000..fb45f93
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/command/cluster/destroy/ClusterDestroyCommandService.java
@@ -0,0 +1,31 @@
+package com.repoachiever.service.command.cluster.destroy;
+
+import process.SProcess;
+import process.SProcessExecutor;
+
+/**
+ * Represents RepoAchiever Cluster destruction command.
+ */
+public class ClusterDestroyCommandService extends SProcess {
+ private final String command;
+ private final SProcessExecutor.OS osType;
+
+ public ClusterDestroyCommandService(Integer pid) {
+ this.osType = SProcessExecutor.getCommandExecutor().getOSType();
+
+ this.command = switch (osType) {
+ case WINDOWS -> null;
+ case UNIX, MAC, ANY -> String.format("kill -15 %d", pid);
+ };
+ }
+
+ @Override
+ public String getCommand() {
+ return command;
+ }
+
+ @Override
+ public SProcessExecutor.OS getOSType() {
+ return osType;
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/command/common/CommandConfigurationHelper.java b/api-server/src/main/java/com/repoachiever/service/command/common/CommandConfigurationHelper.java
new file mode 100644
index 0000000..ae227ba
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/command/common/CommandConfigurationHelper.java
@@ -0,0 +1,71 @@
+package com.repoachiever.service.command.common;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Contains helpful tools used for general command configuration.
+ */
+public class CommandConfigurationHelper {
+ /**
+ * Composes environment variables.
+ *
+ * @param attributes attributes to be included.
+ * @return composed environment variables.
+ */
+ public static String getEnvironmentVariables(Map attributes) {
+ return attributes.entrySet().stream()
+ .map(element -> String.format("%s='%s'", element.getKey(), element.getValue()))
+ .collect(Collectors.joining(" "));
+ }
+
+ /**
+ * Composes Docker volumes declaration.
+ *
+ * @param attributes attributes to be included.
+ * @return composed Docker volumes declaration.
+ */
+ public static String getDockerVolumes(Map attributes) {
+ return attributes.entrySet().stream()
+ .map(element -> String.format("-v %s:%s", element.getKey(), element.getValue()))
+ .collect(Collectors.joining(" "));
+ }
+
+ /**
+ * Composes Docker command arguments declaration.
+ *
+ * @param attributes attributes to be included.
+ * @return composed Docker command arguments declaration.
+ */
+ public static String getDockerCommandArguments(Map attributes) {
+ return attributes.entrySet().stream()
+ .map(element -> String.format("--%s=%s", element.getKey(), element.getValue()))
+ .collect(Collectors.joining(" "));
+ }
+
+ /**
+ * Composes Docker command options declaration.
+ *
+ * @param attributes attributes to be included.
+ * @return composed Docker command options declaration.
+ */
+ public static String getDockerCommandOptions(List attributes) {
+ return attributes.
+ stream().
+ map(element -> String.format("--%s", element))
+ .collect(Collectors.joining(" "));
+ }
+
+ /**
+ * Composes Docker port mappings.
+ *
+ * @param attributes attributes to be included.
+ * @return composed Docker port mappings.
+ */
+ public static String getDockerPorts(Map attributes) {
+ return attributes.entrySet().stream()
+ .map(element -> String.format("-p 0.0.0.0:%d:%d", element.getKey(), element.getValue()))
+ .collect(Collectors.joining(" "));
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/command/docker/availability/DockerAvailabilityCheckCommandService.java b/api-server/src/main/java/com/repoachiever/service/command/docker/availability/DockerAvailabilityCheckCommandService.java
new file mode 100644
index 0000000..a40d911
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/command/docker/availability/DockerAvailabilityCheckCommandService.java
@@ -0,0 +1,33 @@
+package com.repoachiever.service.command.docker.availability;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import process.SProcess;
+import process.SProcessExecutor;
+
+/**
+ * Represents Docker availability check command.
+ */
+@ApplicationScoped
+public class DockerAvailabilityCheckCommandService extends SProcess {
+ private final String command;
+ private final SProcessExecutor.OS osType;
+
+ public DockerAvailabilityCheckCommandService() {
+ this.osType = SProcessExecutor.getCommandExecutor().getOSType();
+
+ this.command = switch (osType) {
+ case WINDOWS -> null;
+ case UNIX, MAC, ANY -> "docker ps 2>/dev/null";
+ };
+ }
+
+ @Override
+ public String getCommand() {
+ return command;
+ }
+
+ @Override
+ public SProcessExecutor.OS getOSType() {
+ return osType;
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/command/docker/inspect/remove/DockerInspectRemoveCommandService.java b/api-server/src/main/java/com/repoachiever/service/command/docker/inspect/remove/DockerInspectRemoveCommandService.java
new file mode 100644
index 0000000..a0fc855
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/command/docker/inspect/remove/DockerInspectRemoveCommandService.java
@@ -0,0 +1,32 @@
+package com.repoachiever.service.command.docker.inspect.remove;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import process.SProcess;
+import process.SProcessExecutor;
+
+/**
+ * Represents Docker container removal command. Does nothing if given container does not exist.
+ */
+public class DockerInspectRemoveCommandService extends SProcess {
+ private final String command;
+ private final SProcessExecutor.OS osType;
+
+ public DockerInspectRemoveCommandService(String name) {
+ this.osType = SProcessExecutor.getCommandExecutor().getOSType();
+
+ this.command = switch (osType) {
+ case WINDOWS -> null;
+ case UNIX, MAC, ANY -> String.format("docker rm -f %s 2>/dev/null", name);
+ };
+ }
+
+ @Override
+ public String getCommand() {
+ return command;
+ }
+
+ @Override
+ public SProcessExecutor.OS getOSType() {
+ return osType;
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/command/docker/network/create/DockerNetworkCreateCommandService.java b/api-server/src/main/java/com/repoachiever/service/command/docker/network/create/DockerNetworkCreateCommandService.java
new file mode 100644
index 0000000..1d6c3cc
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/command/docker/network/create/DockerNetworkCreateCommandService.java
@@ -0,0 +1,34 @@
+package com.repoachiever.service.command.docker.network.create;
+
+import process.SProcess;
+import process.SProcessExecutor;
+
+/**
+ * Represents Docker network creation command.
+ */
+public class DockerNetworkCreateCommandService extends SProcess {
+ private final String command;
+ private final SProcessExecutor.OS osType;
+
+ public DockerNetworkCreateCommandService(String networkName) {
+ this.osType = SProcessExecutor.getCommandExecutor().getOSType();
+
+ this.command = switch (osType) {
+ case WINDOWS -> null;
+ case UNIX, MAC, ANY -> String.format(
+ "docker network inspect %s >/dev/null 2>&1 || docker network create -d bridge %s",
+ networkName,
+ networkName);
+ };
+ }
+
+ @Override
+ public String getCommand() {
+ return command;
+ }
+
+ @Override
+ public SProcessExecutor.OS getOSType() {
+ return osType;
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/command/docker/network/remove/DockerNetworkRemoveCommandService.java b/api-server/src/main/java/com/repoachiever/service/command/docker/network/remove/DockerNetworkRemoveCommandService.java
new file mode 100644
index 0000000..0f94225
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/command/docker/network/remove/DockerNetworkRemoveCommandService.java
@@ -0,0 +1,31 @@
+package com.repoachiever.service.command.docker.network.remove;
+
+import process.SProcess;
+import process.SProcessExecutor;
+
+/**
+ * Represents Docker network removal command.
+ */
+public class DockerNetworkRemoveCommandService extends SProcess {
+ private final String command;
+ private final SProcessExecutor.OS osType;
+
+ public DockerNetworkRemoveCommandService(String networkName) {
+ this.osType = SProcessExecutor.getCommandExecutor().getOSType();
+
+ this.command = switch (osType) {
+ case WINDOWS -> null;
+ case UNIX, MAC, ANY -> String.format("docker network rm %s 2> /dev/null", networkName);
+ };
+ }
+
+ @Override
+ public String getCommand() {
+ return command;
+ }
+
+ @Override
+ public SProcessExecutor.OS getOSType() {
+ return osType;
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/command/grafana/GrafanaDeployCommandService.java b/api-server/src/main/java/com/repoachiever/service/command/grafana/GrafanaDeployCommandService.java
new file mode 100644
index 0000000..4498f9b
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/command/grafana/GrafanaDeployCommandService.java
@@ -0,0 +1,38 @@
+package com.repoachiever.service.command.grafana;
+
+import com.repoachiever.service.command.grafana.common.GrafanaConfigurationHelper;
+import process.SProcess;
+import process.SProcessExecutor;
+
+/**
+ * Represents Grafana deployment command.
+ */
+public class GrafanaDeployCommandService extends SProcess {
+ private final String command;
+ private final SProcessExecutor.OS osType;
+
+ public GrafanaDeployCommandService(
+ String name, String image, Integer port, String configLocation, String internalLocation) {
+ this.osType = SProcessExecutor.getCommandExecutor().getOSType();
+
+ this.command = switch (osType) {
+ case WINDOWS -> null;
+ case UNIX, MAC, ANY -> String.format(
+ "docker run -d %s %s --name %s %s",
+ GrafanaConfigurationHelper.getDockerVolumes(configLocation, internalLocation),
+ GrafanaConfigurationHelper.getDockerPorts(port),
+ name,
+ image);
+ };
+ }
+
+ @Override
+ public String getCommand() {
+ return command;
+ }
+
+ @Override
+ public SProcessExecutor.OS getOSType() {
+ return osType;
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/command/grafana/common/GrafanaConfigurationHelper.java b/api-server/src/main/java/com/repoachiever/service/command/grafana/common/GrafanaConfigurationHelper.java
new file mode 100644
index 0000000..0d203ae
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/command/grafana/common/GrafanaConfigurationHelper.java
@@ -0,0 +1,57 @@
+package com.repoachiever.service.command.grafana.common;
+
+import com.repoachiever.service.command.common.CommandConfigurationHelper;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+/** Contains helpful tools used for Grafana deployment configuration. */
+public class GrafanaConfigurationHelper {
+ /**
+ * Composes environment variables for Grafana deployment.
+ *
+ * @return composed environment variables.
+ */
+ public static String getEnvironmentVariables() {
+ return CommandConfigurationHelper.getEnvironmentVariables(
+ new HashMap<>() {
+ {
+ put("GF_SECURITY_ADMIN_PASSWORD", "repoachiever");
+ put("GF_USERS_ALLOW_SIGN_UP", "false");
+ }
+ });
+ }
+
+ /**
+ * Composes Grafana Docker volumes declaration.
+ *
+ * @param configLocation given Grafana local config directory location.
+ * @param internalLocation given Grafana local internal directory location.
+ * @return composed Grafana Docker volumes declaration.
+ */
+ public static String getDockerVolumes(String configLocation, String internalLocation) {
+ return CommandConfigurationHelper.getDockerVolumes(
+ new HashMap<>() {
+ {
+ put(configLocation, "/etc/grafana/provisioning/");
+ put(internalLocation, "/var/lib/grafana");
+ }
+ });
+ }
+
+ /**
+ * Composes Prometheus Docker port mappings.
+ *
+ * @param port given Prometheus Docker port.
+ * @return composed Prometheus Docker port mappings.
+ */
+ public static String getDockerPorts(Integer port) {
+ return CommandConfigurationHelper.getDockerPorts(
+ new HashMap<>() {
+ {
+ put(port, 3000);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/service/command/nodeexporter/NodeExporterDeployCommandService.java b/api-server/src/main/java/com/repoachiever/service/command/nodeexporter/NodeExporterDeployCommandService.java
new file mode 100644
index 0000000..de20bc5
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/command/nodeexporter/NodeExporterDeployCommandService.java
@@ -0,0 +1,38 @@
+package com.repoachiever.service.command.nodeexporter;
+
+import com.repoachiever.service.command.nodeexporter.common.NodeExporterConfigurationHelper;
+import process.SProcess;
+import process.SProcessExecutor;
+
+/**
+ * Represents Prometheus Node Exporter deployment command.
+ */
+public class NodeExporterDeployCommandService extends SProcess {
+ private final String command;
+ private final SProcessExecutor.OS osType;
+
+ public NodeExporterDeployCommandService(String name, String image, Integer port) {
+ this.osType = SProcessExecutor.getCommandExecutor().getOSType();
+
+ this.command = switch (osType) {
+ case WINDOWS -> null;
+ case UNIX, MAC, ANY -> String.format(
+ "docker run -d %s %s --name %s %s %s",
+ NodeExporterConfigurationHelper.getDockerVolumes(),
+ NodeExporterConfigurationHelper.getDockerPorts(port),
+ name,
+ image,
+ NodeExporterConfigurationHelper.getDockerCommandArguments());
+ };
+ }
+
+ @Override
+ public String getCommand() {
+ return command;
+ }
+
+ @Override
+ public SProcessExecutor.OS getOSType() {
+ return osType;
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/command/nodeexporter/common/NodeExporterConfigurationHelper.java b/api-server/src/main/java/com/repoachiever/service/command/nodeexporter/common/NodeExporterConfigurationHelper.java
new file mode 100644
index 0000000..cbf60fc
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/command/nodeexporter/common/NodeExporterConfigurationHelper.java
@@ -0,0 +1,60 @@
+package com.repoachiever.service.command.nodeexporter.common;
+
+import com.repoachiever.service.command.common.CommandConfigurationHelper;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+/**
+ * Contains helpful tools used for Prometheus NodeExporter deployment configuration.
+ */
+public class NodeExporterConfigurationHelper {
+ /**
+ * Composes Prometheus Node Exporter Docker volumes declaration.
+ *
+ * @return composed Prometheus Node Exporter Docker volumes declaration.
+ */
+ public static String getDockerVolumes() {
+ return CommandConfigurationHelper.getDockerVolumes(
+ new HashMap<>() {
+ {
+ put("/proc", "/host/proc:ro");
+ put("/sys", "/host/sys:ro");
+ put("/", "/rootfs:ro");
+ }
+ });
+ }
+
+ /**
+ * Composes Prometheus Node Exporter Docker command arguments declaration.
+ *
+ * @return composed Prometheus Node Exporter Docker command arguments declaration.
+ */
+ public static String getDockerCommandArguments() {
+ return CommandConfigurationHelper.getDockerCommandArguments(
+ new LinkedHashMap<>() {
+ {
+ put("path.procfs", "/host/proc");
+ put("path.sysfs", "/host/sys");
+ put("collector.filesystem.ignored-mount-points", "\"^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)\"");
+ }
+ });
+ }
+
+
+ /**
+ * Composes Prometheus Node Exporter Docker port mappings.
+ *
+ * @param port given Prometheus Node Exporter Docker port.
+ * @return composed Prometheus Node Exporter Docker port mappings.
+ */
+ public static String getDockerPorts(Integer port) {
+ return CommandConfigurationHelper.getDockerPorts(
+ new HashMap<>() {
+ {
+ put(port, port);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/service/command/prometheus/PrometheusDeployCommandService.java b/api-server/src/main/java/com/repoachiever/service/command/prometheus/PrometheusDeployCommandService.java
new file mode 100644
index 0000000..c835878
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/command/prometheus/PrometheusDeployCommandService.java
@@ -0,0 +1,56 @@
+package com.repoachiever.service.command.prometheus;
+
+import com.repoachiever.service.command.prometheus.common.PrometheusConfigurationHelper;
+import process.SProcess;
+import process.SProcessExecutor;
+
+/**
+ * Represents Prometheus deployment command.
+ */
+public class PrometheusDeployCommandService extends SProcess {
+ private final String command;
+ private final SProcessExecutor.OS osType;
+
+ public PrometheusDeployCommandService(
+ String name, String image, Integer port, String configLocation, String internalLocation) {
+ this.osType = SProcessExecutor.getCommandExecutor().getOSType();
+
+ this.command = switch (osType) {
+ case WINDOWS -> null;
+ case UNIX, MAC, ANY -> String.format(
+ "docker run -d %s %s %s --name %s %s %s %s",
+ PrometheusConfigurationHelper.getDockerParameters(),
+ PrometheusConfigurationHelper.getDockerVolumes(configLocation, internalLocation),
+ PrometheusConfigurationHelper.getDockerPorts(port),
+ name,
+ image,
+ PrometheusConfigurationHelper.getDockerCommandArguments(),
+ PrometheusConfigurationHelper.getDockerCommandOptions());
+ };
+ }
+
+ @Override
+ public String getCommand() {
+ return command;
+ }
+
+ @Override
+ public SProcessExecutor.OS getOSType() {
+ return osType;
+ }
+}
+
+//prometheus:
+// image: prom/prometheus:v2.36.2
+// volumes:
+// - ./prometheus/:/etc/prometheus/
+// - prometheus_data:/prometheus
+// command:
+// - '--config.file=/etc/prometheus/prometheus.yml'
+// - '--storage.tsdb.path=/prometheus'
+// - '--web.console.libraries=/usr/share/prometheus/console_libraries'
+// - '--web.console.templates=/usr/share/prometheus/consoles'
+// - '--web.enable-lifecycle'
+// - '--web.enable-admin-api'
+// ports:
+// - 9090:9090
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/service/command/prometheus/common/PrometheusConfigurationHelper.java b/api-server/src/main/java/com/repoachiever/service/command/prometheus/common/PrometheusConfigurationHelper.java
new file mode 100644
index 0000000..85fb2c7
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/command/prometheus/common/PrometheusConfigurationHelper.java
@@ -0,0 +1,86 @@
+package com.repoachiever.service.command.prometheus.common;
+
+import com.repoachiever.service.command.common.CommandConfigurationHelper;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.UUID;
+
+
+/**
+ * Contains helpful tools used for Prometheus deployment configuration.
+ */
+public class PrometheusConfigurationHelper {
+ /**
+ * Composes Prometheus Docker additional parameters.
+ *
+ * @return composed Docker additional parameters.
+ */
+ public static String getDockerParameters() {
+ return "--add-host=host.docker.internal:host-gateway";
+ }
+
+ /**
+ * Composes Prometheus Docker volumes declaration.
+ *
+ * @param configLocation given Prometheus local config directory location.
+ * @param internalLocation given Prometheus local internal directory location.
+ * @return composed Prometheus Docker volumes declaration.
+ */
+ public static String getDockerVolumes(String configLocation, String internalLocation) {
+ return CommandConfigurationHelper.getDockerVolumes(
+ new HashMap<>() {
+ {
+ put(configLocation, "/etc/prometheus/");
+ put(internalLocation, "/prometheus");
+ }
+ });
+ }
+
+ /**
+ * Composes Prometheus Docker command arguments declaration.
+ *
+ * @return composed Prometheus Docker command arguments declaration.
+ */
+ public static String getDockerCommandArguments() {
+ return CommandConfigurationHelper.getDockerCommandArguments(
+ new LinkedHashMap<>() {
+ {
+ put("config.file", "/etc/prometheus/prometheus.yml");
+ put("storage.tsdb.path", "/prometheus");
+ put("web.console.libraries", "/usr/share/prometheus/console_libraries");
+ put("web.console.templates", "/usr/share/prometheus/consoles");
+ }
+ });
+ }
+
+ /**
+ * Composes Prometheus Docker command options declaration.
+ *
+ * @return composed Prometheus Docker command options declaration.
+ */
+ public static String getDockerCommandOptions() {
+ return CommandConfigurationHelper.getDockerCommandOptions(
+ List.of("web.enable-lifecycle",
+ "web.enable-admin-api"));
+ }
+
+ /**
+ * Composes Prometheus Docker port mappings.
+ *
+ * @param port given Prometheus Docker port.
+ * @return composed Prometheus Docker port mappings.
+ */
+ public static String getDockerPorts(Integer port) {
+ return CommandConfigurationHelper.getDockerPorts(
+ new HashMap<>() {
+ {
+ put(port, port);
+ }
+ });
+ }
+}
+
+
+
diff --git a/api-server/src/main/java/com/repoachiever/service/communication/apiserver/IApiServerCommunicationService.java b/api-server/src/main/java/com/repoachiever/service/communication/apiserver/IApiServerCommunicationService.java
new file mode 100644
index 0000000..e7e7a15
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/communication/apiserver/IApiServerCommunicationService.java
@@ -0,0 +1,45 @@
+package com.repoachiever.service.communication.apiserver;
+
+import java.io.InputStream;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Represents communication provider for RepoAchiever API Server.
+ */
+public interface IApiServerCommunicationService extends Remote {
+ /**
+ * Performs raw content upload operation, initiated by RepoAchiever Cluster.
+ *
+ * @param workspaceUnitKey given user workspace unit key.
+ * @param content given content to be uploaded.
+ * @throws RemoteException if remote request fails.
+ */
+ void performRawContentUpload(String workspaceUnitKey, InputStream content) throws RemoteException;
+
+ /**
+ * Performs additional content(issues, prs, releases) upload operation, initiated by RepoAchiever Cluster.
+ *
+ * @param workspaceUnitKey given user workspace unit key.
+ * @param content given content to be uploaded.
+ * @throws RemoteException if remote request fails.
+ */
+ void performAdditionalContentUpload(String workspaceUnitKey, String content) throws RemoteException;
+
+ /**
+ * Handles incoming log messages related to the given RepoAchiever Cluster allocation.
+ *
+ * @param name given RepoAchiever Cluster allocation name.
+ * @param message given RepoAchiever Cluster log message.
+ * @throws RemoteException if remote request fails.
+ */
+ void performLogsTransfer(String name, String message) throws RemoteException;
+
+ /**
+ * Retrieves latest RepoAchiever API Server health check states.
+ *
+ * @return RepoAchiever API Server health check status.
+ * @throws RemoteException if remote request fails.
+ */
+ Boolean retrieveHealthCheck() throws RemoteException;
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/service/communication/cluster/IClusterCommunicationService.java b/api-server/src/main/java/com/repoachiever/service/communication/cluster/IClusterCommunicationService.java
new file mode 100644
index 0000000..92fc69b
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/communication/cluster/IClusterCommunicationService.java
@@ -0,0 +1,50 @@
+package com.repoachiever.service.communication.cluster;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/** Represents client for RepoAchiever Cluster remote API. */
+public interface IClusterCommunicationService extends Remote {
+ /**
+ * Performs RepoAchiever Cluster suspend operation. Has no effect if RepoAchiever Cluster was
+ * already suspended previously.
+ *
+ * @throws RemoteException if remote request fails.
+ */
+ void performSuspend() throws RemoteException;
+
+ /**
+ * Performs RepoAchiever Cluster serve operation. Has no effect if RepoAchiever Cluster was not
+ * suspended previously.
+ *
+ * @throws RemoteException if remote request fails.
+ */
+ void performServe() throws RemoteException;
+
+ /**
+ * Retrieves latest RepoAchiever Cluster health check states.
+ *
+ * @return RepoAchiever Cluster health check status.
+ * @throws RemoteException if remote request fails.
+ */
+ Boolean retrieveHealthCheck() throws RemoteException;
+
+ /**
+ * Retrieves version of the allocated RepoAchiever Cluster instance allowing to confirm API
+ * compatability.
+ *
+ * @return RepoAchiever Cluster version.
+ * @throws RemoteException if remote request fails.
+ */
+ String retrieveVersion() throws RemoteException;
+
+ /**
+ * Retrieves amount of allocated workers.
+ *
+ * @return amount of allocated workers.
+ * @throws RemoteException if remote request fails.
+ */
+ Integer retrieveWorkerAmount() throws RemoteException;
+}
+
+// TODO: LOCATE ALL RMI RELATED CLASSES AT THE SAME PATH
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/service/communication/common/CommunicationProviderConfigurationHelper.java b/api-server/src/main/java/com/repoachiever/service/communication/common/CommunicationProviderConfigurationHelper.java
new file mode 100644
index 0000000..0a5462c
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/communication/common/CommunicationProviderConfigurationHelper.java
@@ -0,0 +1,15 @@
+package com.repoachiever.service.communication.common;
+
+/** Contains helpful tools used for communication provider configuration. */
+public class CommunicationProviderConfigurationHelper {
+ /**
+ * Composes binding URI declaration for RMI.
+ *
+ * @param registryPort given registry port.
+ * @param suffix given binding suffix.
+ * @return composed binding URI declaration for RMI.
+ */
+ public static String getBindName(Integer registryPort, String suffix) {
+ return String.format("//localhost:%d/%s", registryPort, suffix);
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/service/config/ConfigService.java b/api-server/src/main/java/com/repoachiever/service/config/ConfigService.java
new file mode 100644
index 0000000..eced9fe
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/config/ConfigService.java
@@ -0,0 +1,103 @@
+package com.repoachiever.service.config;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.repoachiever.entity.common.ConfigEntity;
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.exception.ConfigValidationException;
+import io.quarkus.runtime.Startup;
+import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.ValidatorFactory;
+
+import java.io.*;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import lombok.Getter;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Service used to perform RepoAchiever API Server configuration processing operation.
+ */
+@Startup
+@ApplicationScoped
+public class ConfigService {
+ private static final Logger logger = LogManager.getLogger(ConfigService.class);
+
+ @Inject
+ PropertiesEntity properties;
+
+ @Getter
+ private ConfigEntity config;
+
+ /**
+ * Reads configuration from the opened configuration file using mapping with a configuration entity.
+ */
+ @PostConstruct
+ private void configure() {
+ InputStream file = null;
+
+ try {
+ try {
+ file = new FileInputStream(
+ Paths.get(properties.getConfigDirectory(), properties.getConfigName()).toString());
+ } catch (FileNotFoundException e) {
+ logger.fatal(e.getMessage());
+ return;
+ }
+
+ ObjectMapper mapper =
+ new ObjectMapper(new YAMLFactory())
+ .configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, true)
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
+ .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ ObjectReader reader = mapper.reader().forType(new TypeReference() {
+ });
+
+ try {
+ List values = reader.readValues(file).readAll();
+ if (values.isEmpty()) {
+ return;
+ }
+
+ config = values.getFirst();
+ } catch (IOException e) {
+ logger.fatal(e.getMessage());
+ return;
+ }
+
+ try (ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory()) {
+ Validator validator = validatorFactory.getValidator();
+
+ Set> validationResult =
+ validator.validate(config);
+
+ if (!validationResult.isEmpty()) {
+ logger.fatal(new ConfigValidationException(
+ validationResult.stream()
+ .map(ConstraintViolation::getMessage)
+ .collect(Collectors.joining(", "))).getMessage());
+ }
+ }
+ } finally {
+ try {
+ file.close();
+ } catch (IOException e) {
+ logger.fatal(e.getMessage());
+ }
+ }
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/executor/CommandExecutorService.java b/api-server/src/main/java/com/repoachiever/service/executor/CommandExecutorService.java
new file mode 100644
index 0000000..7dbff48
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/executor/CommandExecutorService.java
@@ -0,0 +1,62 @@
+package com.repoachiever.service.executor;
+
+import com.repoachiever.dto.CommandExecutorOutputDto;
+import com.repoachiever.exception.CommandExecutorException;
+import jakarta.enterprise.context.ApplicationScoped;
+import process.SProcess;
+import process.SProcessExecutor;
+import process.exceptions.NonMatchingOSException;
+import process.exceptions.SProcessNotYetStartedException;
+
+import java.io.IOException;
+
+/**
+ * Represents command executor service used to perform commands execution.
+ */
+@ApplicationScoped
+public class CommandExecutorService {
+ private final SProcessExecutor processExecutor;
+
+ CommandExecutorService() {
+ this.processExecutor = SProcessExecutor.getCommandExecutor();
+ }
+
+ /**
+ * Executes given command.
+ *
+ * @param command standalone command
+ * @return output result, which consists of stdout and stderr.
+ * @throws CommandExecutorException when any execution step failed.
+ */
+ public CommandExecutorOutputDto executeCommand(SProcess command) throws CommandExecutorException {
+ try {
+ processExecutor.executeCommand(command);
+ } catch (IOException | NonMatchingOSException e) {
+ throw new CommandExecutorException(e.getMessage());
+ }
+
+ try {
+ command.waitForCompletion();
+ } catch (SProcessNotYetStartedException | InterruptedException e) {
+ throw new CommandExecutorException(e.getMessage());
+ }
+
+ String commandErrorOutput;
+
+ try {
+ commandErrorOutput = command.getErrorOutput();
+ } catch (SProcessNotYetStartedException e) {
+ throw new CommandExecutorException(e.getMessage());
+ }
+
+ String commandNormalOutput;
+
+ try {
+ commandNormalOutput = command.getNormalOutput();
+ } catch (SProcessNotYetStartedException e) {
+ throw new CommandExecutorException(e.getMessage());
+ }
+
+ return CommandExecutorOutputDto.of(commandNormalOutput, commandErrorOutput);
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/healthcheck/health/HealthCheckService.java b/api-server/src/main/java/com/repoachiever/service/healthcheck/health/HealthCheckService.java
new file mode 100644
index 0000000..b368b16
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/healthcheck/health/HealthCheckService.java
@@ -0,0 +1,20 @@
+package com.repoachiever.service.healthcheck.health;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import org.eclipse.microprofile.health.*;
+
+@Liveness
+@ApplicationScoped
+public class HealthCheckService implements HealthCheck {
+ @Override
+ public HealthCheckResponse call() {
+ HealthCheckResponseBuilder healthCheckResponse =
+ HealthCheckResponse.named("Terraform application availability");
+
+ healthCheckResponse.up();
+
+ // TODO: check if docker is installed
+
+ return healthCheckResponse.build();
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/healthcheck/readiness/ReadinessCheckService.java b/api-server/src/main/java/com/repoachiever/service/healthcheck/readiness/ReadinessCheckService.java
new file mode 100644
index 0000000..c399e23
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/healthcheck/readiness/ReadinessCheckService.java
@@ -0,0 +1,23 @@
+package com.repoachiever.service.healthcheck.readiness;
+
+import com.repoachiever.entity.common.PropertiesEntity;
+import org.eclipse.microprofile.health.HealthCheck;
+import org.eclipse.microprofile.health.HealthCheckResponse;
+import org.eclipse.microprofile.health.HealthCheckResponseBuilder;
+
+/** Checks if the Kafka service is available for the given workspace. */
+public class ReadinessCheckService implements HealthCheck {
+
+ public ReadinessCheckService(PropertiesEntity properties) {
+ }
+
+ @Override
+ public HealthCheckResponse call() {
+ HealthCheckResponseBuilder healthCheckResponse =
+ HealthCheckResponse.named("Connection availability");
+
+ healthCheckResponse.up();
+
+ return healthCheckResponse.build();
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/integration/communication/apiserver/ApiServerCommunicationConfigService.java b/api-server/src/main/java/com/repoachiever/service/integration/communication/apiserver/ApiServerCommunicationConfigService.java
new file mode 100644
index 0000000..94f5bb4
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/integration/communication/apiserver/ApiServerCommunicationConfigService.java
@@ -0,0 +1,60 @@
+package com.repoachiever.service.integration.communication.apiserver;
+
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.exception.CommunicationConfigurationFailureException;
+import com.repoachiever.resource.communication.ApiServerCommunicationResource;
+import com.repoachiever.service.config.ConfigService;
+import com.repoachiever.service.communication.common.CommunicationProviderConfigurationHelper;
+import io.quarkus.runtime.Startup;
+import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+
+/**
+ * Service used to perform RepoAchiever API Server communication provider configuration.
+ */
+@Startup(value = 160)
+@ApplicationScoped
+public class ApiServerCommunicationConfigService {
+ private static final Logger logger = LogManager.getLogger(ApiServerCommunicationConfigService.class);
+
+ @Inject
+ PropertiesEntity properties;
+
+ @Inject
+ ConfigService configService;
+
+ /**
+ * Performs setup of RepoAchiever API Server communication provider.
+ */
+ @PostConstruct
+ private void process() {
+ Registry registry;
+
+ try {
+ registry = LocateRegistry.getRegistry(
+ configService.getConfig().getCommunication().getPort());
+ } catch (RemoteException e) {
+ logger.fatal(new CommunicationConfigurationFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ Thread.ofPlatform().start(() -> {
+ try {
+ registry.rebind(
+ CommunicationProviderConfigurationHelper.getBindName(
+ configService.getConfig().getCommunication().getPort(),
+ properties.getCommunicationApiServerName()),
+ new ApiServerCommunicationResource(properties));
+ } catch (RemoteException e) {
+ logger.fatal(e.getMessage());
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/service/integration/communication/cluster/healthcheck/ClusterHealthCheckCommunicationService.java b/api-server/src/main/java/com/repoachiever/service/integration/communication/cluster/healthcheck/ClusterHealthCheckCommunicationService.java
new file mode 100644
index 0000000..02864e4
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/integration/communication/cluster/healthcheck/ClusterHealthCheckCommunicationService.java
@@ -0,0 +1,58 @@
+package com.repoachiever.service.integration.communication.cluster.healthcheck;
+
+import com.repoachiever.dto.ClusterAllocationDto;
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.exception.*;
+import com.repoachiever.resource.communication.ApiServerCommunicationResource;
+import com.repoachiever.service.cluster.ClusterService;
+import com.repoachiever.service.cluster.facade.ClusterFacade;
+import com.repoachiever.service.communication.common.CommunicationProviderConfigurationHelper;
+import com.repoachiever.service.config.ConfigService;
+import com.repoachiever.service.state.StateService;
+import io.quarkus.runtime.Startup;
+import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Service used to perform RepoAchiever Cluster communication health check operations.
+ */
+@ApplicationScoped
+public class ClusterHealthCheckCommunicationService {
+ private static final Logger logger = LogManager.getLogger(ClusterHealthCheckCommunicationService.class);
+
+ @Inject
+ PropertiesEntity properties;
+
+ @Inject
+ ClusterFacade clusterFacade;
+
+ private final static ScheduledExecutorService scheduledExecutorService =
+ Executors.newSingleThreadScheduledExecutor();
+
+ /**
+ * Performs RepoAchiever Cluster communication health check operations. If RepoAchiever Cluster is not responding,
+ * then it will be redeployed.
+ */
+ @PostConstruct
+ private void process() {
+ scheduledExecutorService.schedule(() -> {
+// try {
+// clusterFacade.reapplyUnhealthy();
+// } catch (ClusterUnhealthyReapplicationFailureException e) {
+// logger.fatal(e.getMessage());
+// }
+ }, properties.getCommunicationClusterHealthCheckFrequency(), TimeUnit.MILLISECONDS);
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/service/integration/communication/cluster/topology/ClusterTopologyCommunicationConfigService.java b/api-server/src/main/java/com/repoachiever/service/integration/communication/cluster/topology/ClusterTopologyCommunicationConfigService.java
new file mode 100644
index 0000000..fdcd129
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/integration/communication/cluster/topology/ClusterTopologyCommunicationConfigService.java
@@ -0,0 +1,65 @@
+package com.repoachiever.service.integration.communication.cluster.topology;
+
+import com.repoachiever.exception.*;
+import com.repoachiever.model.ContentApplication;
+import com.repoachiever.repository.facade.RepositoryFacade;
+import com.repoachiever.service.cluster.facade.ClusterFacade;
+import io.quarkus.runtime.Startup;
+import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.event.Observes;
+import jakarta.enterprise.event.Shutdown;
+import jakarta.inject.Inject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.List;
+
+/**
+ * Service used to perform topology configuration.
+ */
+@Startup(value = 170)
+@ApplicationScoped
+public class ClusterTopologyCommunicationConfigService {
+ private static final Logger logger = LogManager.getLogger(ClusterTopologyCommunicationConfigService.class);
+
+ @Inject
+ RepositoryFacade repositoryFacade;
+
+ @Inject
+ ClusterFacade clusterFacade;
+
+ /**
+ * Recreates previously created topology infrastructure if such existed before.
+ */
+ @PostConstruct
+ private void process() {
+ List applications;
+
+ try {
+ applications = repositoryFacade.retrieveContentApplication();
+ } catch (ContentApplicationRetrievalFailureException ignored) {
+ return;
+ }
+
+ for (ContentApplication application : applications) {
+ try {
+ clusterFacade.apply(application);
+ } catch (ClusterApplicationFailureException e) {
+ logger.fatal(e.getMessage());
+ return;
+ }
+ }
+ }
+
+ /**
+ * Gracefully stops all the created topology infrastructure.
+ */
+ public void close(@Observes Shutdown event) {
+ try {
+ clusterFacade.destroyAll();
+ } catch (ClusterFullDestructionFailureException e) {
+ logger.error(e.getMessage());
+ }
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/integration/communication/registry/RegistryCommunicationConfigService.java b/api-server/src/main/java/com/repoachiever/service/integration/communication/registry/RegistryCommunicationConfigService.java
new file mode 100644
index 0000000..8332d70
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/integration/communication/registry/RegistryCommunicationConfigService.java
@@ -0,0 +1,48 @@
+package com.repoachiever.service.integration.communication.registry;
+
+import com.repoachiever.exception.CommunicationConfigurationFailureException;
+import com.repoachiever.service.config.ConfigService;
+import io.quarkus.runtime.Startup;
+import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+
+/**
+ * Service used to perform initial communication infrastructure configuration.
+ */
+@Startup(value = 150)
+@ApplicationScoped
+public class RegistryCommunicationConfigService {
+ private static final Logger logger = LogManager.getLogger(RegistryCommunicationConfigService.class);
+
+ @Inject
+ ConfigService configService;
+
+ /**
+ * Performs initial communication infrastructure configuration.
+ */
+ @PostConstruct
+ private void process() {
+ Registry registry;
+
+ try {
+ registry = LocateRegistry.createRegistry(
+ configService.getConfig().getCommunication().getPort());
+ } catch (RemoteException e) {
+ logger.fatal(new CommunicationConfigurationFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ try {
+ registry.list();
+ } catch (RemoteException e) {
+ logger.fatal(new CommunicationConfigurationFailureException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/integration/diagnostics/DiagnosticsConfigService.java b/api-server/src/main/java/com/repoachiever/service/integration/diagnostics/DiagnosticsConfigService.java
new file mode 100644
index 0000000..ba9f04d
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/integration/diagnostics/DiagnosticsConfigService.java
@@ -0,0 +1,335 @@
+package com.repoachiever.service.integration.diagnostics;
+
+import com.repoachiever.dto.CommandExecutorOutputDto;
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.exception.*;
+import com.repoachiever.service.command.docker.availability.DockerAvailabilityCheckCommandService;
+import com.repoachiever.service.command.docker.inspect.remove.DockerInspectRemoveCommandService;
+import com.repoachiever.service.command.docker.network.create.DockerNetworkCreateCommandService;
+import com.repoachiever.service.command.docker.network.remove.DockerNetworkRemoveCommandService;
+import com.repoachiever.service.command.grafana.GrafanaDeployCommandService;
+import com.repoachiever.service.command.nodeexporter.NodeExporterDeployCommandService;
+import com.repoachiever.service.command.prometheus.PrometheusDeployCommandService;
+import com.repoachiever.service.config.ConfigService;
+import com.repoachiever.service.executor.CommandExecutorService;
+import com.repoachiever.service.telemetry.TelemetryService;
+import io.quarkus.runtime.Startup;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Objects;
+
+/**
+ * Service used to perform diagnostics infrastructure configuration operations.
+ */
+@Startup
+@Priority(value = 190)
+@ApplicationScoped
+public class DiagnosticsConfigService {
+ private static final Logger logger = LogManager.getLogger(DiagnosticsConfigService.class);
+
+ @Inject
+ PropertiesEntity properties;
+
+ @Inject
+ ConfigService configService;
+
+ @Inject
+ CommandExecutorService commandExecutorService;
+
+ @Inject
+ DockerAvailabilityCheckCommandService dockerAvailabilityCheckCommandService;
+
+ /**
+ * Creates Docker diagnostics network and deploys diagnostics infrastructure instances with pre-defined configurations.
+ */
+ @PostConstruct
+ private void process() {
+ if (configService.getConfig().getDiagnostics().getEnabled()) {
+ CommandExecutorOutputDto dockerAvailabilityCommandOutput;
+
+ try {
+ dockerAvailabilityCommandOutput =
+ commandExecutorService.executeCommand(dockerAvailabilityCheckCommandService);
+ } catch (CommandExecutorException e) {
+ logger.fatal(new DockerIsNotAvailableException(e.getMessage()).getMessage());
+ return;
+ }
+
+ String dockerAvailabilityCommandErrorOutput = dockerAvailabilityCommandOutput.getErrorOutput();
+
+ if ((Objects.nonNull(dockerAvailabilityCommandErrorOutput) &&
+ !dockerAvailabilityCommandErrorOutput.isEmpty()) ||
+ dockerAvailabilityCommandOutput.getNormalOutput().isEmpty()) {
+ logger.fatal(new DockerIsNotAvailableException(dockerAvailabilityCommandErrorOutput).getMessage());
+ return;
+ }
+
+ DockerInspectRemoveCommandService dockerInspectRemoveCommandService =
+ new DockerInspectRemoveCommandService(properties.getDiagnosticsPrometheusDockerName());
+
+ CommandExecutorOutputDto dockerInspectRemoveCommandOutput;
+
+ try {
+ dockerInspectRemoveCommandOutput =
+ commandExecutorService.executeCommand(dockerInspectRemoveCommandService);
+ } catch (CommandExecutorException e) {
+ logger.fatal(new DockerInspectRemovalFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ String dockerInspectRemoveCommandErrorOutput = dockerInspectRemoveCommandOutput.getErrorOutput();
+
+ if (Objects.nonNull(dockerInspectRemoveCommandErrorOutput) &&
+ !dockerInspectRemoveCommandErrorOutput.isEmpty()) {
+ logger.fatal(new DockerInspectRemovalFailureException(
+ dockerInspectRemoveCommandErrorOutput).getMessage());
+ }
+
+
+ dockerInspectRemoveCommandService =
+ new DockerInspectRemoveCommandService(properties.getDiagnosticsPrometheusNodeExporterDockerName());
+
+ try {
+ dockerInspectRemoveCommandOutput =
+ commandExecutorService.executeCommand(dockerInspectRemoveCommandService);
+ } catch (CommandExecutorException e) {
+ logger.fatal(new DockerInspectRemovalFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ dockerInspectRemoveCommandErrorOutput = dockerInspectRemoveCommandOutput.getErrorOutput();
+
+ if (Objects.nonNull(dockerInspectRemoveCommandErrorOutput) &&
+ !dockerInspectRemoveCommandErrorOutput.isEmpty()) {
+ logger.fatal(new DockerInspectRemovalFailureException(
+ dockerInspectRemoveCommandErrorOutput).getMessage());
+ }
+
+ dockerInspectRemoveCommandService =
+ new DockerInspectRemoveCommandService(properties.getDiagnosticsGrafanaDockerName());
+
+ try {
+ dockerInspectRemoveCommandOutput =
+ commandExecutorService.executeCommand(dockerInspectRemoveCommandService);
+ } catch (CommandExecutorException e) {
+ logger.fatal(new DockerInspectRemovalFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ dockerInspectRemoveCommandErrorOutput = dockerInspectRemoveCommandOutput.getErrorOutput();
+
+ if (Objects.nonNull(dockerInspectRemoveCommandErrorOutput) &&
+ !dockerInspectRemoveCommandErrorOutput.isEmpty()) {
+ logger.fatal(new DockerInspectRemovalFailureException(
+ dockerInspectRemoveCommandErrorOutput).getMessage());
+ }
+
+ DockerNetworkCreateCommandService dockerNetworkCreateCommandService =
+ new DockerNetworkCreateCommandService(properties.getDiagnosticsCommonDockerNetworkName());
+
+ CommandExecutorOutputDto dockerNetworkCreateCommandOutput;
+
+ try {
+ dockerNetworkCreateCommandOutput =
+ commandExecutorService.executeCommand(dockerNetworkCreateCommandService);
+ } catch (CommandExecutorException e) {
+ logger.fatal(new DockerNetworkCreateFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ String dockerNetworkCreateCommandErrorOutput = dockerNetworkCreateCommandOutput.getErrorOutput();
+
+ if (Objects.nonNull(dockerNetworkCreateCommandErrorOutput) &&
+ !dockerNetworkCreateCommandErrorOutput.isEmpty()) {
+ logger.fatal(new DockerNetworkCreateFailureException(
+ dockerNetworkCreateCommandErrorOutput).getMessage());
+ }
+
+ NodeExporterDeployCommandService nodeExporterDeployCommandService =
+ new NodeExporterDeployCommandService(
+ properties.getDiagnosticsPrometheusNodeExporterDockerName(),
+ properties.getDiagnosticsPrometheusNodeExporterDockerImage(),
+ configService.getConfig().getDiagnostics().getNodeExporter().getPort());
+
+ CommandExecutorOutputDto nodeExporterDeployCommandOutput;
+
+ try {
+ nodeExporterDeployCommandOutput =
+ commandExecutorService.executeCommand(nodeExporterDeployCommandService);
+ } catch (CommandExecutorException e) {
+ logger.fatal(new NodeExporterDeploymentFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ String nodeExporterDeployCommandErrorOutput = nodeExporterDeployCommandOutput.getErrorOutput();
+
+ if (Objects.nonNull(nodeExporterDeployCommandErrorOutput) &&
+ !nodeExporterDeployCommandErrorOutput.isEmpty()) {
+ logger.fatal(new NodeExporterDeploymentFailureException(
+ nodeExporterDeployCommandErrorOutput).getMessage());
+ }
+
+ PrometheusDeployCommandService prometheusDeployCommandService =
+ new PrometheusDeployCommandService(
+ properties.getDiagnosticsPrometheusDockerName(),
+ properties.getDiagnosticsPrometheusDockerImage(),
+ configService.getConfig().getDiagnostics().getPrometheus().getPort(),
+ properties.getDiagnosticsPrometheusConfigLocation(),
+ properties.getDiagnosticsPrometheusInternalLocation());
+
+ CommandExecutorOutputDto prometheusDeployCommandOutput;
+
+ try {
+ prometheusDeployCommandOutput =
+ commandExecutorService.executeCommand(prometheusDeployCommandService);
+ } catch (CommandExecutorException e) {
+ logger.fatal(new PrometheusDeploymentFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ String prometheusDeployCommandErrorOutput = prometheusDeployCommandOutput.getErrorOutput();
+
+ if (Objects.nonNull(prometheusDeployCommandErrorOutput) &&
+ !prometheusDeployCommandErrorOutput.isEmpty()) {
+ logger.fatal(new PrometheusDeploymentFailureException(
+ prometheusDeployCommandErrorOutput).getMessage());
+ }
+
+ GrafanaDeployCommandService grafanaDeployCommandService =
+ new GrafanaDeployCommandService(
+ properties.getDiagnosticsGrafanaDockerName(),
+ properties.getDiagnosticsGrafanaDockerImage(),
+ configService.getConfig().getDiagnostics().getGrafana().getPort(),
+ properties.getDiagnosticsGrafanaConfigLocation(),
+ properties.getDiagnosticsGrafanaInternalLocation());
+
+ CommandExecutorOutputDto grafanaDeployCommandOutput;
+
+ try {
+ grafanaDeployCommandOutput =
+ commandExecutorService.executeCommand(grafanaDeployCommandService);
+ } catch (CommandExecutorException e) {
+ logger.fatal(new PrometheusDeploymentFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ String grafanaDeployCommandErrorOutput = grafanaDeployCommandOutput.getErrorOutput();
+
+ if (Objects.nonNull(grafanaDeployCommandErrorOutput) &&
+ !grafanaDeployCommandErrorOutput.isEmpty()) {
+ logger.fatal(new PrometheusDeploymentFailureException(
+ grafanaDeployCommandErrorOutput).getMessage());
+ }
+ }
+ }
+
+ /**
+ * Removes created Docker networks and stops started diagnostics containers.
+ */
+ @PreDestroy
+ private void close() {
+ if (configService.getConfig().getDiagnostics().getEnabled()) {
+ CommandExecutorOutputDto dockerAvailabilityCommandOutput;
+
+ try {
+ dockerAvailabilityCommandOutput =
+ commandExecutorService.executeCommand(dockerAvailabilityCheckCommandService);
+ } catch (CommandExecutorException e) {
+ return;
+ }
+
+ String dockerAvailabilityCommandErrorOutput = dockerAvailabilityCommandOutput.getErrorOutput();
+
+ if ((Objects.nonNull(dockerAvailabilityCommandErrorOutput) &&
+ !dockerAvailabilityCommandErrorOutput.isEmpty()) ||
+ dockerAvailabilityCommandOutput.getNormalOutput().isEmpty()) {
+ return;
+ }
+
+ DockerNetworkRemoveCommandService dockerNetworkRemoveCommandService =
+ new DockerNetworkRemoveCommandService(properties.getDiagnosticsCommonDockerNetworkName());
+
+ CommandExecutorOutputDto dockerNetworkRemoveCommandOutput;
+
+ try {
+ dockerNetworkRemoveCommandOutput =
+ commandExecutorService.executeCommand(dockerNetworkRemoveCommandService);
+ } catch (CommandExecutorException e) {
+ logger.fatal(new DockerNetworkRemoveFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ String dockerNetworkRemoveCommandErrorOutput = dockerNetworkRemoveCommandOutput.getErrorOutput();
+
+ if (Objects.nonNull(dockerNetworkRemoveCommandErrorOutput) &&
+ !dockerNetworkRemoveCommandErrorOutput.isEmpty()) {
+ logger.fatal(new DockerNetworkRemoveFailureException(dockerNetworkRemoveCommandErrorOutput).getMessage());
+ }
+
+ DockerInspectRemoveCommandService dockerInspectRemoveCommandService =
+ new DockerInspectRemoveCommandService(properties.getDiagnosticsPrometheusDockerName());
+
+ CommandExecutorOutputDto dockerInspectRemoveCommandOutput;
+
+ try {
+ dockerInspectRemoveCommandOutput =
+ commandExecutorService.executeCommand(dockerInspectRemoveCommandService);
+ } catch (CommandExecutorException e) {
+ logger.fatal(new DockerInspectRemovalFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ String dockerInspectRemoveCommandErrorOutput = dockerInspectRemoveCommandOutput.getErrorOutput();
+
+ if (Objects.nonNull(dockerInspectRemoveCommandErrorOutput) &&
+ !dockerInspectRemoveCommandErrorOutput.isEmpty()) {
+ logger.fatal(new DockerInspectRemovalFailureException(
+ dockerInspectRemoveCommandErrorOutput).getMessage());
+ }
+
+ dockerInspectRemoveCommandService =
+ new DockerInspectRemoveCommandService(properties.getDiagnosticsPrometheusNodeExporterDockerName());
+
+ try {
+ dockerInspectRemoveCommandOutput =
+ commandExecutorService.executeCommand(dockerInspectRemoveCommandService);
+ } catch (CommandExecutorException e) {
+ logger.fatal(new DockerInspectRemovalFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ dockerInspectRemoveCommandErrorOutput = dockerInspectRemoveCommandOutput.getErrorOutput();
+
+ if (Objects.nonNull(dockerInspectRemoveCommandErrorOutput) &&
+ !dockerInspectRemoveCommandErrorOutput.isEmpty()) {
+ logger.fatal(new DockerInspectRemovalFailureException(
+ dockerInspectRemoveCommandErrorOutput).getMessage());
+ }
+
+ dockerInspectRemoveCommandService =
+ new DockerInspectRemoveCommandService(properties.getDiagnosticsGrafanaDockerName());
+
+ try {
+ dockerInspectRemoveCommandOutput =
+ commandExecutorService.executeCommand(dockerInspectRemoveCommandService);
+ } catch (CommandExecutorException e) {
+ logger.fatal(new DockerInspectRemovalFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ dockerInspectRemoveCommandErrorOutput = dockerInspectRemoveCommandOutput.getErrorOutput();
+
+ if (Objects.nonNull(dockerInspectRemoveCommandErrorOutput) &&
+ !dockerInspectRemoveCommandErrorOutput.isEmpty()) {
+ logger.fatal(new DockerInspectRemovalFailureException(
+ dockerInspectRemoveCommandErrorOutput).getMessage());
+ }
+ }
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/integration/diagnostics/telemetry/TelemetryConfigService.java b/api-server/src/main/java/com/repoachiever/service/integration/diagnostics/telemetry/TelemetryConfigService.java
new file mode 100644
index 0000000..a4b0a48
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/integration/diagnostics/telemetry/TelemetryConfigService.java
@@ -0,0 +1,168 @@
+package com.repoachiever.service.integration.diagnostics.telemetry;
+
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.exception.TelemetryOperationFailureException;
+import com.repoachiever.service.config.ConfigService;
+import com.repoachiever.service.telemetry.binding.TelemetryBinding;
+import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
+import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
+import io.micrometer.core.instrument.binder.system.DiskSpaceMetrics;
+import io.micrometer.core.instrument.binder.system.ProcessorMetrics;
+import io.micrometer.core.instrument.binder.system.UptimeMetrics;
+import io.micrometer.prometheus.PrometheusConfig;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import io.quarkus.runtime.Startup;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.*;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Service used to perform diagnostics telemetry configuration operations.
+ */
+@Startup
+@Priority(value = 200)
+@ApplicationScoped
+public class TelemetryConfigService {
+ private static final Logger logger = LogManager.getLogger(TelemetryConfigService.class);
+
+ @Inject
+ PropertiesEntity properties;
+
+ @Inject
+ ConfigService configService;
+
+ @Inject
+ TelemetryBinding telemetryBinding;
+
+ private ServerSocket connector;
+
+ private final ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
+
+ /**
+ * Performs telemetry metrics server configuration, registering bindings provided by external provider.
+ */
+ @PostConstruct
+ private void configure() {
+ try {
+ connector = new ServerSocket(
+ configService.getConfig().getDiagnostics().getMetrics().getPort());
+ } catch (IOException e) {
+ logger.fatal(new TelemetryOperationFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ PrometheusMeterRegistry prometheusRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
+
+ new JvmThreadMetrics().bindTo(prometheusRegistry);
+ new JvmMemoryMetrics().bindTo(prometheusRegistry);
+ new DiskSpaceMetrics(new File(properties.getWorkspaceDirectory())).bindTo(prometheusRegistry);
+ new ProcessorMetrics().bindTo(prometheusRegistry);
+ new UptimeMetrics().bindTo(prometheusRegistry);
+
+ telemetryBinding.bindTo(prometheusRegistry);
+
+ Thread.ofPlatform().start(() -> {
+ while (!connector.isClosed()) {
+ Socket connection;
+
+ try {
+ connection = connector.accept();
+ } catch (IOException ignored) {
+ continue;
+ }
+
+ try {
+ connection.setSoTimeout(properties.getDiagnosticsMetricsConnectionTimeout());
+ } catch (SocketException e) {
+ logger.error(new TelemetryOperationFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ executorService.execute(() -> {
+ OutputStreamWriter outputStreamWriter;
+
+ try {
+ outputStreamWriter =
+ new OutputStreamWriter(connection.getOutputStream(), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ logger.error(new TelemetryOperationFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ BufferedReader inputStreamReader;
+ try {
+ inputStreamReader = new BufferedReader(
+ new InputStreamReader(connection.getInputStream()));
+ } catch (IOException e) {
+ logger.error(new TelemetryOperationFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ try {
+ outputStreamWriter.write(
+ String.format(
+ "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n%s",
+ prometheusRegistry.scrape()));
+ } catch (IOException ignored) {
+ return;
+ }
+
+ try {
+ outputStreamWriter.flush();
+ } catch (IOException e) {
+ logger.error(new TelemetryOperationFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ try {
+ inputStreamReader.readLine();
+ } catch (IOException e) {
+ logger.error(new TelemetryOperationFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ try {
+ inputStreamReader.close();
+ } catch (IOException e) {
+ logger.error(new TelemetryOperationFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ try {
+ outputStreamWriter.close();
+ } catch (IOException e) {
+ logger.error(new TelemetryOperationFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ try {
+ connection.close();
+ } catch (IOException e) {
+ logger.error(new TelemetryOperationFailureException(e.getMessage()).getMessage());
+ }
+ });
+ }
+ });
+ }
+
+ @PreDestroy
+ private void close() {
+ try {
+ connector.close();
+ } catch (IOException e) {
+ logger.error(new TelemetryOperationFailureException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/integration/diagnostics/template/TemplateConfigService.java b/api-server/src/main/java/com/repoachiever/service/integration/diagnostics/template/TemplateConfigService.java
new file mode 100644
index 0000000..b3fd358
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/integration/diagnostics/template/TemplateConfigService.java
@@ -0,0 +1,214 @@
+package com.repoachiever.service.integration.diagnostics.template;
+
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.exception.DiagnosticsTemplateProcessingFailureException;
+import com.repoachiever.service.config.ConfigService;
+import freemarker.cache.FileTemplateLoader;
+import freemarker.template.*;
+import io.quarkus.runtime.Startup;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.ApplicationScoped;
+
+import jakarta.inject.Inject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import static freemarker.template.Configuration.VERSION_2_3_32;
+
+/**
+ * Service used to perform diagnostics template configuration operations.
+ */
+@Startup
+@Priority(value = 180)
+@ApplicationScoped
+public class TemplateConfigService {
+ private static final Logger logger = LogManager.getLogger(TemplateConfigService.class);
+
+ @Inject
+ PropertiesEntity properties;
+
+ @Inject
+ ConfigService configService;
+
+ /**
+ * Performs diagnostics infrastructure configuration templates parsing operations.
+ */
+ @PostConstruct
+ private void process() {
+ if (configService.getConfig().getDiagnostics().getEnabled()) {
+ Configuration cfg = new Configuration(VERSION_2_3_32);
+ try {
+ cfg.setTemplateLoader(new FileTemplateLoader(new File(properties.getDiagnosticsPrometheusConfigLocation())));
+ } catch (IOException e) {
+ logger.fatal(new DiagnosticsTemplateProcessingFailureException(e.getMessage()).getMessage());
+ return;
+ }
+ cfg.setDefaultEncoding("UTF-8");
+
+ Template template;
+
+ try {
+ template = cfg.getTemplate(properties.getDiagnosticsPrometheusConfigTemplate());
+ } catch (IOException e) {
+ logger.fatal(new DiagnosticsTemplateProcessingFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ Writer fileWriter;
+
+ try {
+ fileWriter = new FileWriter(
+ Paths.get(
+ properties.getDiagnosticsPrometheusConfigLocation(),
+ properties.getDiagnosticsPrometheusConfigOutput()).
+ toFile());
+ } catch (IOException e) {
+ logger.fatal(new DiagnosticsTemplateProcessingFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ Map input = new HashMap<>() {
+ {
+ put("metrics", new HashMap() {
+ {
+ put("host", "host.docker.internal");
+ put("port", String.valueOf(configService.getConfig().getDiagnostics().getMetrics().getPort()));
+ }
+ });
+ put("nodeexporter", new HashMap() {
+ {
+ put("host", properties.getDiagnosticsPrometheusNodeExporterDockerName());
+ put("port", String.valueOf(configService.getConfig().getDiagnostics().getNodeExporter().getPort()));
+ }
+ });
+ }
+ };
+
+ try {
+ template.process(input, fileWriter);
+ } catch (TemplateException | IOException e) {
+ logger.fatal(new DiagnosticsTemplateProcessingFailureException(e.getMessage()).getMessage());
+ } finally {
+ try {
+ fileWriter.close();
+ } catch (IOException e) {
+ logger.fatal(new DiagnosticsTemplateProcessingFailureException(e.getMessage()).getMessage());
+ }
+ }
+
+ try {
+ cfg.setTemplateLoader(new FileTemplateLoader(
+ new File(properties.getDiagnosticsGrafanaDatasourcesLocation())));
+ } catch (IOException e) {
+ logger.fatal(new DiagnosticsTemplateProcessingFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ try {
+ template = cfg.getTemplate(properties.getDiagnosticsGrafanaDatasourcesTemplate());
+ } catch (IOException e) {
+ logger.fatal(new DiagnosticsTemplateProcessingFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ try {
+ fileWriter = new FileWriter(
+ Paths.get(
+ properties.getDiagnosticsGrafanaDatasourcesLocation(),
+ properties.getDiagnosticsGrafanaDatasourcesOutput()).
+ toFile());
+ } catch (IOException e) {
+ logger.fatal(new DiagnosticsTemplateProcessingFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ input = new HashMap<>() {
+ {
+ put("prometheus", new HashMap() {
+ {
+ put("host", properties.getDiagnosticsPrometheusDockerName());
+ put("port", String.valueOf(configService.getConfig().getDiagnostics().getPrometheus().getPort()));
+ }
+ });
+ }
+ };
+
+ try {
+ template.process(input, fileWriter);
+ } catch (TemplateException | IOException e) {
+ logger.fatal(new DiagnosticsTemplateProcessingFailureException(e.getMessage()).getMessage());
+ } finally {
+ try {
+ fileWriter.close();
+ } catch (IOException e) {
+ logger.fatal(new DiagnosticsTemplateProcessingFailureException(e.getMessage()).getMessage());
+ }
+ }
+
+ try {
+ cfg.setTemplateLoader(new FileTemplateLoader(
+ new File(properties.getDiagnosticsGrafanaDashboardsLocation())));
+ } catch (IOException e) {
+ logger.fatal(new DiagnosticsTemplateProcessingFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ try {
+ template = cfg.getTemplate(properties.getDiagnosticsGrafanaDashboardsDiagnosticsTemplate());
+ } catch (IOException e) {
+ logger.fatal(new DiagnosticsTemplateProcessingFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ try {
+ fileWriter = new FileWriter(
+ Paths.get(
+ properties.getDiagnosticsGrafanaDashboardsLocation(),
+ properties.getDiagnosticsGrafanaDashboardsDiagnosticsOutput()).
+ toFile());
+ } catch (IOException e) {
+ logger.fatal(new DiagnosticsTemplateProcessingFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ input = new HashMap<>() {
+ {
+ put("info", new HashMap() {
+ {
+ put("version", properties.getGitCommitId());
+ }
+ });
+ put("nodeexporter", new HashMap() {
+ {
+ put("port", String.valueOf(
+ configService.getConfig().getDiagnostics().getNodeExporter().getPort()));
+ }
+ });
+ }
+ };
+
+ try {
+ template.process(input, fileWriter);
+ } catch (TemplateException | IOException e) {
+ logger.fatal(new DiagnosticsTemplateProcessingFailureException(e.getMessage()).getMessage());
+ } finally {
+ try {
+ fileWriter.close();
+ } catch (IOException e) {
+ logger.fatal(new DiagnosticsTemplateProcessingFailureException(e.getMessage()).getMessage());
+ }
+ }
+ }
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/integration/http/HttpServerConfigService.java b/api-server/src/main/java/com/repoachiever/service/integration/http/HttpServerConfigService.java
new file mode 100644
index 0000000..341f4f2
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/integration/http/HttpServerConfigService.java
@@ -0,0 +1,28 @@
+package com.repoachiever.service.integration.http;
+
+import com.repoachiever.entity.common.ConfigEntity;
+import com.repoachiever.service.config.ConfigService;
+import io.quarkus.vertx.http.HttpServerOptionsCustomizer;
+import io.vertx.core.http.HttpServerOptions;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+/**
+ * Provides http server configuration used as a source of properties for all defined resources.
+ */
+@ApplicationScoped
+public class HttpServerConfigService implements HttpServerOptionsCustomizer {
+ @Inject
+ ConfigService configService;
+
+ /**
+ * @see HttpServerOptionsCustomizer
+ */
+ @Override
+ public void customizeHttpServer(HttpServerOptions options) {
+ ConfigEntity.Connection connection = configService.getConfig().getConnection();
+
+ options.setPort(connection.getPort());
+ }
+}
+
diff --git a/api-server/src/main/java/com/repoachiever/service/integration/properties/general/GeneralPropertiesConfigService.java b/api-server/src/main/java/com/repoachiever/service/integration/properties/general/GeneralPropertiesConfigService.java
new file mode 100644
index 0000000..782db0e
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/integration/properties/general/GeneralPropertiesConfigService.java
@@ -0,0 +1,17 @@
+package com.repoachiever.service.integration.properties.general;
+
+import io.quarkus.runtime.Startup;
+import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.ApplicationScoped;
+
+/**
+ * Service used to perform general properties configuration operations.
+ */
+@Startup
+@ApplicationScoped
+public class GeneralPropertiesConfigService {
+ @PostConstruct
+ private void process() {
+
+ }
+}
\ No newline at end of file
diff --git a/api-server/src/main/java/com/repoachiever/service/integration/properties/git/GitPropertiesConfigService.java b/api-server/src/main/java/com/repoachiever/service/integration/properties/git/GitPropertiesConfigService.java
new file mode 100644
index 0000000..88653cc
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/integration/properties/git/GitPropertiesConfigService.java
@@ -0,0 +1,75 @@
+package com.repoachiever.service.integration.properties.git;
+
+import io.quarkus.runtime.annotations.StaticInitSafe;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.eclipse.microprofile.config.spi.ConfigSource;
+
+/** Provides access to external config source used as a source of Git info. */
+@StaticInitSafe
+public class GitPropertiesConfigService implements ConfigSource {
+ private static final Logger logger = LogManager.getLogger(GitPropertiesConfigService.class);
+
+ private static final String GIT_CONFIG_PROPERTIES_FILE = "git.properties";
+
+ private final Properties config;
+
+ public GitPropertiesConfigService() {
+ this.config = new Properties();
+
+ ClassLoader classLoader = getClass().getClassLoader();
+ InputStream gitBuildPropertiesStream =
+ classLoader.getResourceAsStream(GIT_CONFIG_PROPERTIES_FILE);
+ try {
+ config.load(gitBuildPropertiesStream);
+ } catch (IOException e) {
+ logger.fatal(e.getMessage());
+ }
+ }
+
+ /**
+ * @see ConfigSource
+ */
+ @Override
+ public Map getProperties() {
+ return ConfigSource.super.getProperties();
+ }
+
+ /**
+ * @see ConfigSource
+ */
+ @Override
+ public Set getPropertyNames() {
+ return config.keySet().stream().map(element -> (String) element).collect(Collectors.toSet());
+ }
+
+ /**
+ * @see ConfigSource
+ */
+ @Override
+ public int getOrdinal() {
+ return ConfigSource.super.getOrdinal();
+ }
+
+ /**
+ * @see ConfigSource
+ */
+ @Override
+ public String getValue(String key) {
+ return (String) config.get(key);
+ }
+
+ /**
+ * @see ConfigSource
+ */
+ @Override
+ public String getName() {
+ return GitPropertiesConfigService.class.getSimpleName();
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/integration/state/StateConfigService.java b/api-server/src/main/java/com/repoachiever/service/integration/state/StateConfigService.java
new file mode 100644
index 0000000..6a35e33
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/integration/state/StateConfigService.java
@@ -0,0 +1,66 @@
+package com.repoachiever.service.integration.state;
+
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.exception.ApiServerInstanceIsAlreadyRunningException;
+import com.repoachiever.service.state.StateService;
+import io.quarkus.runtime.Startup;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Service used to perform application critical state configuration.
+ */
+@Startup
+@Priority(value = 140)
+@ApplicationScoped
+public class StateConfigService {
+ private static final Logger logger = LogManager.getLogger(StateConfigService.class);
+
+ @Inject
+ PropertiesEntity properties;
+
+ /**
+ * Performs application state initialization operations.
+ */
+ @PostConstruct
+ private void process() {
+ Path running = Paths.get(properties.getStateLocation(), properties.getStateRunningName());
+
+ if (Files.exists(running)) {
+ logger.fatal(new ApiServerInstanceIsAlreadyRunningException().getMessage());
+ return;
+ }
+
+ try {
+ Files.createFile(running);
+ } catch (IOException e) {
+ logger.fatal(e.getMessage());
+ }
+
+ StateService.setStarted(true);
+ }
+
+ /**
+ * Performs graceful application state cleanup after execution is finished.
+ */
+ @PreDestroy
+ private void close() {
+ if (StateService.getStarted()) {
+ try {
+ Files.delete(Paths.get(properties.getStateLocation(), properties.getStateRunningName()));
+ } catch (IOException e) {
+ logger.error(e.getMessage());
+ }
+ }
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/state/StateService.java b/api-server/src/main/java/com/repoachiever/service/state/StateService.java
new file mode 100644
index 0000000..95d1579
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/state/StateService.java
@@ -0,0 +1,80 @@
+package com.repoachiever.service.state;
+
+import com.repoachiever.dto.ClusterAllocationDto;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+
+/**
+ * Service used to operate as a collection of application state properties.
+ */
+public class StateService {
+ /**
+ * Represents if RepoAchiever API Server application has been started.
+ */
+ @Getter
+ @Setter
+ private static Boolean started = false;
+
+ /**
+ * Represents RepoAchiever Cluster topology state guard.
+ */
+ @Getter
+ private final static ReentrantLock topologyStateGuard = new ReentrantLock();
+
+
+ /**
+ * Represents a set of all available RepoAchiever Cluster allocations.
+ */
+ @Getter
+ private final static List clusterAllocations = new ArrayList<>();
+
+ /**
+ * Retrieves RepoAchiever allocations with the given workspace unit key.
+ *
+ * @param workspaceUnitKey given workspace unit key.
+ * @return filtered RepoAchiever allocations according to the given workspace unit key.
+ */
+ public static List getClusterAllocationsByWorkspaceUnitKey(String workspaceUnitKey) {
+ return clusterAllocations.
+ stream().
+ filter(element -> Objects.equals(element.getWorkspaceUnitKey(), workspaceUnitKey)).
+ collect(Collectors.toList());
+ }
+
+ /**
+ * Adds new RepoAchiever Cluster allocations.
+ *
+ * @param allocations given RepoAchiever Cluster allocations.
+ */
+ public static void addClusterAllocations(List allocations) {
+ clusterAllocations.addAll(allocations);
+ }
+
+ /**
+ * Checks if RepoAchiever Cluster allocations with the given name exists.
+ *
+ * @param name given RepoAchiever Cluster allocation.
+ * @return result of the check.
+ */
+ public static Boolean isClusterAllocationPresentByName(String name) {
+ return clusterAllocations
+ .stream()
+ .anyMatch(element -> Objects.equals(element.getName(), name));
+ }
+
+ /**
+ * Removes RepoAchiever Cluster allocations with the given names.
+ *
+ * @param names given RepoAchiever Cluster allocation names.
+ */
+ public static void removeClusterAllocationByNames(List names) {
+ names.forEach(
+ element1 -> clusterAllocations.removeIf(element2 -> Objects.equals(element2.getName(), element1)));
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/telemetry/TelemetryService.java b/api-server/src/main/java/com/repoachiever/service/telemetry/TelemetryService.java
new file mode 100644
index 0000000..35706da
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/telemetry/TelemetryService.java
@@ -0,0 +1,57 @@
+package com.repoachiever.service.telemetry;
+
+import com.repoachiever.service.telemetry.binding.TelemetryBinding;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+/**
+ * Provides access to gather information and expose it to telemetry representation tool.
+ */
+@ApplicationScoped
+public class TelemetryService {
+ @Inject
+ TelemetryBinding telemetryBinding;
+
+ /**
+ * Increases allocated workers amount counter.
+ *
+ * @param value given increment value.
+ */
+ public void increaseWorkersAmountBy(Integer value) {
+ telemetryBinding.getWorkerAmount().set(telemetryBinding.getWorkerAmount().get() + value);
+ }
+
+ /**
+ * Decreases allocated workers amount counter.
+ *
+ * @param value given increment value.
+ */
+ public void decreaseWorkersAmountBy(Integer value) {
+ telemetryBinding.getWorkerAmount().set(telemetryBinding.getWorkerAmount().get() - value);
+ }
+
+ /**
+ * Increases allocated clusters amount counter.
+ *
+ * @param value given increment value.
+ */
+ public void increaseClustersAmountBy(Integer value) {
+ telemetryBinding.getWorkerAmount().set(telemetryBinding.getWorkerAmount().get() + value);
+ }
+
+ /**
+ * Decreases allocated clusters amount counter.
+ *
+ * @param value given increment value.
+ */
+ public void decreaseClustersAmountBy(Integer value) {
+ telemetryBinding.getWorkerAmount().set(telemetryBinding.getWorkerAmount().get() - value);
+ }
+
+ /**
+ * Records latest cluster allocation health check.
+ */
+ public void recordClusterHealthCheck() {
+
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/telemetry/binding/TelemetryBinding.java b/api-server/src/main/java/com/repoachiever/service/telemetry/binding/TelemetryBinding.java
new file mode 100644
index 0000000..239e829
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/telemetry/binding/TelemetryBinding.java
@@ -0,0 +1,44 @@
+package com.repoachiever.service.telemetry.binding;
+
+import io.micrometer.core.instrument.Timer;
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.binder.MeterBinder;
+import jakarta.enterprise.context.ApplicationScoped;
+import lombok.Getter;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Service used to create custom telemetry bindings used to distribute application metrics.
+ */
+@ApplicationScoped
+public class TelemetryBinding implements MeterBinder {
+ @Getter
+ private final AtomicInteger workerAmount = new AtomicInteger();
+
+ @Getter
+ private final AtomicInteger clusterAmount = new AtomicInteger();
+
+ @Getter
+ private Timer clusterHealthCheck;
+
+ /**
+ * @see MeterBinder
+ */
+ @Override
+ public void bindTo(@NotNull MeterRegistry meterRegistry) {
+ Gauge.builder("general.worker_amount", workerAmount, AtomicInteger::get)
+ .description("Represents amount of allocated workers")
+ .register(meterRegistry);
+
+ Gauge.builder("general.cluster_amount", clusterAmount, AtomicInteger::get)
+ .description("Represents amount of allocated clusters")
+ .register(meterRegistry);
+
+ clusterHealthCheck = Timer.builder("general.cluster_health_check")
+ .description("Represents all the performed health check requests for allocated clusters")
+ .register(meterRegistry);
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/vendor/VendorFacade.java b/api-server/src/main/java/com/repoachiever/service/vendor/VendorFacade.java
new file mode 100644
index 0000000..5c2c4b5
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/vendor/VendorFacade.java
@@ -0,0 +1,28 @@
+package com.repoachiever.service.vendor;
+
+import com.repoachiever.model.CredentialsFieldsExternal;
+import com.repoachiever.model.Provider;
+import com.repoachiever.service.vendor.git.github.GitGitHubVendorService;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+/** Provides high-level access to VCS vendor operations. */
+@ApplicationScoped
+public class VendorFacade {
+ @Inject
+ GitGitHubVendorService gitGitHubVendorService;
+
+ /**
+ * Checks if the given external credentials are valid, according to the given provider name.
+ *
+ * @param provider given external provider name.
+ * @param credentialsFieldExternal given external credentials.
+ * @return result of the check.
+ */
+ public Boolean isExternalCredentialsValid(Provider provider, CredentialsFieldsExternal credentialsFieldExternal) {
+ return switch (provider) {
+ case LOCAL -> true;
+ case GITHUB -> gitGitHubVendorService.isTokenValid(credentialsFieldExternal.getToken());
+ };
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/vendor/common/VendorConfigurationHelper.java b/api-server/src/main/java/com/repoachiever/service/vendor/common/VendorConfigurationHelper.java
new file mode 100644
index 0000000..eab22c9
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/vendor/common/VendorConfigurationHelper.java
@@ -0,0 +1,16 @@
+package com.repoachiever.service.vendor.common;
+
+/**
+ * Contains helpful tools used for vendor configuration.
+ */
+public class VendorConfigurationHelper {
+ /**
+ * Converts given raw token value to a wrapped format.
+ *
+ * @param token given raw token value.
+ * @return wrapped token.
+ */
+ public static String getWrappedToken(String token) {
+ return String.format("Bearer %s", token);
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/vendor/git/github/GitGitHubVendorService.java b/api-server/src/main/java/com/repoachiever/service/vendor/git/github/GitGitHubVendorService.java
new file mode 100644
index 0000000..dbe5dd7
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/vendor/git/github/GitGitHubVendorService.java
@@ -0,0 +1,33 @@
+package com.repoachiever.service.vendor.git.github;
+
+import com.repoachiever.service.client.github.IGitHubClientService;
+import com.repoachiever.service.vendor.common.VendorConfigurationHelper;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.Response;
+import org.apache.http.HttpStatus;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+
+@ApplicationScoped
+public class GitGitHubVendorService {
+ @Inject
+ @RestClient
+ IGitHubClientService gitHubClientService;
+
+ /**
+ * Checks if the given token is valid.
+ *
+ * @return result of the check.
+ */
+ public boolean isTokenValid(String token) {
+ try {
+ Response response = gitHubClientService
+ .getOctocat(VendorConfigurationHelper.getWrappedToken(token));
+
+ return response.getStatus() == HttpStatus.SC_OK;
+ } catch (WebApplicationException e) {
+ return false;
+ }
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/workspace/WorkspaceService.java b/api-server/src/main/java/com/repoachiever/service/workspace/WorkspaceService.java
new file mode 100644
index 0000000..f2aee94
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/workspace/WorkspaceService.java
@@ -0,0 +1,362 @@
+package com.repoachiever.service.workspace;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.repoachiever.entity.common.MetadataFileEntity;
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.exception.*;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.xml.bind.DatatypeConverter;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.MessageDigest;
+
+import lombok.SneakyThrows;
+import org.apache.commons.io.FileUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.util.FileSystemUtils;
+
+/**
+ * Represents local content workspace for different users.
+ */
+@ApplicationScoped
+public class WorkspaceService {
+ private static final Logger logger = LogManager.getLogger(WorkspaceService.class);
+
+ @Inject
+ PropertiesEntity properties;
+
+ /**
+ * Creates unit key from the given segments.
+ *
+ * @param segments given segments to be used for unit key creation.
+ * @return created unit key from the given segments.
+ */
+ @SneakyThrows
+ public String createUnitKey(String... segments) {
+ MessageDigest md = MessageDigest.getInstance("SHA3-256");
+ return DatatypeConverter.printHexBinary(md.digest(String.join(".", segments).getBytes()));
+ }
+
+ /**
+ * Creates content directory in the given workspace unit directory
+ *
+ * @param workspaceUnitDirectory given workspace unit directory
+ * @throws WorkspaceContentDirectoryCreationFailureException if workspace content directory creation operation failed.
+ */
+ public void createContentDirectory(String workspaceUnitDirectory) throws
+ WorkspaceContentDirectoryCreationFailureException {
+ Path unitDirectoryPath = Path.of(workspaceUnitDirectory, properties.getWorkspaceContentDirectory());
+
+ if (Files.notExists(unitDirectoryPath)) {
+ try {
+ Files.createDirectory(unitDirectoryPath);
+ } catch (IOException e) {
+ throw new WorkspaceContentDirectoryCreationFailureException(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Creates workspace unit with the help of the given key.
+ *
+ * @param key given workspace unit key.
+ * @throws WorkspaceUnitDirectoryCreationFailureException if workspace unit directory creation operation failed.
+ */
+ public void createUnitDirectory(String key) throws
+ WorkspaceUnitDirectoryCreationFailureException {
+ Path unitDirectoryPath = Path.of(properties.getWorkspaceDirectory(), key);
+
+ if (Files.notExists(unitDirectoryPath)) {
+ try {
+ Files.createDirectory(unitDirectoryPath);
+ } catch (IOException e) {
+ throw new WorkspaceUnitDirectoryCreationFailureException(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Removes workspace unit with the help of the given key.
+ *
+ * @param key given workspace unit key.
+ * @throws WorkspaceUnitDirectoryRemovalFailureException if IO operation failed.
+ */
+ public void removeUnitDirectory(String key) throws WorkspaceUnitDirectoryRemovalFailureException {
+ try {
+ FileSystemUtils.deleteRecursively(Path.of(properties.getWorkspaceDirectory(), key));
+ } catch (IOException e) {
+ throw new WorkspaceUnitDirectoryRemovalFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Checks if workspace unit directory with the help of the given key.
+ *
+ * @param key given workspace unit key.
+ * @return result if workspace unit directory exists with the help of the given key.
+ */
+ public boolean isUnitDirectoryExist(String key) {
+ return Files.exists(Paths.get(properties.getWorkspaceDirectory(), key));
+ }
+
+ /**
+ * Retrieves path for the workspace unit with the help of the given key.
+ *
+ * @param key given workspace unit key.
+ * @throws WorkspaceUnitDirectoryNotFoundException if workspace unit with the given name does not
+ * exist.
+ */
+ public String getUnitDirectory(String key) throws WorkspaceUnitDirectoryNotFoundException {
+ Path unitDirectoryPath = Path.of(properties.getWorkspaceDirectory(), key);
+
+ if (Files.notExists(unitDirectoryPath)) {
+ throw new WorkspaceUnitDirectoryNotFoundException();
+ }
+
+ return unitDirectoryPath.toString();
+ }
+
+ /**
+ * Writes given content repository to the given workspace unit directory.
+ *
+ * @param workspaceUnitDirectory given workspace unit directory.
+ * @param name given content repository name.
+ * @param input given content repository input.
+ * @throws ContentFileWriteFailureException if content file cannot be created.
+ */
+ public void createContentFile(String workspaceUnitDirectory, String name, InputStream input) throws
+ ContentFileWriteFailureException {
+ Path contentDirectoryPath = Path.of(workspaceUnitDirectory, properties.getWorkspaceContentDirectory(), name);
+
+ File file = new File(contentDirectoryPath.toString());
+
+ try {
+ FileUtils.copyInputStreamToFile(input, file);
+ } catch (IOException e) {
+ throw new ContentFileWriteFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Removes content file in the given workspace unit.
+ *
+ * @param workspaceUnitDirectory given workspace unit directory.
+ * @param name given content repository name.
+ * @throws ContentFileRemovalFailureException if content file cannot be created.
+ */
+ public void removeContentFile(String workspaceUnitDirectory, String name) throws
+ ContentFileRemovalFailureException {
+ try {
+ FileSystemUtils.deleteRecursively(
+ Path.of(workspaceUnitDirectory, properties.getWorkspaceContentDirectory(), name));
+ } catch (IOException e) {
+ throw new ContentFileRemovalFailureException(e);
+ }
+ }
+
+ /**
+ * Retrieves content file of the given name with the help of the given workspace unit directory.
+ *
+ * @param workspaceUnitDirectory given workspace unit directory.
+ * @param name given name of the content file.
+ * @return content file entity.
+ * @throws ContentFileNotFoundException if the content file not found.
+ */
+ public OutputStream getContentFile(String workspaceUnitDirectory, String name) throws
+ ContentFileNotFoundException {
+ Path contentDirectoryPath = Path.of(workspaceUnitDirectory, properties.getWorkspaceContentDirectory(), name);
+
+ File file = new File(contentDirectoryPath.toString());
+
+ try {
+ return new FileOutputStream(file);
+ } catch (FileNotFoundException e) {
+ throw new ContentFileNotFoundException(e);
+ }
+ }
+
+ /**
+ * Writes metadata file input of the given type to the given workspace unit directory.
+ *
+ * @param workspaceUnitDirectory given workspace unit directory.
+ * @param type given type of the metadata file.
+ * @param input given metadata file entity input.
+ * @throws MetadataFileWriteFailureException if metadata file cannot be created.
+ */
+ private void createMetadataFile(String workspaceUnitDirectory, String type, MetadataFileEntity input)
+ throws MetadataFileWriteFailureException {
+ ObjectMapper mapper = new ObjectMapper();
+
+ File variableFile =
+ new File(
+ Paths.get(workspaceUnitDirectory, properties.getWorkspaceMetadataDirectory(), type)
+ .toString());
+
+ try {
+ mapper.writeValue(variableFile, input);
+ } catch (IOException e) {
+ throw new MetadataFileWriteFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Writes metadata file input of the prs type to the given workspace unit directory.
+ *
+ * @param workspaceUnitDirectory given workspace unit directory.
+ * @param input given metadata file entity input.
+ * @throws MetadataFileWriteFailureException if metadata file cannot be created.
+ */
+ public void createPRsMetadataFile(String workspaceUnitDirectory, MetadataFileEntity input)
+ throws MetadataFileWriteFailureException {
+ createMetadataFile(workspaceUnitDirectory, properties.getWorkspacePRsMetadataFileName(), input);
+ }
+
+ /**
+ * Writes metadata file input of the issues type to the given workspace unit directory.
+ *
+ * @param workspaceUnitDirectory given workspace unit directory.
+ * @param input given metadata file entity input.
+ * @throws MetadataFileWriteFailureException if metadata file cannot be created.
+ */
+ public void createIssuesMetadataFile(String workspaceUnitDirectory, MetadataFileEntity input)
+ throws MetadataFileWriteFailureException {
+ createMetadataFile(workspaceUnitDirectory, properties.getWorkspaceIssuesMetadataFileName(), input);
+ }
+
+ /**
+ * Writes metadata file input of the releases type to the given workspace unit directory.
+ *
+ * @param workspaceUnitDirectory given workspace unit directory.
+ * @param input given metadata file entity input.
+ * @throws MetadataFileWriteFailureException if metadata file cannot be created.
+ */
+ public void createReleasesMetadataFile(String workspaceUnitDirectory, MetadataFileEntity input)
+ throws MetadataFileWriteFailureException {
+ createMetadataFile(workspaceUnitDirectory, properties.getWorkspaceReleasesMetadataFileName(), input);
+ }
+
+ /**
+ * Checks if metadata file of the given type exists in the given workspace unit directory.
+ *
+ * @param workspaceUnitDirectory given workspace unit directory.
+ * @param type given type of the metadata file.
+ * @return result if metadata file exists in the given workspace unit directory.
+ */
+ private boolean isMetadataFileExist(String workspaceUnitDirectory, String type) {
+ return Files.exists(
+ Paths.get(workspaceUnitDirectory, properties.getWorkspaceMetadataDirectory(), type));
+ }
+
+ /**
+ * Checks if metadata file of prs type exists in the given workspace unit directory.
+ *
+ * @param workspaceUnitDirectory given workspace unit directory.
+ * @return result if metadata file exists in the given workspace unit directory.
+ */
+ private boolean isPRsMetadataFileExist(String workspaceUnitDirectory) {
+ return isMetadataFileExist(workspaceUnitDirectory, properties.getWorkspacePRsMetadataFileName());
+ }
+
+ /**
+ * Checks if metadata file of issues type exists in the given workspace unit directory.
+ *
+ * @param workspaceUnitDirectory given workspace unit directory.
+ * @return result if metadata file exists in the given workspace unit directory.
+ */
+ private boolean isIssuesMetadataFileExist(String workspaceUnitDirectory) {
+ return isMetadataFileExist(workspaceUnitDirectory, properties.getWorkspaceIssuesMetadataFileName());
+ }
+
+ /**
+ * Checks if metadata file of releases type exists in the given workspace unit directory.
+ *
+ * @param workspaceUnitDirectory given workspace unit directory.
+ * @return result if metadata file exists in the given workspace unit directory.
+ */
+ private boolean isReleasesMetadataFileExist(String workspaceUnitDirectory) {
+ return isMetadataFileExist(workspaceUnitDirectory, properties.getWorkspaceReleasesMetadataFileName());
+ }
+
+ /**
+ * Retrieves metadata file content of the given type with the help of the given workspace unit directory.
+ *
+ * @param workspaceUnitDirectory given workspace unit directory.
+ * @param type given type of the metadata file.
+ * @return metadata file entity.
+ * @throws MetadataFileNotFoundException if the metadata file not found.
+ */
+ public MetadataFileEntity getMetadataFileContent(String workspaceUnitDirectory, String type)
+ throws MetadataFileNotFoundException {
+ ObjectMapper mapper =
+ new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, true)
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
+ .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ ObjectReader reader = mapper.reader().forType(new TypeReference() {
+ });
+
+ InputStream variableFile;
+ try {
+ variableFile =
+ new FileInputStream(
+ Paths.get(workspaceUnitDirectory, properties.getWorkspaceMetadataDirectory(), type)
+ .toString());
+ } catch (FileNotFoundException e) {
+ throw new MetadataFileNotFoundException(e.getMessage());
+ }
+
+ try {
+ return reader.readValues(variableFile).readAll().getFirst();
+ } catch (IOException e) {
+ logger.fatal(e.getMessage());
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieves metadata file content of prs type with the help of the given workspace unit directory.
+ *
+ * @param workspaceUnitDirectory given workspace unit directory.
+ * @return metadata file entity.
+ * @throws MetadataFileNotFoundException if the metadata variable file not found.
+ */
+ public MetadataFileEntity getPRsMetadataFileContent(String workspaceUnitDirectory)
+ throws MetadataFileNotFoundException {
+ return getMetadataFileContent(workspaceUnitDirectory, properties.getWorkspacePRsMetadataFileName());
+ }
+
+ /**
+ * Retrieves metadata file content of issues type with the help of the given workspace unit directory.
+ *
+ * @param workspaceUnitDirectory given workspace unit directory.
+ * @return metadata file entity.
+ * @throws MetadataFileNotFoundException if the metadata variable file not found.
+ */
+ public MetadataFileEntity getIssuesMetadataFileContent(String workspaceUnitDirectory)
+ throws MetadataFileNotFoundException {
+ return getMetadataFileContent(workspaceUnitDirectory, properties.getWorkspaceIssuesMetadataFileName());
+ }
+
+ /**
+ * Retrieves metadata file content of releases type with the help of the given workspace unit directory.
+ *
+ * @param workspaceUnitDirectory given workspace unit directory.
+ * @return metadata file entity.
+ * @throws MetadataFileNotFoundException if the metadata variable file not found.
+ */
+ public MetadataFileEntity getReleasesMetadataFileContent(String workspaceUnitDirectory)
+ throws MetadataFileNotFoundException {
+ return getMetadataFileContent(workspaceUnitDirectory, properties.getWorkspaceReleasesMetadataFileName());
+ }
+}
diff --git a/api-server/src/main/java/com/repoachiever/service/workspace/facade/WorkspaceFacade.java b/api-server/src/main/java/com/repoachiever/service/workspace/facade/WorkspaceFacade.java
new file mode 100644
index 0000000..45a17ff
--- /dev/null
+++ b/api-server/src/main/java/com/repoachiever/service/workspace/facade/WorkspaceFacade.java
@@ -0,0 +1,114 @@
+package com.repoachiever.service.workspace.facade;
+
+import com.repoachiever.entity.common.PropertiesEntity;
+import com.repoachiever.model.CredentialsFieldsFull;
+import com.repoachiever.model.Provider;
+import com.repoachiever.service.workspace.WorkspaceService;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import java.io.InputStream;
+
+/**
+ * Provides high-level access to workspace operations.
+ */
+@ApplicationScoped
+public class WorkspaceFacade {
+ @Inject
+ PropertiesEntity properties;
+
+ @Inject
+ WorkspaceService workspaceService;
+
+ /**
+ * Creates unit key with the help of the given readiness check application.
+ *
+ * @param provider given provider.
+ * @param credentialsFields given credentials.
+ * @return result of the readiness check for the given configuration.
+ */
+ public String createUnitKey(Provider provider, CredentialsFieldsFull credentialsFields) {
+ return switch (provider) {
+ case LOCAL -> workspaceService.createUnitKey(String.valueOf(credentialsFields.getInternal().getId()));
+ case GITHUB -> workspaceService.createUnitKey(
+ String.valueOf(credentialsFields.getInternal().getId()), credentialsFields.getExternal().getToken());
+ };
+ }
+
+ /**
+ *
+ * @param input
+ * @param hash
+ * @param id
+ * @param provider
+ * @param credentialsFields
+ */
+ public void addContent(
+ InputStream input, String hash, String id, Provider provider, CredentialsFieldsFull credentialsFields) {
+
+ }
+
+ /**
+ *
+ * @param id
+ * @param provider
+ * @param credentialsFields
+ */
+ public void updatePRsMetadataFile(
+ String id, Provider provider, CredentialsFieldsFull credentialsFields) {
+
+ }
+
+ /**
+ *
+ * @param id
+ * @param provider
+ * @param credentialsFields
+ */
+ public void updateIssuesMetadataFile(
+ String id, Provider provider, CredentialsFieldsFull credentialsFields) {
+ }
+
+ /**
+ *
+ * @param id
+ * @param provider
+ * @param credentialsFields
+ */
+ public void updateReleasesMetadataFile(
+ String id, Provider provider, CredentialsFieldsFull credentialsFields) {
+ }
+
+// /**
+// * Update Kafka host in internal config file.
+// *
+// * @param provider given provider.
+// * @param credentialsFields given credentials.
+// * @throws WorkspaceUnitDirectoryNotFoundException if workspace unit directory was not found.
+// * @throws InternalConfigNotFoundException if internal config file was not found.
+// * @throws InternalConfigWriteFailureException if internal config file cannot be created.
+// */
+// public void updateKafkaHost(
+// String machineAdWorkspaceUnitDirectoryNotFoundExceptiondress, Provider provider, CredentialsFields credentialsFields)
+// throws,
+// InternalConfigNotFoundException,
+// InternalConfigWriteFailureException {
+// String workspaceUnitKey = createUnitKey(provider, credentialsFields);
+//
+// String workspaceUnitDirectory = workspaceService.getUnitDirectory(workspaceUnitKey);
+//
+// InternalConfigEntity internalConfig = null;
+//
+// if (workspaceService.isInternalConfigFileExist(workspaceUnitDirectory)) {
+// internalConfig = workspaceService.getInternalConfigFileContent(workspaceUnitDirectory);
+// }
+//
+// if (Objects.isNull(internalConfig)) {
+// internalConfig = new InternalConfigEntity();
+// }
+//
+// internalConfig.getKafka().setHost(machineAddress);
+//
+// workspaceService.createInternalConfigFile(workspaceUnitDirectory, internalConfig);
+// }
+}
diff --git a/api-server/src/main/openapi/openapi.yml b/api-server/src/main/openapi/openapi.yml
new file mode 100644
index 0000000..240916a
--- /dev/null
+++ b/api-server/src/main/openapi/openapi.yml
@@ -0,0 +1,352 @@
+openapi: 3.0.1
+info:
+ title: OpenAPI document of RepoAchiever API Server
+ description: RepoAchiever API Server Open API documentation
+ version: "1.0"
+
+tags:
+ - name: ContentResource
+ description: Contains all endpoints related to operations on processed content.
+ - name: StateResource
+ description: Contains all endpoints related to state processing.
+ - name: InfoResource
+ description: Contains all endpoints related to general info of API Server.
+ - name: HealthResource
+ description: Contains all endpoints related to general API Server health information.
+
+paths:
+ /v1/content:
+ post:
+ tags:
+ - ContentResource
+ requestBody:
+ required: true
+ description: Content retrieval application
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ContentRetrievalApplication"
+ responses:
+ 204:
+ description: A list of all available content
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ContentRetrievalResult"
+ /v1/content/apply:
+ post:
+ tags:
+ - ContentResource
+ requestBody:
+ required: true
+ description: Content configuration application
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ContentApplication"
+ responses:
+ 204:
+ description: Given content configuration was successfully applied
+ 400:
+ description: Given content configuration was not applied
+ /v1/content/withdraw:
+ delete:
+ tags:
+ - ContentResource
+ requestBody:
+ required: true
+ description: Content withdraw application. Does not remove persisted content.
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ContentWithdrawal"
+ responses:
+ 204:
+ description: Given content configuration was successfully withdrawn
+ 400:
+ description: Given content configuration was not withdrawn
+ /v1/content/download:
+ get:
+ tags:
+ - ContentResource
+ parameters:
+ - in: query
+ name: location
+ schema:
+ type: string
+ description: Name of content location to be downloaded
+ responses:
+ 200:
+ description: A content was successfully retrieved
+ content:
+ application/octet-stream:
+ schema:
+ type: string
+ format: binary
+ /v1/content/clean:
+ post:
+ tags:
+ - ContentResource
+ requestBody:
+ required: true
+ description: Content configuration application
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ContentCleanup"
+ responses:
+ 201:
+ description: Content with the given configuration was successfully deleted
+ 400:
+ description: Content with the given configuration was not deleted
+ /v1/state/content:
+ post:
+ tags:
+ - StateResource
+ requestBody:
+ required: true
+ description: Given content state key
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ContentStateApplication"
+ responses:
+ 201:
+ description: Content state hash is retrieved successfully
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ContentStateApplicationResult"
+ /v1/info/version:
+ get:
+ tags:
+ - InfoResource
+ responses:
+ 200:
+ description: General information about running API Server
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/VersionInfoResult"
+ /v1/info/cluster:
+ get:
+ tags:
+ - InfoResource
+ responses:
+ 200:
+ description: General information about running clusters
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ClusterInfoResult"
+ /v1/info/telemetry:
+ get:
+ tags:
+ - InfoResource
+ responses:
+ 200:
+ description: A set of Prometheus samples used by Grafana instance
+ content:
+ text/plain:
+ schema:
+ type: string
+ /v1/health:
+ get:
+ tags:
+ - HealthResource
+ responses:
+ 200:
+ description: General health information about running API Server
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/HealthCheckResult"
+ /v1/readiness:
+ post:
+ tags:
+ - HealthResource
+ requestBody:
+ required: true
+ description: Check if API Server is ready to serve for the given user
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ReadinessCheckApplication"
+ responses:
+ 200:
+ description: General health information about running API Server
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ReadinessCheckResult"
+components:
+ schemas:
+ Provider:
+ type: string
+ enum:
+ - git-local
+ - git-github
+ CredentialsFieldsFull:
+ required:
+ - internal
+ properties:
+ internal:
+ $ref: "#/components/schemas/CredentialsFieldsInternal"
+ external:
+ $ref: "#/components/schemas/CredentialsFieldsExternal"
+ CredentialsFieldsInternal:
+ properties:
+ id:
+ type: integer
+ CredentialsFieldsExternal:
+ anyOf:
+ - $ref: "#/components/schemas/GitGitHubCredentials"
+ GitGitHubCredentials:
+ required:
+ - token
+ properties:
+ token:
+ type: string
+ ContentRetrievalApplication:
+ required:
+ - provider
+ - credentials
+ properties:
+ provider:
+ $ref: "#/components/schemas/Provider"
+ credentials:
+ $ref: "#/components/schemas/CredentialsFieldsFull"
+ ContentRetrievalResult:
+ required:
+ - locations
+ properties:
+ locations:
+ type: array
+ items:
+ type: string
+ ContentApplication:
+ required:
+ - locations
+ - provider
+ - credentials
+ properties:
+ locations:
+ type: array
+ items:
+ type: string
+ provider:
+ $ref: "#/components/schemas/Provider"
+ credentials:
+ $ref: "#/components/schemas/CredentialsFieldsFull"
+ ContentWithdrawal:
+ required:
+ - credentials
+ - provider
+ properties:
+ provider:
+ $ref: "#/components/schemas/Provider"
+ credentials:
+ $ref: "#/components/schemas/CredentialsFieldsFull"
+ ContentCleanup:
+ required:
+ - credentials
+ properties:
+ credentials:
+ $ref: "#/components/schemas/CredentialsFieldsFull"
+ ContentStateApplication:
+ required:
+ - provider
+ - credentials
+ properties:
+ provider:
+ $ref: "#/components/schemas/Provider"
+ credentials:
+ $ref: "#/components/schemas/CredentialsFieldsFull"
+ ContentStateApplicationResult:
+ required:
+ - hash
+ properties:
+ hash:
+ type: string
+ VersionInfoResult:
+ properties:
+ externalApi:
+ $ref: "#/components/schemas/VersionExternalApiInfoResult"
+ VersionExternalApiInfoResult:
+ required:
+ - version
+ - hash
+ properties:
+ version:
+ type: string
+ hash:
+ type: string
+ ClusterInfoResult:
+ type: array
+ items:
+ $ref: "#/components/schemas/ClusterInfoUnit"
+ ClusterInfoUnit:
+ required:
+ - name
+ properties:
+ name:
+ type: string
+ health:
+ type: boolean
+ workers:
+ type: integer
+ HealthCheckResult:
+ required:
+ - status
+ - checks
+ properties:
+ status:
+ $ref: "#/components/schemas/HealthCheckStatus"
+ checks:
+ type: array
+ items:
+ $ref: "#/components/schemas/HealthCheckUnit"
+ HealthCheckUnit:
+ required:
+ - name
+ - status
+ properties:
+ name:
+ type: string
+ status:
+ $ref: "#/components/schemas/HealthCheckStatus"
+ HealthCheckStatus:
+ type: string
+ enum:
+ - UP
+ - DOWN
+ ReadinessCheckApplication:
+ properties:
+ test:
+ type: object
+ ReadinessCheckResult:
+ required:
+ - name
+ - status
+ - data
+ properties:
+ name:
+ type: string
+ status:
+ $ref: "#/components/schemas/ReadinessCheckStatus"
+ data:
+ type: object
+ ReadinessCheckUnit:
+ required:
+ - name
+ - status
+ properties:
+ name:
+ type: string
+ status:
+ $ref: "#/components/schemas/ReadinessCheckStatus"
+ ReadinessCheckStatus:
+ type: string
+ enum:
+ - UP
+ - DOWN
diff --git a/api-server/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource b/api-server/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource
new file mode 100644
index 0000000..1803d67
--- /dev/null
+++ b/api-server/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource
@@ -0,0 +1 @@
+com.repoachiever.service.integration.properties.git.GitPropertiesConfigService
\ No newline at end of file
diff --git a/api-server/src/main/resources/application.properties b/api-server/src/main/resources/application.properties
new file mode 100644
index 0000000..f8772cd
--- /dev/null
+++ b/api-server/src/main/resources/application.properties
@@ -0,0 +1,155 @@
+# Describes general internal Quarkus configuration.
+quarkus.http.cors=false
+quarkus.smallrye-health.ui.always-include=true
+quarkus.swagger-ui.always-include=true
+quarkus.native.builder-image=graalvm
+
+# Describes database Quarkus configuration.
+quarkus.datasource.jdbc.driver=org.sqlite.JDBC
+quarkus.datasource.db-kind=other
+quarkus.datasource.jdbc.url=jdbc:sqlite:${user.home}/.repoachiever/internal/database/data.db
+quarkus.datasource.username=repoachiever_user
+quarkus.datasource.password=repoachiever_password
+
+# Describes LiquiBase Quarkus configuration.
+quarkus.liquibase.change-log=liquibase/config.yaml
+quarkus.liquibase.migrate-at-start=true
+
+# Describes external Quarkus clients configuration.
+quarkus.rest-client.github.url=https://api.github.com
+quarkus.rest-client.small-rye-health-check.url=http://${quarkus.http.host}:${quarkus.http.port}
+
+# Describes location of RepoAchiever API Server states.
+state.location=${user.home}/.repoachiever/internal/state
+
+# Describes name of RepoAchiever API Server running state.
+state.running.name=.running
+
+# Describes database config table name.
+database.tables.config.name=config
+
+# Describes database content table name.
+database.tables.content.name=content
+
+# Describes database provider table name.
+database.tables.provider.name=provider
+
+# Describes database secrets table name.
+database.tables.secret.name=secret
+
+# Describes database statement close delay duration.
+database.statement.close-delay=10000
+
+# Describes git configuration properties file.
+git.config.location=git.properties
+
+# Describes location of RepoAchiever executable files.
+bin.directory=${user.home}/.repoachiever/bin
+
+# Describes location of RepoAchiever Cluster executable file.
+bin.cluster.location=cluster/cluster.jar
+
+# Describes location of application configurations.
+config.directory=${user.home}/.repoachiever/config
+
+# Describes name of the application configuration file.
+config.name=api-server.yaml
+
+# Describes location of local workspace.
+workspace.directory=${user.home}/.repoachiever/workspace
+
+# Describes location of content directory.
+workspace.content.directory=content
+
+# Describes max amount of versions for each content repository.
+workspace.content.version-amount=5
+
+# Describes location of metadata directory.
+workspace.metadata.directory=metadata
+
+# Describes location of pull requests metadata file.
+workspace.prs-metadata-file.name=prs.json
+
+# Describes location of issue metadata file.
+workspace.issues-metadata-file.name=issues.json
+
+# Describes location of releases metadata file.
+workspace.releases-metadata-file.name=releases.json
+
+# Describes RepoAchiever Cluster context environment variable name.
+repoachiever-cluster.context.alias=REPOACHIEVER_CLUSTER_CONTEXT
+
+# Describes RepoAchiever API Server communication provider name.
+communication.api-server.name=repoachiever-api-server
+
+# Describes RepoAchiever Cluster allocation base prefix.
+communication.cluster.base=repoachiever-cluster
+
+# Describes RepoAchiever Cluster startup await frequency duration.
+communication.cluster.startup-await-frequency=1000
+
+# Describes RepoAchiever Cluster startup timeout duration.
+communication.cluster.startup-timeout=10000
+
+# Describes RepoAchiever Cluster health check operation frequency duration.
+communication.cluster.health-check-frequency=1000
+
+# Describes name of the Docker network used to install diagnostics infrastructure.
+diagnostics.common.docker.network.name=repoachiever-cluster
+
+# Describes location of Grafana configuration files.
+diagnostics.grafana.config.location=${user.home}/.repoachiever/diagnostics/grafana/config
+
+# Describes location of Grafana datasources configuration files.
+diagnostics.grafana.datasources.location=${user.home}/.repoachiever/diagnostics/grafana/config/datasources
+
+# Describes name of Grafana configuration template file.
+diagnostics.grafana.datasources.template=datasource.tmpl
+
+# Describes name of Grafana configuration template processing output file.
+diagnostics.grafana.datasources.output=datasource.yml
+
+# Describes location of Grafana dashboards configuration files.
+diagnostics.grafana.dashboards.location=${user.home}/.repoachiever/diagnostics/grafana/config/dashboards
+
+# Describes location of Grafana diagnostics dashboards configuration files.
+diagnostics.grafana.dashboards.diagnostics.template=diagnostics.tmpl
+
+# Describes location of Grafana diagnostics dashboards configuration files.
+diagnostics.grafana.dashboards.diagnostics.output=diagnostics.json
+
+# Describes location of Grafana internal files.
+diagnostics.grafana.internal.location=${user.home}/.repoachiever/diagnostics/grafana/internal
+
+# Describes name of the Docker container used for Grafana instance deployment.
+diagnostics.grafana.docker.name=repoachiever-diagnostics-grafana
+
+# Describes image name of the Docker container used for Grafana instance deployment.
+diagnostics.grafana.docker.image=grafana/grafana
+
+# Describes location of Prometheus configuration files.
+diagnostics.prometheus.config.location=${user.home}/.repoachiever/diagnostics/prometheus/config
+
+# Describes name of Prometheus configuration template file.
+diagnostics.prometheus.config.template=prometheus.tmpl
+
+# Describes name of Prometheus configuration template processing output file.
+diagnostics.prometheus.config.output=prometheus.yml
+
+# Describes location of Prometheus internal files.
+diagnostics.prometheus.internal.location=${user.home}/.repoachiever/diagnostics/prometheus/internal
+
+# Describes name of the Docker container used for Prometheus instance deployment.
+diagnostics.prometheus.docker.name=repoachiever-diagnostics-prometheus
+
+# Describes image name of the Docker container used for Prometheus instance deployment.
+diagnostics.prometheus.docker.image=prom/prometheus:v2.36.2
+
+# Describes name of the Docker container used for Prometheus Node Exporter instance deployment.
+diagnostics.prometheus.node-exporter.docker.name=repoachiever-diagnostics-prometheus-node-exporter
+
+# Describes image name of the Docker container used for Prometheus Node Exporter instance deployment.
+diagnostics.prometheus.node-exporter.docker.image=quay.io/prometheus/node-exporter:latest
+
+# Describes connection timeout used by metrics service.
+diagnostics.metrics.connection.timeout=3000
\ No newline at end of file
diff --git a/api-server/src/main/resources/liquibase/config.yaml b/api-server/src/main/resources/liquibase/config.yaml
new file mode 100644
index 0000000..ef09d45
--- /dev/null
+++ b/api-server/src/main/resources/liquibase/config.yaml
@@ -0,0 +1,104 @@
+databaseChangeLog:
+ - changeSet:
+ id: 1
+ author: YarikRevich
+ changes:
+ - createTable:
+ tableName: config
+ columns:
+ - column:
+ name: id
+ type: INT
+ autoIncrement: true
+ constraints:
+ primaryKey: true
+ nullable: false
+ - column:
+ name: name
+ type: VARCHAR
+ constraints:
+ unique: true
+ nullable: false
+ - column:
+ name: hash
+ type: VARCHAR
+ constraints:
+ nullable: false
+ - createTable:
+ tableName: secret
+ columns:
+ - column:
+ name: id
+ type: INT
+ autoIncrement: true
+ constraints:
+ primaryKey: true
+ nullable: false
+ - column:
+ name: session
+ type: INT
+ constraints:
+ nullable: false
+ - column:
+ name: credentials
+ type: VARCHAR
+ constraints:
+ nullable: true
+ - createTable:
+ tableName: provider
+ columns:
+ - column:
+ name: id
+ type: INT
+ autoIncrement: true
+ constraints:
+ primaryKey: true
+ nullable: false
+ - column:
+ name: name
+ type: VARCHAR
+ constraints:
+ unique: true
+ nullable: false
+ - createTable:
+ tableName: content
+ columns:
+ - column:
+ name: id
+ type: INT
+ autoIncrement: true
+ constraints:
+ primaryKey: true
+ nullable: false
+ - column:
+ name: location
+ type: VARCHAR
+ constraints:
+ nullable: false
+ - column:
+ name: provider
+ type: INT
+ constraints:
+ foreignKeyName: provider_fk
+ references: provider(id)
+ nullable: false
+ - column:
+ name: secret
+ type: INT
+ constraints:
+ foreignKeyName: secret_fk
+ references: secret(id)
+ nullable: false
+ - loadData:
+ tableName: provider
+ usePreparedStatements: false
+ separator: ;
+ relativeToChangelogFile: true
+ file: data/data.csv
+ encoding: UTF-8
+ quotchar: ''''
+ columns:
+ - column:
+ header: Name
+ name: name
+ type: STRING
\ No newline at end of file
diff --git a/api-server/src/main/resources/liquibase/data/data.csv b/api-server/src/main/resources/liquibase/data/data.csv
new file mode 100644
index 0000000..5b0462b
--- /dev/null
+++ b/api-server/src/main/resources/liquibase/data/data.csv
@@ -0,0 +1,3 @@
+Name
+git-local
+git-github
\ No newline at end of file
diff --git a/api-server/src/main/resources/log4j2.xml b/api-server/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..e47463a
--- /dev/null
+++ b/api-server/src/main/resources/log4j2.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api-server/target/classes/META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat b/api-server/target/classes/META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat
new file mode 100644
index 0000000..1a80445
Binary files /dev/null and b/api-server/target/classes/META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat differ
diff --git a/api-server/target/classes/META-INF/panache-archive.marker b/api-server/target/classes/META-INF/panache-archive.marker
new file mode 100644
index 0000000..eb166da
--- /dev/null
+++ b/api-server/target/classes/META-INF/panache-archive.marker
@@ -0,0 +1 @@
+This file is a marker, it exists to tell Quarkus that this archive has a dependency on Panache, and may need to be transformed at build time
\ No newline at end of file
diff --git a/api-server/target/classes/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource b/api-server/target/classes/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource
new file mode 100644
index 0000000..1803d67
--- /dev/null
+++ b/api-server/target/classes/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource
@@ -0,0 +1 @@
+com.repoachiever.service.integration.properties.git.GitPropertiesConfigService
\ No newline at end of file
diff --git a/api-server/target/classes/application.properties b/api-server/target/classes/application.properties
new file mode 100644
index 0000000..f8772cd
--- /dev/null
+++ b/api-server/target/classes/application.properties
@@ -0,0 +1,155 @@
+# Describes general internal Quarkus configuration.
+quarkus.http.cors=false
+quarkus.smallrye-health.ui.always-include=true
+quarkus.swagger-ui.always-include=true
+quarkus.native.builder-image=graalvm
+
+# Describes database Quarkus configuration.
+quarkus.datasource.jdbc.driver=org.sqlite.JDBC
+quarkus.datasource.db-kind=other
+quarkus.datasource.jdbc.url=jdbc:sqlite:${user.home}/.repoachiever/internal/database/data.db
+quarkus.datasource.username=repoachiever_user
+quarkus.datasource.password=repoachiever_password
+
+# Describes LiquiBase Quarkus configuration.
+quarkus.liquibase.change-log=liquibase/config.yaml
+quarkus.liquibase.migrate-at-start=true
+
+# Describes external Quarkus clients configuration.
+quarkus.rest-client.github.url=https://api.github.com
+quarkus.rest-client.small-rye-health-check.url=http://${quarkus.http.host}:${quarkus.http.port}
+
+# Describes location of RepoAchiever API Server states.
+state.location=${user.home}/.repoachiever/internal/state
+
+# Describes name of RepoAchiever API Server running state.
+state.running.name=.running
+
+# Describes database config table name.
+database.tables.config.name=config
+
+# Describes database content table name.
+database.tables.content.name=content
+
+# Describes database provider table name.
+database.tables.provider.name=provider
+
+# Describes database secrets table name.
+database.tables.secret.name=secret
+
+# Describes database statement close delay duration.
+database.statement.close-delay=10000
+
+# Describes git configuration properties file.
+git.config.location=git.properties
+
+# Describes location of RepoAchiever executable files.
+bin.directory=${user.home}/.repoachiever/bin
+
+# Describes location of RepoAchiever Cluster executable file.
+bin.cluster.location=cluster/cluster.jar
+
+# Describes location of application configurations.
+config.directory=${user.home}/.repoachiever/config
+
+# Describes name of the application configuration file.
+config.name=api-server.yaml
+
+# Describes location of local workspace.
+workspace.directory=${user.home}/.repoachiever/workspace
+
+# Describes location of content directory.
+workspace.content.directory=content
+
+# Describes max amount of versions for each content repository.
+workspace.content.version-amount=5
+
+# Describes location of metadata directory.
+workspace.metadata.directory=metadata
+
+# Describes location of pull requests metadata file.
+workspace.prs-metadata-file.name=prs.json
+
+# Describes location of issue metadata file.
+workspace.issues-metadata-file.name=issues.json
+
+# Describes location of releases metadata file.
+workspace.releases-metadata-file.name=releases.json
+
+# Describes RepoAchiever Cluster context environment variable name.
+repoachiever-cluster.context.alias=REPOACHIEVER_CLUSTER_CONTEXT
+
+# Describes RepoAchiever API Server communication provider name.
+communication.api-server.name=repoachiever-api-server
+
+# Describes RepoAchiever Cluster allocation base prefix.
+communication.cluster.base=repoachiever-cluster
+
+# Describes RepoAchiever Cluster startup await frequency duration.
+communication.cluster.startup-await-frequency=1000
+
+# Describes RepoAchiever Cluster startup timeout duration.
+communication.cluster.startup-timeout=10000
+
+# Describes RepoAchiever Cluster health check operation frequency duration.
+communication.cluster.health-check-frequency=1000
+
+# Describes name of the Docker network used to install diagnostics infrastructure.
+diagnostics.common.docker.network.name=repoachiever-cluster
+
+# Describes location of Grafana configuration files.
+diagnostics.grafana.config.location=${user.home}/.repoachiever/diagnostics/grafana/config
+
+# Describes location of Grafana datasources configuration files.
+diagnostics.grafana.datasources.location=${user.home}/.repoachiever/diagnostics/grafana/config/datasources
+
+# Describes name of Grafana configuration template file.
+diagnostics.grafana.datasources.template=datasource.tmpl
+
+# Describes name of Grafana configuration template processing output file.
+diagnostics.grafana.datasources.output=datasource.yml
+
+# Describes location of Grafana dashboards configuration files.
+diagnostics.grafana.dashboards.location=${user.home}/.repoachiever/diagnostics/grafana/config/dashboards
+
+# Describes location of Grafana diagnostics dashboards configuration files.
+diagnostics.grafana.dashboards.diagnostics.template=diagnostics.tmpl
+
+# Describes location of Grafana diagnostics dashboards configuration files.
+diagnostics.grafana.dashboards.diagnostics.output=diagnostics.json
+
+# Describes location of Grafana internal files.
+diagnostics.grafana.internal.location=${user.home}/.repoachiever/diagnostics/grafana/internal
+
+# Describes name of the Docker container used for Grafana instance deployment.
+diagnostics.grafana.docker.name=repoachiever-diagnostics-grafana
+
+# Describes image name of the Docker container used for Grafana instance deployment.
+diagnostics.grafana.docker.image=grafana/grafana
+
+# Describes location of Prometheus configuration files.
+diagnostics.prometheus.config.location=${user.home}/.repoachiever/diagnostics/prometheus/config
+
+# Describes name of Prometheus configuration template file.
+diagnostics.prometheus.config.template=prometheus.tmpl
+
+# Describes name of Prometheus configuration template processing output file.
+diagnostics.prometheus.config.output=prometheus.yml
+
+# Describes location of Prometheus internal files.
+diagnostics.prometheus.internal.location=${user.home}/.repoachiever/diagnostics/prometheus/internal
+
+# Describes name of the Docker container used for Prometheus instance deployment.
+diagnostics.prometheus.docker.name=repoachiever-diagnostics-prometheus
+
+# Describes image name of the Docker container used for Prometheus instance deployment.
+diagnostics.prometheus.docker.image=prom/prometheus:v2.36.2
+
+# Describes name of the Docker container used for Prometheus Node Exporter instance deployment.
+diagnostics.prometheus.node-exporter.docker.name=repoachiever-diagnostics-prometheus-node-exporter
+
+# Describes image name of the Docker container used for Prometheus Node Exporter instance deployment.
+diagnostics.prometheus.node-exporter.docker.image=quay.io/prometheus/node-exporter:latest
+
+# Describes connection timeout used by metrics service.
+diagnostics.metrics.connection.timeout=3000
\ No newline at end of file
diff --git a/api-server/target/classes/git.properties b/api-server/target/classes/git.properties
new file mode 100644
index 0000000..8625b3b
--- /dev/null
+++ b/api-server/target/classes/git.properties
@@ -0,0 +1,22 @@
+#Generated by Git-Commit-Id-Plugin
+#Sun May 12 16:25:41 CEST 2024
+git.branch=feature/base
+git.build.host=Yaroslavs-MacBook-Pro.local
+git.build.time=05/12/2024 16\:25\:41 +0200
+git.build.user.email=yariksvitlitskiy81@gmail.com
+git.build.user.name=Yaroslav Svitlytskyi
+git.build.version=1.0-SNAPSHOT
+git.closest.tag.commit.count=
+git.closest.tag.name=
+git.commit.id=7af25884b7fd03964465f3e8e205c3aff2bff78b
+git.commit.id.abbrev=7af25884
+git.commit.id.describe=7af2588-dirty
+git.commit.id.describe-short=7af2588-dirty
+git.commit.message.full=fix\: fixed bugs
+git.commit.message.short=fix\: fixed bugs
+git.commit.time=05/12/2024 15\:00\:09 +0200
+git.commit.user.email=yariksvitlitskiy81@gmail.com
+git.commit.user.name=Yaroslav Svitlytskyi
+git.dirty=true
+git.remote.origin.url=git@github.com\:YarikRevich/RepoArchiever.git
+git.tags=
diff --git a/api-server/target/classes/liquibase/config.yaml b/api-server/target/classes/liquibase/config.yaml
new file mode 100644
index 0000000..ef09d45
--- /dev/null
+++ b/api-server/target/classes/liquibase/config.yaml
@@ -0,0 +1,104 @@
+databaseChangeLog:
+ - changeSet:
+ id: 1
+ author: YarikRevich
+ changes:
+ - createTable:
+ tableName: config
+ columns:
+ - column:
+ name: id
+ type: INT
+ autoIncrement: true
+ constraints:
+ primaryKey: true
+ nullable: false
+ - column:
+ name: name
+ type: VARCHAR
+ constraints:
+ unique: true
+ nullable: false
+ - column:
+ name: hash
+ type: VARCHAR
+ constraints:
+ nullable: false
+ - createTable:
+ tableName: secret
+ columns:
+ - column:
+ name: id
+ type: INT
+ autoIncrement: true
+ constraints:
+ primaryKey: true
+ nullable: false
+ - column:
+ name: session
+ type: INT
+ constraints:
+ nullable: false
+ - column:
+ name: credentials
+ type: VARCHAR
+ constraints:
+ nullable: true
+ - createTable:
+ tableName: provider
+ columns:
+ - column:
+ name: id
+ type: INT
+ autoIncrement: true
+ constraints:
+ primaryKey: true
+ nullable: false
+ - column:
+ name: name
+ type: VARCHAR
+ constraints:
+ unique: true
+ nullable: false
+ - createTable:
+ tableName: content
+ columns:
+ - column:
+ name: id
+ type: INT
+ autoIncrement: true
+ constraints:
+ primaryKey: true
+ nullable: false
+ - column:
+ name: location
+ type: VARCHAR
+ constraints:
+ nullable: false
+ - column:
+ name: provider
+ type: INT
+ constraints:
+ foreignKeyName: provider_fk
+ references: provider(id)
+ nullable: false
+ - column:
+ name: secret
+ type: INT
+ constraints:
+ foreignKeyName: secret_fk
+ references: secret(id)
+ nullable: false
+ - loadData:
+ tableName: provider
+ usePreparedStatements: false
+ separator: ;
+ relativeToChangelogFile: true
+ file: data/data.csv
+ encoding: UTF-8
+ quotchar: ''''
+ columns:
+ - column:
+ header: Name
+ name: name
+ type: STRING
\ No newline at end of file
diff --git a/api-server/target/classes/liquibase/data/data.csv b/api-server/target/classes/liquibase/data/data.csv
new file mode 100644
index 0000000..5b0462b
--- /dev/null
+++ b/api-server/target/classes/liquibase/data/data.csv
@@ -0,0 +1,3 @@
+Name
+git-local
+git-github
\ No newline at end of file
diff --git a/api-server/target/classes/log4j2.xml b/api-server/target/classes/log4j2.xml
new file mode 100644
index 0000000..e47463a
--- /dev/null
+++ b/api-server/target/classes/log4j2.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api-server/target/failsafe-reports/failsafe-summary.xml b/api-server/target/failsafe-reports/failsafe-summary.xml
new file mode 100644
index 0000000..fffbf43
--- /dev/null
+++ b/api-server/target/failsafe-reports/failsafe-summary.xml
@@ -0,0 +1,8 @@
+
+
+ 0
+ 0
+ 0
+ 0
+
+
\ No newline at end of file
diff --git a/api-server/target/generated-sources/openapi/.dockerignore b/api-server/target/generated-sources/openapi/.dockerignore
new file mode 100644
index 0000000..b86c7ac
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/.dockerignore
@@ -0,0 +1,4 @@
+*
+!target/*-runner
+!target/*-runner.jar
+!target/lib/*
\ No newline at end of file
diff --git a/api-server/target/generated-sources/openapi/.openapi-generator-ignore b/api-server/target/generated-sources/openapi/.openapi-generator-ignore
new file mode 100644
index 0000000..7484ee5
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/.openapi-generator-ignore
@@ -0,0 +1,23 @@
+# OpenAPI Generator Ignore
+# Generated by openapi-generator https://github.com/openapitools/openapi-generator
+
+# Use this file to prevent files from being overwritten by the generator.
+# The patterns follow closely to .gitignore or .dockerignore.
+
+# As an example, the C# client generator defines ApiClient.cs.
+# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
+#ApiClient.cs
+
+# You can match any string of characters against a directory, file or extension with a single asterisk (*):
+#foo/*/qux
+# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
+
+# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
+#foo/**/qux
+# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
+
+# You can also negate patterns with an exclamation (!).
+# For example, you can ignore all files in a docs folder with the file extension .md:
+#docs/*.md
+# Then explicitly reverse the ignore rule for a single file:
+#!docs/README.md
diff --git a/api-server/target/generated-sources/openapi/.openapi-generator/FILES b/api-server/target/generated-sources/openapi/.openapi-generator/FILES
new file mode 100644
index 0000000..ced31dc
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/.openapi-generator/FILES
@@ -0,0 +1,35 @@
+.dockerignore
+.openapi-generator-ignore
+README.md
+pom.xml
+src/main/docker/Dockerfile.jvm
+src/main/docker/Dockerfile.native
+src/main/java/com/repoachiever/RestResourceRoot.java
+src/main/java/com/repoachiever/api/ContentResourceApi.java
+src/main/java/com/repoachiever/api/HealthResourceApi.java
+src/main/java/com/repoachiever/api/InfoResourceApi.java
+src/main/java/com/repoachiever/api/StateResourceApi.java
+src/main/java/com/repoachiever/model/ClusterInfoUnit.java
+src/main/java/com/repoachiever/model/ContentApplication.java
+src/main/java/com/repoachiever/model/ContentCleanup.java
+src/main/java/com/repoachiever/model/ContentRetrievalApplication.java
+src/main/java/com/repoachiever/model/ContentRetrievalResult.java
+src/main/java/com/repoachiever/model/ContentStateApplication.java
+src/main/java/com/repoachiever/model/ContentStateApplicationResult.java
+src/main/java/com/repoachiever/model/ContentWithdrawal.java
+src/main/java/com/repoachiever/model/CredentialsFieldsExternal.java
+src/main/java/com/repoachiever/model/CredentialsFieldsFull.java
+src/main/java/com/repoachiever/model/CredentialsFieldsInternal.java
+src/main/java/com/repoachiever/model/GitGitHubCredentials.java
+src/main/java/com/repoachiever/model/HealthCheckResult.java
+src/main/java/com/repoachiever/model/HealthCheckStatus.java
+src/main/java/com/repoachiever/model/HealthCheckUnit.java
+src/main/java/com/repoachiever/model/Provider.java
+src/main/java/com/repoachiever/model/ReadinessCheckApplication.java
+src/main/java/com/repoachiever/model/ReadinessCheckResult.java
+src/main/java/com/repoachiever/model/ReadinessCheckStatus.java
+src/main/java/com/repoachiever/model/ReadinessCheckUnit.java
+src/main/java/com/repoachiever/model/VersionExternalApiInfoResult.java
+src/main/java/com/repoachiever/model/VersionInfoResult.java
+src/main/resources/META-INF/openapi.yaml
+src/main/resources/application.properties
diff --git a/api-server/target/generated-sources/openapi/.openapi-generator/VERSION b/api-server/target/generated-sources/openapi/.openapi-generator/VERSION
new file mode 100644
index 0000000..c0be8a7
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/.openapi-generator/VERSION
@@ -0,0 +1 @@
+6.4.0
\ No newline at end of file
diff --git a/api-server/target/generated-sources/openapi/.openapi-generator/openapi.yml-default.sha256 b/api-server/target/generated-sources/openapi/.openapi-generator/openapi.yml-default.sha256
new file mode 100644
index 0000000..229ea46
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/.openapi-generator/openapi.yml-default.sha256
@@ -0,0 +1 @@
+23dd5ef7368f378589d86135d0c9e3602c82eeb0dd992c1c2efe429c7811b94e
\ No newline at end of file
diff --git a/api-server/target/generated-sources/openapi/README.md b/api-server/target/generated-sources/openapi/README.md
new file mode 100644
index 0000000..578b4eb
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/README.md
@@ -0,0 +1,15 @@
+# JAX-RS server with OpenAPI using Quarkus
+
+## Overview
+This server was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using an
+[OpenAPI-Spec](https://openapis.org), you can easily generate a server stub.
+
+This is an example of building a OpenAPI-enabled JAX-RS server.
+This example uses the [JAX-RS](https://jax-rs-spec.java.net/) framework and
+the [Eclipse-MicroProfile-OpenAPI](https://github.com/eclipse/microprofile-open-api) addition.
+
+The pom file is configured to use [Quarkus](https://quarkus.io/) as application server.
+
+This project produces a jar that defines some interfaces.
+The jar can be used in combination with another project providing the implementation.
+
diff --git a/api-server/target/generated-sources/openapi/pom.xml b/api-server/target/generated-sources/openapi/pom.xml
new file mode 100644
index 0000000..b1e2d58
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/pom.xml
@@ -0,0 +1,137 @@
+
+
+ 4.0.0
+ org.openapitools
+ openapi-jaxrs-client
+ openapi-jaxrs-client
+ 1.0
+
+
+
+ 3.8.1
+ true
+ 1.8
+ 1.8
+ UTF-8
+ UTF-8
+ 1.1.1.Final
+ quarkus-universe-bom
+ io.quarkus
+ 1.1.1.Final
+ 2.22.1
+
+
+
+
+ ${quarkus.platform.group-id}
+ ${quarkus.platform.artifact-id}
+ ${quarkus.platform.version}
+ pom
+ import
+
+
+
+
+
+ io.quarkus
+ quarkus-resteasy
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ io.quarkus
+ quarkus-smallrye-openapi
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 1.9.1
+
+
+ add-source
+ generate-sources
+
+ add-source
+
+
+
+
+
+
+
+
+
+
+ io.quarkus
+ quarkus-maven-plugin
+ ${quarkus-plugin.version}
+
+
+
+ build
+
+
+
+
+
+ maven-compiler-plugin
+ ${compiler-plugin.version}
+
+
+ maven-surefire-plugin
+ ${surefire-plugin.version}
+
+
+ org.jboss.logmanager.LogManager
+
+
+
+
+
+
+
+ native
+
+
+ native
+
+
+
+
+
+ maven-failsafe-plugin
+ ${surefire-plugin.version}
+
+
+
+ integration-test
+ verify
+
+
+
+ ${project.build.directory}/${project.build.finalName}-runner
+
+
+
+
+
+
+
+
+ native
+
+
+
+
diff --git a/api-server/target/generated-sources/openapi/src/main/docker/Dockerfile.jvm b/api-server/target/generated-sources/openapi/src/main/docker/Dockerfile.jvm
new file mode 100644
index 0000000..87730c9
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/docker/Dockerfile.jvm
@@ -0,0 +1,34 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the docker image run:
+#
+# mvn package
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/openapi-jaxrs-client-jvm .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/openapi-jaxrs-client-jvm
+#
+###
+FROM fabric8/java-alpine-openjdk8-jre:1.6.5
+ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV AB_ENABLED=jmx_exporter
+
+# Be prepared for running in OpenShift too
+RUN adduser -G root --no-create-home --disabled-password 1001 \
+ && chown -R 1001 /deployments \
+ && chmod -R "g+rwX" /deployments \
+ && chown -R 1001:root /deployments
+
+COPY target/lib/* /deployments/lib/
+COPY target/*-runner.jar /deployments/app.jar
+EXPOSE 8080
+
+# run with user 1001
+USER 1001
+
+ENTRYPOINT [ "/deployments/run-java.sh" ]
\ No newline at end of file
diff --git a/api-server/target/generated-sources/openapi/src/main/docker/Dockerfile.native b/api-server/target/generated-sources/openapi/src/main/docker/Dockerfile.native
new file mode 100644
index 0000000..1a46ca0
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/docker/Dockerfile.native
@@ -0,0 +1,22 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode
+#
+# Before building the docker image run:
+#
+# mvn package -Pnative -Dquarkus.native.container-build=true
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native -t quarkus/openapi-jaxrs-client .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/openapi-jaxrs-client
+#
+###
+FROM registry.access.redhat.com/ubi8/ubi-minimal
+WORKDIR /work/
+COPY target/*-runner /work/application
+RUN chmod 775 /work
+EXPOSE 8080
+CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
\ No newline at end of file
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/RestResourceRoot.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/RestResourceRoot.java
new file mode 100644
index 0000000..d213a41
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/RestResourceRoot.java
@@ -0,0 +1,5 @@
+package com.repoachiever;
+
+public class RestResourceRoot {
+ public static final String APPLICATION_PATH = "";
+}
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/ContentResourceApi.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/ContentResourceApi.java
new file mode 100644
index 0000000..d67cf06
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/ContentResourceApi.java
@@ -0,0 +1,49 @@
+package com.repoachiever.api;
+
+import com.repoachiever.model.ContentApplication;
+import com.repoachiever.model.ContentCleanup;
+import com.repoachiever.model.ContentRetrievalApplication;
+import com.repoachiever.model.ContentRetrievalResult;
+import com.repoachiever.model.ContentWithdrawal;
+import java.io.File;
+
+import jakarta.ws.rs.*;
+import jakarta.ws.rs.core.Response;
+
+
+
+import java.io.InputStream;
+import java.util.Map;
+import java.util.List;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+@Path("/v1/content")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")
+public interface ContentResourceApi {
+
+ @POST
+ @Path("/apply")
+ @Consumes({ "application/json" })
+ void v1ContentApplyPost(@Valid @NotNull ContentApplication contentApplication);
+
+ @POST
+ @Path("/clean")
+ @Consumes({ "application/json" })
+ void v1ContentCleanPost(@Valid @NotNull ContentCleanup contentCleanup);
+
+ @GET
+ @Path("/download")
+ @Produces({ "application/octet-stream" })
+ File v1ContentDownloadGet(@QueryParam("location") String location);
+
+ @POST
+ @Consumes({ "application/json" })
+ @Produces({ "application/json" })
+ ContentRetrievalResult v1ContentPost(@Valid @NotNull ContentRetrievalApplication contentRetrievalApplication);
+
+ @DELETE
+ @Path("/withdraw")
+ @Consumes({ "application/json" })
+ void v1ContentWithdrawDelete(@Valid @NotNull ContentWithdrawal contentWithdrawal);
+}
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/HealthResourceApi.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/HealthResourceApi.java
new file mode 100644
index 0000000..803db5f
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/HealthResourceApi.java
@@ -0,0 +1,32 @@
+package com.repoachiever.api;
+
+import com.repoachiever.model.HealthCheckResult;
+import com.repoachiever.model.ReadinessCheckApplication;
+import com.repoachiever.model.ReadinessCheckResult;
+
+import jakarta.ws.rs.*;
+import jakarta.ws.rs.core.Response;
+
+
+
+import java.io.InputStream;
+import java.util.Map;
+import java.util.List;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+@Path("/v1")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")
+public interface HealthResourceApi {
+
+ @GET
+ @Path("/health")
+ @Produces({ "application/json" })
+ HealthCheckResult v1HealthGet();
+
+ @POST
+ @Path("/readiness")
+ @Consumes({ "application/json" })
+ @Produces({ "application/json" })
+ ReadinessCheckResult v1ReadinessPost(@Valid @NotNull ReadinessCheckApplication readinessCheckApplication);
+}
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/InfoResourceApi.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/InfoResourceApi.java
new file mode 100644
index 0000000..14be2b6
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/InfoResourceApi.java
@@ -0,0 +1,35 @@
+package com.repoachiever.api;
+
+import com.repoachiever.model.ClusterInfoUnit;
+import com.repoachiever.model.VersionInfoResult;
+
+import jakarta.ws.rs.*;
+import jakarta.ws.rs.core.Response;
+
+
+
+import java.io.InputStream;
+import java.util.Map;
+import java.util.List;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+@Path("/v1/info")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")
+public interface InfoResourceApi {
+
+ @GET
+ @Path("/cluster")
+ @Produces({ "application/json" })
+ List v1InfoClusterGet();
+
+ @GET
+ @Path("/telemetry")
+ @Produces({ "text/plain" })
+ String v1InfoTelemetryGet();
+
+ @GET
+ @Path("/version")
+ @Produces({ "application/json" })
+ VersionInfoResult v1InfoVersionGet();
+}
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/StateResourceApi.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/StateResourceApi.java
new file mode 100644
index 0000000..ed43a92
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/StateResourceApi.java
@@ -0,0 +1,25 @@
+package com.repoachiever.api;
+
+import com.repoachiever.model.ContentStateApplication;
+import com.repoachiever.model.ContentStateApplicationResult;
+
+import jakarta.ws.rs.*;
+import jakarta.ws.rs.core.Response;
+
+
+
+import java.io.InputStream;
+import java.util.Map;
+import java.util.List;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+@Path("/v1/state/content")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")
+public interface StateResourceApi {
+
+ @POST
+ @Consumes({ "application/json" })
+ @Produces({ "application/json" })
+ ContentStateApplicationResult v1StateContentPost(@Valid @NotNull ContentStateApplication contentStateApplication);
+}
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ClusterInfoUnit.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ClusterInfoUnit.java
new file mode 100644
index 0000000..da62edc
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ClusterInfoUnit.java
@@ -0,0 +1,123 @@
+package com.repoachiever.model;
+
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("ClusterInfoUnit")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class ClusterInfoUnit implements Serializable {
+ private @Valid String name;
+ private @Valid Boolean health;
+ private @Valid Integer workers;
+
+ /**
+ **/
+ public ClusterInfoUnit name(String name) {
+ this.name = name;
+ return this;
+ }
+
+
+ @JsonProperty("name")
+ @NotNull
+ public String getName() {
+ return name;
+ }
+
+ @JsonProperty("name")
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ **/
+ public ClusterInfoUnit health(Boolean health) {
+ this.health = health;
+ return this;
+ }
+
+
+ @JsonProperty("health")
+ public Boolean getHealth() {
+ return health;
+ }
+
+ @JsonProperty("health")
+ public void setHealth(Boolean health) {
+ this.health = health;
+ }
+
+ /**
+ **/
+ public ClusterInfoUnit workers(Integer workers) {
+ this.workers = workers;
+ return this;
+ }
+
+
+ @JsonProperty("workers")
+ public Integer getWorkers() {
+ return workers;
+ }
+
+ @JsonProperty("workers")
+ public void setWorkers(Integer workers) {
+ this.workers = workers;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ClusterInfoUnit clusterInfoUnit = (ClusterInfoUnit) o;
+ return Objects.equals(this.name, clusterInfoUnit.name) &&
+ Objects.equals(this.health, clusterInfoUnit.health) &&
+ Objects.equals(this.workers, clusterInfoUnit.workers);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, health, workers);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class ClusterInfoUnit {\n");
+
+ sb.append(" name: ").append(toIndentedString(name)).append("\n");
+ sb.append(" health: ").append(toIndentedString(health)).append("\n");
+ sb.append(" workers: ").append(toIndentedString(workers)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentApplication.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentApplication.java
new file mode 100644
index 0000000..dc4b33e
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentApplication.java
@@ -0,0 +1,145 @@
+package com.repoachiever.model;
+
+import com.repoachiever.model.CredentialsFieldsFull;
+import com.repoachiever.model.Provider;
+import java.util.ArrayList;
+import java.util.List;
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("ContentApplication")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class ContentApplication implements Serializable {
+ private @Valid List locations = new ArrayList<>();
+ private @Valid Provider provider;
+ private @Valid CredentialsFieldsFull credentials;
+
+ /**
+ **/
+ public ContentApplication locations(List locations) {
+ this.locations = locations;
+ return this;
+ }
+
+
+ @JsonProperty("locations")
+ @NotNull
+ public List getLocations() {
+ return locations;
+ }
+
+ @JsonProperty("locations")
+ public void setLocations(List locations) {
+ this.locations = locations;
+ }
+
+ public ContentApplication addLocationsItem(String locationsItem) {
+ if (this.locations == null) {
+ this.locations = new ArrayList<>();
+ }
+
+ this.locations.add(locationsItem);
+ return this;
+ }
+
+ public ContentApplication removeLocationsItem(String locationsItem) {
+ if (locationsItem != null && this.locations != null) {
+ this.locations.remove(locationsItem);
+ }
+
+ return this;
+ }
+ /**
+ **/
+ public ContentApplication provider(Provider provider) {
+ this.provider = provider;
+ return this;
+ }
+
+
+ @JsonProperty("provider")
+ @NotNull
+ public Provider getProvider() {
+ return provider;
+ }
+
+ @JsonProperty("provider")
+ public void setProvider(Provider provider) {
+ this.provider = provider;
+ }
+
+ /**
+ **/
+ public ContentApplication credentials(CredentialsFieldsFull credentials) {
+ this.credentials = credentials;
+ return this;
+ }
+
+
+ @JsonProperty("credentials")
+ @NotNull
+ public CredentialsFieldsFull getCredentials() {
+ return credentials;
+ }
+
+ @JsonProperty("credentials")
+ public void setCredentials(CredentialsFieldsFull credentials) {
+ this.credentials = credentials;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ContentApplication contentApplication = (ContentApplication) o;
+ return Objects.equals(this.locations, contentApplication.locations) &&
+ Objects.equals(this.provider, contentApplication.provider) &&
+ Objects.equals(this.credentials, contentApplication.credentials);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(locations, provider, credentials);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class ContentApplication {\n");
+
+ sb.append(" locations: ").append(toIndentedString(locations)).append("\n");
+ sb.append(" provider: ").append(toIndentedString(provider)).append("\n");
+ sb.append(" credentials: ").append(toIndentedString(credentials)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentCleanup.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentCleanup.java
new file mode 100644
index 0000000..2970568
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentCleanup.java
@@ -0,0 +1,82 @@
+package com.repoachiever.model;
+
+import com.repoachiever.model.CredentialsFieldsFull;
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("ContentCleanup")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class ContentCleanup implements Serializable {
+ private @Valid CredentialsFieldsFull credentials;
+
+ /**
+ **/
+ public ContentCleanup credentials(CredentialsFieldsFull credentials) {
+ this.credentials = credentials;
+ return this;
+ }
+
+
+ @JsonProperty("credentials")
+ @NotNull
+ public CredentialsFieldsFull getCredentials() {
+ return credentials;
+ }
+
+ @JsonProperty("credentials")
+ public void setCredentials(CredentialsFieldsFull credentials) {
+ this.credentials = credentials;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ContentCleanup contentCleanup = (ContentCleanup) o;
+ return Objects.equals(this.credentials, contentCleanup.credentials);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(credentials);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class ContentCleanup {\n");
+
+ sb.append(" credentials: ").append(toIndentedString(credentials)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentRetrievalApplication.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentRetrievalApplication.java
new file mode 100644
index 0000000..1c1dcfa
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentRetrievalApplication.java
@@ -0,0 +1,105 @@
+package com.repoachiever.model;
+
+import com.repoachiever.model.CredentialsFieldsFull;
+import com.repoachiever.model.Provider;
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("ContentRetrievalApplication")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class ContentRetrievalApplication implements Serializable {
+ private @Valid Provider provider;
+ private @Valid CredentialsFieldsFull credentials;
+
+ /**
+ **/
+ public ContentRetrievalApplication provider(Provider provider) {
+ this.provider = provider;
+ return this;
+ }
+
+
+ @JsonProperty("provider")
+ @NotNull
+ public Provider getProvider() {
+ return provider;
+ }
+
+ @JsonProperty("provider")
+ public void setProvider(Provider provider) {
+ this.provider = provider;
+ }
+
+ /**
+ **/
+ public ContentRetrievalApplication credentials(CredentialsFieldsFull credentials) {
+ this.credentials = credentials;
+ return this;
+ }
+
+
+ @JsonProperty("credentials")
+ @NotNull
+ public CredentialsFieldsFull getCredentials() {
+ return credentials;
+ }
+
+ @JsonProperty("credentials")
+ public void setCredentials(CredentialsFieldsFull credentials) {
+ this.credentials = credentials;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ContentRetrievalApplication contentRetrievalApplication = (ContentRetrievalApplication) o;
+ return Objects.equals(this.provider, contentRetrievalApplication.provider) &&
+ Objects.equals(this.credentials, contentRetrievalApplication.credentials);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(provider, credentials);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class ContentRetrievalApplication {\n");
+
+ sb.append(" provider: ").append(toIndentedString(provider)).append("\n");
+ sb.append(" credentials: ").append(toIndentedString(credentials)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentRetrievalResult.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentRetrievalResult.java
new file mode 100644
index 0000000..17cf3d5
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentRetrievalResult.java
@@ -0,0 +1,99 @@
+package com.repoachiever.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("ContentRetrievalResult")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class ContentRetrievalResult implements Serializable {
+ private @Valid List locations = new ArrayList<>();
+
+ /**
+ **/
+ public ContentRetrievalResult locations(List locations) {
+ this.locations = locations;
+ return this;
+ }
+
+
+ @JsonProperty("locations")
+ @NotNull
+ public List getLocations() {
+ return locations;
+ }
+
+ @JsonProperty("locations")
+ public void setLocations(List locations) {
+ this.locations = locations;
+ }
+
+ public ContentRetrievalResult addLocationsItem(String locationsItem) {
+ if (this.locations == null) {
+ this.locations = new ArrayList<>();
+ }
+
+ this.locations.add(locationsItem);
+ return this;
+ }
+
+ public ContentRetrievalResult removeLocationsItem(String locationsItem) {
+ if (locationsItem != null && this.locations != null) {
+ this.locations.remove(locationsItem);
+ }
+
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ContentRetrievalResult contentRetrievalResult = (ContentRetrievalResult) o;
+ return Objects.equals(this.locations, contentRetrievalResult.locations);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(locations);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class ContentRetrievalResult {\n");
+
+ sb.append(" locations: ").append(toIndentedString(locations)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentStateApplication.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentStateApplication.java
new file mode 100644
index 0000000..5e5cbc0
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentStateApplication.java
@@ -0,0 +1,105 @@
+package com.repoachiever.model;
+
+import com.repoachiever.model.CredentialsFieldsFull;
+import com.repoachiever.model.Provider;
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("ContentStateApplication")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class ContentStateApplication implements Serializable {
+ private @Valid Provider provider;
+ private @Valid CredentialsFieldsFull credentials;
+
+ /**
+ **/
+ public ContentStateApplication provider(Provider provider) {
+ this.provider = provider;
+ return this;
+ }
+
+
+ @JsonProperty("provider")
+ @NotNull
+ public Provider getProvider() {
+ return provider;
+ }
+
+ @JsonProperty("provider")
+ public void setProvider(Provider provider) {
+ this.provider = provider;
+ }
+
+ /**
+ **/
+ public ContentStateApplication credentials(CredentialsFieldsFull credentials) {
+ this.credentials = credentials;
+ return this;
+ }
+
+
+ @JsonProperty("credentials")
+ @NotNull
+ public CredentialsFieldsFull getCredentials() {
+ return credentials;
+ }
+
+ @JsonProperty("credentials")
+ public void setCredentials(CredentialsFieldsFull credentials) {
+ this.credentials = credentials;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ContentStateApplication contentStateApplication = (ContentStateApplication) o;
+ return Objects.equals(this.provider, contentStateApplication.provider) &&
+ Objects.equals(this.credentials, contentStateApplication.credentials);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(provider, credentials);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class ContentStateApplication {\n");
+
+ sb.append(" provider: ").append(toIndentedString(provider)).append("\n");
+ sb.append(" credentials: ").append(toIndentedString(credentials)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentStateApplicationResult.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentStateApplicationResult.java
new file mode 100644
index 0000000..94f9f7d
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentStateApplicationResult.java
@@ -0,0 +1,81 @@
+package com.repoachiever.model;
+
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("ContentStateApplicationResult")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class ContentStateApplicationResult implements Serializable {
+ private @Valid String hash;
+
+ /**
+ **/
+ public ContentStateApplicationResult hash(String hash) {
+ this.hash = hash;
+ return this;
+ }
+
+
+ @JsonProperty("hash")
+ @NotNull
+ public String getHash() {
+ return hash;
+ }
+
+ @JsonProperty("hash")
+ public void setHash(String hash) {
+ this.hash = hash;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ContentStateApplicationResult contentStateApplicationResult = (ContentStateApplicationResult) o;
+ return Objects.equals(this.hash, contentStateApplicationResult.hash);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(hash);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class ContentStateApplicationResult {\n");
+
+ sb.append(" hash: ").append(toIndentedString(hash)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentWithdrawal.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentWithdrawal.java
new file mode 100644
index 0000000..af1c0a7
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentWithdrawal.java
@@ -0,0 +1,105 @@
+package com.repoachiever.model;
+
+import com.repoachiever.model.CredentialsFieldsFull;
+import com.repoachiever.model.Provider;
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("ContentWithdrawal")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class ContentWithdrawal implements Serializable {
+ private @Valid Provider provider;
+ private @Valid CredentialsFieldsFull credentials;
+
+ /**
+ **/
+ public ContentWithdrawal provider(Provider provider) {
+ this.provider = provider;
+ return this;
+ }
+
+
+ @JsonProperty("provider")
+ @NotNull
+ public Provider getProvider() {
+ return provider;
+ }
+
+ @JsonProperty("provider")
+ public void setProvider(Provider provider) {
+ this.provider = provider;
+ }
+
+ /**
+ **/
+ public ContentWithdrawal credentials(CredentialsFieldsFull credentials) {
+ this.credentials = credentials;
+ return this;
+ }
+
+
+ @JsonProperty("credentials")
+ @NotNull
+ public CredentialsFieldsFull getCredentials() {
+ return credentials;
+ }
+
+ @JsonProperty("credentials")
+ public void setCredentials(CredentialsFieldsFull credentials) {
+ this.credentials = credentials;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ContentWithdrawal contentWithdrawal = (ContentWithdrawal) o;
+ return Objects.equals(this.provider, contentWithdrawal.provider) &&
+ Objects.equals(this.credentials, contentWithdrawal.credentials);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(provider, credentials);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class ContentWithdrawal {\n");
+
+ sb.append(" provider: ").append(toIndentedString(provider)).append("\n");
+ sb.append(" credentials: ").append(toIndentedString(credentials)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/CredentialsFieldsExternal.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/CredentialsFieldsExternal.java
new file mode 100644
index 0000000..45dbb53
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/CredentialsFieldsExternal.java
@@ -0,0 +1,82 @@
+package com.repoachiever.model;
+
+import com.repoachiever.model.GitGitHubCredentials;
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("CredentialsFieldsExternal")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class CredentialsFieldsExternal implements Serializable {
+ private @Valid String token;
+
+ /**
+ **/
+ public CredentialsFieldsExternal token(String token) {
+ this.token = token;
+ return this;
+ }
+
+
+ @JsonProperty("token")
+ @NotNull
+ public String getToken() {
+ return token;
+ }
+
+ @JsonProperty("token")
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CredentialsFieldsExternal credentialsFieldsExternal = (CredentialsFieldsExternal) o;
+ return Objects.equals(this.token, credentialsFieldsExternal.token);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(token);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class CredentialsFieldsExternal {\n");
+
+ sb.append(" token: ").append(toIndentedString(token)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/CredentialsFieldsFull.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/CredentialsFieldsFull.java
new file mode 100644
index 0000000..b054b75
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/CredentialsFieldsFull.java
@@ -0,0 +1,104 @@
+package com.repoachiever.model;
+
+import com.repoachiever.model.CredentialsFieldsExternal;
+import com.repoachiever.model.CredentialsFieldsInternal;
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("CredentialsFieldsFull")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class CredentialsFieldsFull implements Serializable {
+ private @Valid CredentialsFieldsInternal internal;
+ private @Valid CredentialsFieldsExternal external;
+
+ /**
+ **/
+ public CredentialsFieldsFull internal(CredentialsFieldsInternal internal) {
+ this.internal = internal;
+ return this;
+ }
+
+
+ @JsonProperty("internal")
+ @NotNull
+ public CredentialsFieldsInternal getInternal() {
+ return internal;
+ }
+
+ @JsonProperty("internal")
+ public void setInternal(CredentialsFieldsInternal internal) {
+ this.internal = internal;
+ }
+
+ /**
+ **/
+ public CredentialsFieldsFull external(CredentialsFieldsExternal external) {
+ this.external = external;
+ return this;
+ }
+
+
+ @JsonProperty("external")
+ public CredentialsFieldsExternal getExternal() {
+ return external;
+ }
+
+ @JsonProperty("external")
+ public void setExternal(CredentialsFieldsExternal external) {
+ this.external = external;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CredentialsFieldsFull credentialsFieldsFull = (CredentialsFieldsFull) o;
+ return Objects.equals(this.internal, credentialsFieldsFull.internal) &&
+ Objects.equals(this.external, credentialsFieldsFull.external);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(internal, external);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class CredentialsFieldsFull {\n");
+
+ sb.append(" internal: ").append(toIndentedString(internal)).append("\n");
+ sb.append(" external: ").append(toIndentedString(external)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/CredentialsFieldsInternal.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/CredentialsFieldsInternal.java
new file mode 100644
index 0000000..d49c4a7
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/CredentialsFieldsInternal.java
@@ -0,0 +1,80 @@
+package com.repoachiever.model;
+
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("CredentialsFieldsInternal")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class CredentialsFieldsInternal implements Serializable {
+ private @Valid Integer id;
+
+ /**
+ **/
+ public CredentialsFieldsInternal id(Integer id) {
+ this.id = id;
+ return this;
+ }
+
+
+ @JsonProperty("id")
+ public Integer getId() {
+ return id;
+ }
+
+ @JsonProperty("id")
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CredentialsFieldsInternal credentialsFieldsInternal = (CredentialsFieldsInternal) o;
+ return Objects.equals(this.id, credentialsFieldsInternal.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class CredentialsFieldsInternal {\n");
+
+ sb.append(" id: ").append(toIndentedString(id)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/GitGitHubCredentials.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/GitGitHubCredentials.java
new file mode 100644
index 0000000..ea5b5d0
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/GitGitHubCredentials.java
@@ -0,0 +1,81 @@
+package com.repoachiever.model;
+
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("GitGitHubCredentials")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class GitGitHubCredentials implements Serializable {
+ private @Valid String token;
+
+ /**
+ **/
+ public GitGitHubCredentials token(String token) {
+ this.token = token;
+ return this;
+ }
+
+
+ @JsonProperty("token")
+ @NotNull
+ public String getToken() {
+ return token;
+ }
+
+ @JsonProperty("token")
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ GitGitHubCredentials gitGitHubCredentials = (GitGitHubCredentials) o;
+ return Objects.equals(this.token, gitGitHubCredentials.token);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(token);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class GitGitHubCredentials {\n");
+
+ sb.append(" token: ").append(toIndentedString(token)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/HealthCheckResult.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/HealthCheckResult.java
new file mode 100644
index 0000000..5092b48
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/HealthCheckResult.java
@@ -0,0 +1,123 @@
+package com.repoachiever.model;
+
+import com.repoachiever.model.HealthCheckStatus;
+import com.repoachiever.model.HealthCheckUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("HealthCheckResult")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class HealthCheckResult implements Serializable {
+ private @Valid HealthCheckStatus status;
+ private @Valid List checks = new ArrayList<>();
+
+ /**
+ **/
+ public HealthCheckResult status(HealthCheckStatus status) {
+ this.status = status;
+ return this;
+ }
+
+
+ @JsonProperty("status")
+ @NotNull
+ public HealthCheckStatus getStatus() {
+ return status;
+ }
+
+ @JsonProperty("status")
+ public void setStatus(HealthCheckStatus status) {
+ this.status = status;
+ }
+
+ /**
+ **/
+ public HealthCheckResult checks(List checks) {
+ this.checks = checks;
+ return this;
+ }
+
+
+ @JsonProperty("checks")
+ @NotNull
+ public List getChecks() {
+ return checks;
+ }
+
+ @JsonProperty("checks")
+ public void setChecks(List checks) {
+ this.checks = checks;
+ }
+
+ public HealthCheckResult addChecksItem(HealthCheckUnit checksItem) {
+ if (this.checks == null) {
+ this.checks = new ArrayList<>();
+ }
+
+ this.checks.add(checksItem);
+ return this;
+ }
+
+ public HealthCheckResult removeChecksItem(HealthCheckUnit checksItem) {
+ if (checksItem != null && this.checks != null) {
+ this.checks.remove(checksItem);
+ }
+
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ HealthCheckResult healthCheckResult = (HealthCheckResult) o;
+ return Objects.equals(this.status, healthCheckResult.status) &&
+ Objects.equals(this.checks, healthCheckResult.checks);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(status, checks);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class HealthCheckResult {\n");
+
+ sb.append(" status: ").append(toIndentedString(status)).append("\n");
+ sb.append(" checks: ").append(toIndentedString(checks)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/HealthCheckStatus.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/HealthCheckStatus.java
new file mode 100644
index 0000000..c4f7833
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/HealthCheckStatus.java
@@ -0,0 +1,57 @@
+package com.repoachiever.model;
+
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * Gets or Sets HealthCheckStatus
+ */
+public enum HealthCheckStatus {
+
+ UP("UP"),
+
+ DOWN("DOWN");
+
+ private String value;
+
+ HealthCheckStatus(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Convert a String into String, as specified in the
+ * See JAX RS 2.0 Specification, section 3.2, p. 12
+ */
+ public static HealthCheckStatus fromString(String s) {
+ for (HealthCheckStatus b : HealthCheckStatus.values()) {
+ // using Objects.toString() to be safe if value type non-object type
+ // because types like 'int' etc. will be auto-boxed
+ if (java.util.Objects.toString(b.value).equals(s)) {
+ return b;
+ }
+ }
+ throw new IllegalArgumentException("Unexpected string value '" + s + "'");
+ }
+
+ @Override
+ @JsonValue
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+ @JsonCreator
+ public static HealthCheckStatus fromValue(String value) {
+ for (HealthCheckStatus b : HealthCheckStatus.values()) {
+ if (b.value.equals(value)) {
+ return b;
+ }
+ }
+ throw new IllegalArgumentException("Unexpected value '" + value + "'");
+ }
+}
+
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/HealthCheckUnit.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/HealthCheckUnit.java
new file mode 100644
index 0000000..0188c87
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/HealthCheckUnit.java
@@ -0,0 +1,104 @@
+package com.repoachiever.model;
+
+import com.repoachiever.model.HealthCheckStatus;
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("HealthCheckUnit")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class HealthCheckUnit implements Serializable {
+ private @Valid String name;
+ private @Valid HealthCheckStatus status;
+
+ /**
+ **/
+ public HealthCheckUnit name(String name) {
+ this.name = name;
+ return this;
+ }
+
+
+ @JsonProperty("name")
+ @NotNull
+ public String getName() {
+ return name;
+ }
+
+ @JsonProperty("name")
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ **/
+ public HealthCheckUnit status(HealthCheckStatus status) {
+ this.status = status;
+ return this;
+ }
+
+
+ @JsonProperty("status")
+ @NotNull
+ public HealthCheckStatus getStatus() {
+ return status;
+ }
+
+ @JsonProperty("status")
+ public void setStatus(HealthCheckStatus status) {
+ this.status = status;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ HealthCheckUnit healthCheckUnit = (HealthCheckUnit) o;
+ return Objects.equals(this.name, healthCheckUnit.name) &&
+ Objects.equals(this.status, healthCheckUnit.status);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, status);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class HealthCheckUnit {\n");
+
+ sb.append(" name: ").append(toIndentedString(name)).append("\n");
+ sb.append(" status: ").append(toIndentedString(status)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/Provider.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/Provider.java
new file mode 100644
index 0000000..4f6b4a0
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/Provider.java
@@ -0,0 +1,57 @@
+package com.repoachiever.model;
+
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * Gets or Sets Provider
+ */
+public enum Provider {
+
+ LOCAL("git-local"),
+
+ GITHUB("git-github");
+
+ private String value;
+
+ Provider(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Convert a String into String, as specified in the
+ * See JAX RS 2.0 Specification, section 3.2, p. 12
+ */
+ public static Provider fromString(String s) {
+ for (Provider b : Provider.values()) {
+ // using Objects.toString() to be safe if value type non-object type
+ // because types like 'int' etc. will be auto-boxed
+ if (java.util.Objects.toString(b.value).equals(s)) {
+ return b;
+ }
+ }
+ throw new IllegalArgumentException("Unexpected string value '" + s + "'");
+ }
+
+ @Override
+ @JsonValue
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+ @JsonCreator
+ public static Provider fromValue(String value) {
+ for (Provider b : Provider.values()) {
+ if (b.value.equals(value)) {
+ return b;
+ }
+ }
+ throw new IllegalArgumentException("Unexpected value '" + value + "'");
+ }
+}
+
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckApplication.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckApplication.java
new file mode 100644
index 0000000..6fd2623
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckApplication.java
@@ -0,0 +1,80 @@
+package com.repoachiever.model;
+
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("ReadinessCheckApplication")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class ReadinessCheckApplication implements Serializable {
+ private @Valid Object test;
+
+ /**
+ **/
+ public ReadinessCheckApplication test(Object test) {
+ this.test = test;
+ return this;
+ }
+
+
+ @JsonProperty("test")
+ public Object getTest() {
+ return test;
+ }
+
+ @JsonProperty("test")
+ public void setTest(Object test) {
+ this.test = test;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ReadinessCheckApplication readinessCheckApplication = (ReadinessCheckApplication) o;
+ return Objects.equals(this.test, readinessCheckApplication.test);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(test);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class ReadinessCheckApplication {\n");
+
+ sb.append(" test: ").append(toIndentedString(test)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckResult.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckResult.java
new file mode 100644
index 0000000..4f35056
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckResult.java
@@ -0,0 +1,126 @@
+package com.repoachiever.model;
+
+import com.repoachiever.model.ReadinessCheckStatus;
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("ReadinessCheckResult")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class ReadinessCheckResult implements Serializable {
+ private @Valid String name;
+ private @Valid ReadinessCheckStatus status;
+ private @Valid Object data;
+
+ /**
+ **/
+ public ReadinessCheckResult name(String name) {
+ this.name = name;
+ return this;
+ }
+
+
+ @JsonProperty("name")
+ @NotNull
+ public String getName() {
+ return name;
+ }
+
+ @JsonProperty("name")
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ **/
+ public ReadinessCheckResult status(ReadinessCheckStatus status) {
+ this.status = status;
+ return this;
+ }
+
+
+ @JsonProperty("status")
+ @NotNull
+ public ReadinessCheckStatus getStatus() {
+ return status;
+ }
+
+ @JsonProperty("status")
+ public void setStatus(ReadinessCheckStatus status) {
+ this.status = status;
+ }
+
+ /**
+ **/
+ public ReadinessCheckResult data(Object data) {
+ this.data = data;
+ return this;
+ }
+
+
+ @JsonProperty("data")
+ @NotNull
+ public Object getData() {
+ return data;
+ }
+
+ @JsonProperty("data")
+ public void setData(Object data) {
+ this.data = data;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ReadinessCheckResult readinessCheckResult = (ReadinessCheckResult) o;
+ return Objects.equals(this.name, readinessCheckResult.name) &&
+ Objects.equals(this.status, readinessCheckResult.status) &&
+ Objects.equals(this.data, readinessCheckResult.data);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, status, data);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class ReadinessCheckResult {\n");
+
+ sb.append(" name: ").append(toIndentedString(name)).append("\n");
+ sb.append(" status: ").append(toIndentedString(status)).append("\n");
+ sb.append(" data: ").append(toIndentedString(data)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckStatus.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckStatus.java
new file mode 100644
index 0000000..7770414
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckStatus.java
@@ -0,0 +1,57 @@
+package com.repoachiever.model;
+
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * Gets or Sets ReadinessCheckStatus
+ */
+public enum ReadinessCheckStatus {
+
+ UP("UP"),
+
+ DOWN("DOWN");
+
+ private String value;
+
+ ReadinessCheckStatus(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Convert a String into String, as specified in the
+ * See JAX RS 2.0 Specification, section 3.2, p. 12
+ */
+ public static ReadinessCheckStatus fromString(String s) {
+ for (ReadinessCheckStatus b : ReadinessCheckStatus.values()) {
+ // using Objects.toString() to be safe if value type non-object type
+ // because types like 'int' etc. will be auto-boxed
+ if (java.util.Objects.toString(b.value).equals(s)) {
+ return b;
+ }
+ }
+ throw new IllegalArgumentException("Unexpected string value '" + s + "'");
+ }
+
+ @Override
+ @JsonValue
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+ @JsonCreator
+ public static ReadinessCheckStatus fromValue(String value) {
+ for (ReadinessCheckStatus b : ReadinessCheckStatus.values()) {
+ if (b.value.equals(value)) {
+ return b;
+ }
+ }
+ throw new IllegalArgumentException("Unexpected value '" + value + "'");
+ }
+}
+
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckUnit.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckUnit.java
new file mode 100644
index 0000000..6c94db2
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckUnit.java
@@ -0,0 +1,104 @@
+package com.repoachiever.model;
+
+import com.repoachiever.model.ReadinessCheckStatus;
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("ReadinessCheckUnit")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class ReadinessCheckUnit implements Serializable {
+ private @Valid String name;
+ private @Valid ReadinessCheckStatus status;
+
+ /**
+ **/
+ public ReadinessCheckUnit name(String name) {
+ this.name = name;
+ return this;
+ }
+
+
+ @JsonProperty("name")
+ @NotNull
+ public String getName() {
+ return name;
+ }
+
+ @JsonProperty("name")
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ **/
+ public ReadinessCheckUnit status(ReadinessCheckStatus status) {
+ this.status = status;
+ return this;
+ }
+
+
+ @JsonProperty("status")
+ @NotNull
+ public ReadinessCheckStatus getStatus() {
+ return status;
+ }
+
+ @JsonProperty("status")
+ public void setStatus(ReadinessCheckStatus status) {
+ this.status = status;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ReadinessCheckUnit readinessCheckUnit = (ReadinessCheckUnit) o;
+ return Objects.equals(this.name, readinessCheckUnit.name) &&
+ Objects.equals(this.status, readinessCheckUnit.status);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, status);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class ReadinessCheckUnit {\n");
+
+ sb.append(" name: ").append(toIndentedString(name)).append("\n");
+ sb.append(" status: ").append(toIndentedString(status)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/VersionExternalApiInfoResult.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/VersionExternalApiInfoResult.java
new file mode 100644
index 0000000..428db14
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/VersionExternalApiInfoResult.java
@@ -0,0 +1,103 @@
+package com.repoachiever.model;
+
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("VersionExternalApiInfoResult")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class VersionExternalApiInfoResult implements Serializable {
+ private @Valid String version;
+ private @Valid String hash;
+
+ /**
+ **/
+ public VersionExternalApiInfoResult version(String version) {
+ this.version = version;
+ return this;
+ }
+
+
+ @JsonProperty("version")
+ @NotNull
+ public String getVersion() {
+ return version;
+ }
+
+ @JsonProperty("version")
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ /**
+ **/
+ public VersionExternalApiInfoResult hash(String hash) {
+ this.hash = hash;
+ return this;
+ }
+
+
+ @JsonProperty("hash")
+ @NotNull
+ public String getHash() {
+ return hash;
+ }
+
+ @JsonProperty("hash")
+ public void setHash(String hash) {
+ this.hash = hash;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ VersionExternalApiInfoResult versionExternalApiInfoResult = (VersionExternalApiInfoResult) o;
+ return Objects.equals(this.version, versionExternalApiInfoResult.version) &&
+ Objects.equals(this.hash, versionExternalApiInfoResult.hash);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(version, hash);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class VersionExternalApiInfoResult {\n");
+
+ sb.append(" version: ").append(toIndentedString(version)).append("\n");
+ sb.append(" hash: ").append(toIndentedString(hash)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/VersionInfoResult.java b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/VersionInfoResult.java
new file mode 100644
index 0000000..ce5829e
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/VersionInfoResult.java
@@ -0,0 +1,81 @@
+package com.repoachiever.model;
+
+import com.repoachiever.model.VersionExternalApiInfoResult;
+import java.io.Serializable;
+import jakarta.validation.constraints.*;
+import jakarta.validation.Valid;
+
+import java.util.Objects;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+
+
+
+@JsonTypeName("VersionInfoResult")
+@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2024-05-12T16:25:41.890330+02:00[Europe/Warsaw]")@lombok.Data @lombok.NoArgsConstructor @lombok.AllArgsConstructor(staticName = "of")
+
+public class VersionInfoResult implements Serializable {
+ private @Valid VersionExternalApiInfoResult externalApi;
+
+ /**
+ **/
+ public VersionInfoResult externalApi(VersionExternalApiInfoResult externalApi) {
+ this.externalApi = externalApi;
+ return this;
+ }
+
+
+ @JsonProperty("externalApi")
+ public VersionExternalApiInfoResult getExternalApi() {
+ return externalApi;
+ }
+
+ @JsonProperty("externalApi")
+ public void setExternalApi(VersionExternalApiInfoResult externalApi) {
+ this.externalApi = externalApi;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ VersionInfoResult versionInfoResult = (VersionInfoResult) o;
+ return Objects.equals(this.externalApi, versionInfoResult.externalApi);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(externalApi);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class VersionInfoResult {\n");
+
+ sb.append(" externalApi: ").append(toIndentedString(externalApi)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+
+}
+
diff --git a/api-server/target/generated-sources/openapi/src/main/resources/META-INF/openapi.yaml b/api-server/target/generated-sources/openapi/src/main/resources/META-INF/openapi.yaml
new file mode 100644
index 0000000..1e89526
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/resources/META-INF/openapi.yaml
@@ -0,0 +1,465 @@
+openapi: 3.0.1
+info:
+ description: RepoAchiever API Server Open API documentation
+ title: OpenAPI document of RepoAchiever API Server
+ version: "1.0"
+servers:
+- url: /
+tags:
+- description: Contains all endpoints related to operations on processed content.
+ name: ContentResource
+- description: Contains all endpoints related to state processing.
+ name: StateResource
+- description: Contains all endpoints related to general info of API Server.
+ name: InfoResource
+- description: Contains all endpoints related to general API Server health information.
+ name: HealthResource
+paths:
+ /v1/content:
+ post:
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ContentRetrievalApplication'
+ description: Content retrieval application
+ required: true
+ responses:
+ "204":
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ContentRetrievalResult'
+ description: A list of all available content
+ tags:
+ - ContentResource
+ x-content-type: application/json
+ x-accepts: application/json
+ x-tags:
+ - tag: ContentResource
+ /v1/content/apply:
+ post:
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ContentApplication'
+ description: Content configuration application
+ required: true
+ responses:
+ "204":
+ description: Given content configuration was successfully applied
+ "400":
+ description: Given content configuration was not applied
+ tags:
+ - ContentResource
+ x-content-type: application/json
+ x-accepts: application/json
+ x-tags:
+ - tag: ContentResource
+ /v1/content/withdraw:
+ delete:
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ContentWithdrawal'
+ description: Content withdraw application. Does not remove persisted content.
+ required: true
+ responses:
+ "204":
+ description: Given content configuration was successfully withdrawn
+ "400":
+ description: Given content configuration was not withdrawn
+ tags:
+ - ContentResource
+ x-content-type: application/json
+ x-accepts: application/json
+ x-tags:
+ - tag: ContentResource
+ /v1/content/download:
+ get:
+ parameters:
+ - description: Name of content location to be downloaded
+ explode: true
+ in: query
+ name: location
+ required: false
+ schema:
+ type: string
+ style: form
+ responses:
+ "200":
+ content:
+ application/octet-stream:
+ schema:
+ format: binary
+ type: string
+ description: A content was successfully retrieved
+ tags:
+ - ContentResource
+ x-accepts: application/octet-stream
+ x-tags:
+ - tag: ContentResource
+ /v1/content/clean:
+ post:
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ContentCleanup'
+ description: Content configuration application
+ required: true
+ responses:
+ "201":
+ description: Content with the given configuration was successfully deleted
+ "400":
+ description: Content with the given configuration was not deleted
+ tags:
+ - ContentResource
+ x-content-type: application/json
+ x-accepts: application/json
+ x-tags:
+ - tag: ContentResource
+ /v1/state/content:
+ post:
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ContentStateApplication'
+ description: Given content state key
+ required: true
+ responses:
+ "201":
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ContentStateApplicationResult'
+ description: Content state hash is retrieved successfully
+ tags:
+ - StateResource
+ x-content-type: application/json
+ x-accepts: application/json
+ x-tags:
+ - tag: StateResource
+ /v1/info/version:
+ get:
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/VersionInfoResult'
+ description: General information about running API Server
+ tags:
+ - InfoResource
+ x-accepts: application/json
+ x-tags:
+ - tag: InfoResource
+ /v1/info/cluster:
+ get:
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ClusterInfoResult'
+ description: General information about running clusters
+ tags:
+ - InfoResource
+ x-accepts: application/json
+ x-tags:
+ - tag: InfoResource
+ /v1/info/telemetry:
+ get:
+ responses:
+ "200":
+ content:
+ text/plain:
+ schema:
+ type: string
+ description: A set of Prometheus samples used by Grafana instance
+ tags:
+ - InfoResource
+ x-accepts: text/plain
+ x-tags:
+ - tag: InfoResource
+ /v1/health:
+ get:
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/HealthCheckResult'
+ description: General health information about running API Server
+ tags:
+ - HealthResource
+ x-accepts: application/json
+ x-tags:
+ - tag: HealthResource
+ /v1/readiness:
+ post:
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ReadinessCheckApplication'
+ description: Check if API Server is ready to serve for the given user
+ required: true
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ReadinessCheckResult'
+ description: General health information about running API Server
+ tags:
+ - HealthResource
+ x-content-type: application/json
+ x-accepts: application/json
+ x-tags:
+ - tag: HealthResource
+components:
+ schemas:
+ Provider:
+ enum:
+ - git-local
+ - git-github
+ type: string
+ CredentialsFieldsFull:
+ example:
+ internal:
+ id: 0
+ external: null
+ properties:
+ internal:
+ $ref: '#/components/schemas/CredentialsFieldsInternal'
+ external:
+ $ref: '#/components/schemas/CredentialsFieldsExternal'
+ required:
+ - internal
+ CredentialsFieldsInternal:
+ example:
+ id: 0
+ properties:
+ id:
+ type: integer
+ CredentialsFieldsExternal:
+ anyOf:
+ - $ref: '#/components/schemas/GitGitHubCredentials'
+ GitGitHubCredentials:
+ properties:
+ token:
+ type: string
+ required:
+ - token
+ ContentRetrievalApplication:
+ example:
+ provider: null
+ credentials:
+ internal:
+ id: 0
+ external: null
+ properties:
+ provider:
+ $ref: '#/components/schemas/Provider'
+ credentials:
+ $ref: '#/components/schemas/CredentialsFieldsFull'
+ required:
+ - credentials
+ - provider
+ ContentRetrievalResult:
+ example:
+ locations:
+ - locations
+ - locations
+ properties:
+ locations:
+ items:
+ type: string
+ type: array
+ required:
+ - locations
+ ContentApplication:
+ example:
+ provider: null
+ credentials:
+ internal:
+ id: 0
+ external: null
+ locations:
+ - locations
+ - locations
+ properties:
+ locations:
+ items:
+ type: string
+ type: array
+ provider:
+ $ref: '#/components/schemas/Provider'
+ credentials:
+ $ref: '#/components/schemas/CredentialsFieldsFull'
+ required:
+ - credentials
+ - locations
+ - provider
+ ContentWithdrawal:
+ example:
+ provider: null
+ credentials:
+ internal:
+ id: 0
+ external: null
+ properties:
+ provider:
+ $ref: '#/components/schemas/Provider'
+ credentials:
+ $ref: '#/components/schemas/CredentialsFieldsFull'
+ required:
+ - credentials
+ - provider
+ ContentCleanup:
+ example:
+ credentials:
+ internal:
+ id: 0
+ external: null
+ properties:
+ credentials:
+ $ref: '#/components/schemas/CredentialsFieldsFull'
+ required:
+ - credentials
+ ContentStateApplication:
+ example:
+ provider: null
+ credentials:
+ internal:
+ id: 0
+ external: null
+ properties:
+ provider:
+ $ref: '#/components/schemas/Provider'
+ credentials:
+ $ref: '#/components/schemas/CredentialsFieldsFull'
+ required:
+ - credentials
+ - provider
+ ContentStateApplicationResult:
+ example:
+ hash: hash
+ properties:
+ hash:
+ type: string
+ required:
+ - hash
+ VersionInfoResult:
+ example:
+ externalApi:
+ version: version
+ hash: hash
+ properties:
+ externalApi:
+ $ref: '#/components/schemas/VersionExternalApiInfoResult'
+ VersionExternalApiInfoResult:
+ example:
+ version: version
+ hash: hash
+ properties:
+ version:
+ type: string
+ hash:
+ type: string
+ required:
+ - hash
+ - version
+ ClusterInfoResult:
+ items:
+ $ref: '#/components/schemas/ClusterInfoUnit'
+ type: array
+ ClusterInfoUnit:
+ example:
+ name: name
+ health: true
+ workers: 0
+ properties:
+ name:
+ type: string
+ health:
+ type: boolean
+ workers:
+ type: integer
+ required:
+ - name
+ HealthCheckResult:
+ example:
+ checks:
+ - name: name
+ status: null
+ - name: name
+ status: null
+ status: null
+ properties:
+ status:
+ $ref: '#/components/schemas/HealthCheckStatus'
+ checks:
+ items:
+ $ref: '#/components/schemas/HealthCheckUnit'
+ type: array
+ required:
+ - checks
+ - status
+ HealthCheckUnit:
+ example:
+ name: name
+ status: null
+ properties:
+ name:
+ type: string
+ status:
+ $ref: '#/components/schemas/HealthCheckStatus'
+ required:
+ - name
+ - status
+ HealthCheckStatus:
+ enum:
+ - UP
+ - DOWN
+ type: string
+ ReadinessCheckApplication:
+ example:
+ test: "{}"
+ properties:
+ test:
+ type: object
+ ReadinessCheckResult:
+ example:
+ data: "{}"
+ name: name
+ status: null
+ properties:
+ name:
+ type: string
+ status:
+ $ref: '#/components/schemas/ReadinessCheckStatus'
+ data:
+ type: object
+ required:
+ - data
+ - name
+ - status
+ ReadinessCheckUnit:
+ properties:
+ name:
+ type: string
+ status:
+ $ref: '#/components/schemas/ReadinessCheckStatus'
+ required:
+ - name
+ - status
+ ReadinessCheckStatus:
+ enum:
+ - UP
+ - DOWN
+ type: string
diff --git a/api-server/target/generated-sources/openapi/src/main/resources/application.properties b/api-server/target/generated-sources/openapi/src/main/resources/application.properties
new file mode 100644
index 0000000..83b16e9
--- /dev/null
+++ b/api-server/target/generated-sources/openapi/src/main/resources/application.properties
@@ -0,0 +1,5 @@
+# Configuration file
+# key = value
+
+mp.openapi.scan.disable=true
+
diff --git a/api-server/target/maven-archiver/pom.properties b/api-server/target/maven-archiver/pom.properties
new file mode 100644
index 0000000..b2fcf44
--- /dev/null
+++ b/api-server/target/maven-archiver/pom.properties
@@ -0,0 +1,4 @@
+#Created by Apache Maven 3.9.6
+artifactId=api-server
+groupId=com.repoachiever
+version=1.0-SNAPSHOT
diff --git a/api-server/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/api-server/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
new file mode 100644
index 0000000..63ea8a0
--- /dev/null
+++ b/api-server/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
@@ -0,0 +1,210 @@
+com/repoachiever/model/GitGitHubCredentials.class
+com/repoachiever/service/command/cluster/deploy/ClusterDeployCommandService.class
+com/repoachiever/repository/facade/RepositoryFacade.class
+com/repoachiever/model/ContentCleanup.class
+com/repoachiever/exception/ContentFileNotFoundException.class
+com/repoachiever/dto/RepositoryContentUnitDto.class
+com/repoachiever/entity/common/ClusterContextEntity$Metadata.class
+com/repoachiever/api/InfoResourceApi.class
+com/repoachiever/service/integration/communication/cluster/healthcheck/ClusterHealthCheckCommunicationService.class
+com/repoachiever/service/workspace/WorkspaceService$1.class
+com/repoachiever/exception/RepositoryContentApplicationFailureException.class
+com/repoachiever/model/HealthCheckUnit.class
+com/repoachiever/dto/CommandExecutorOutputDto.class
+com/repoachiever/model/HealthCheckResult.class
+com/repoachiever/exception/CommandExecutorException.class
+com/repoachiever/exception/QueryExecutionFailureException.class
+com/repoachiever/entity/common/ClusterContextEntity$Resource$Cluster.class
+com/repoachiever/mapping/CredentialsFieldIsNotValidExceptionMapper.class
+com/repoachiever/service/cluster/resource/ClusterCommunicationResource.class
+com/repoachiever/model/ContentStateApplication.class
+com/repoachiever/exception/NodeExporterDeploymentFailureException.class
+com/repoachiever/resource/ContentResource.class
+com/repoachiever/exception/WorkspaceUnitDirectoryNotFoundException.class
+com/repoachiever/resource/HealthResource.class
+com/repoachiever/entity/common/ClusterContextEntity$Filter.class
+com/repoachiever/entity/common/ConfigEntity$Connection.class
+com/repoachiever/service/command/prometheus/common/PrometheusConfigurationHelper$3.class
+com/repoachiever/exception/DockerNetworkRemoveFailureException.class
+com/repoachiever/service/command/cluster/destroy/ClusterDestroyCommandService.class
+com/repoachiever/service/command/grafana/common/GrafanaConfigurationHelper$1.class
+com/repoachiever/entity/common/ConfigEntity$Diagnostics.class
+com/repoachiever/entity/repository/ProviderEntity.class
+com/repoachiever/entity/common/ConfigEntity.class
+com/repoachiever/service/integration/diagnostics/template/TemplateConfigService$3.class
+com/repoachiever/service/command/cluster/common/ClusterConfigurationHelper.class
+com/repoachiever/exception/ClusterApplicationTimeoutException.class
+com/repoachiever/converter/ContentCredentialsToClusterContextCredentialsConverter$1.class
+com/repoachiever/service/command/prometheus/common/PrometheusConfigurationHelper$2.class
+com/repoachiever/entity/common/ClusterContextEntity$Communication.class
+com/repoachiever/model/CredentialsFieldsFull.class
+com/repoachiever/exception/RepositoryOperationFailureException.class
+com/repoachiever/model/ReadinessCheckUnit.class
+com/repoachiever/entity/common/ClusterContextEntity$Content.class
+com/repoachiever/service/integration/diagnostics/template/TemplateConfigService$3$1.class
+com/repoachiever/service/integration/diagnostics/template/TemplateConfigService$2.class
+com/repoachiever/model/ContentRetrievalApplication.class
+com/repoachiever/api/ContentResourceApi.class
+com/repoachiever/exception/ContentFileWriteFailureException.class
+com/repoachiever/exception/QueryEmptyResultException.class
+com/repoachiever/entity/common/ClusterContextEntity$Resource.class
+com/repoachiever/service/integration/diagnostics/template/TemplateConfigService$1.class
+com/repoachiever/entity/common/ClusterContextEntity$Resource$Worker.class
+com/repoachiever/repository/common/RepositoryConfigurationHelper.class
+com/repoachiever/service/command/cluster/common/ClusterConfigurationHelper$1.class
+com/repoachiever/entity/common/ClusterContextEntity$Service$Provider.class
+com/repoachiever/entity/common/ConfigEntity$Diagnostics$Grafana.class
+com/repoachiever/exception/WorkspaceContentDirectoryCreationFailureException.class
+com/repoachiever/service/command/docker/network/remove/DockerNetworkRemoveCommandService.class
+com/repoachiever/model/HealthCheckStatus.class
+com/repoachiever/service/command/prometheus/PrometheusDeployCommandService$1.class
+com/repoachiever/service/config/ConfigService.class
+com/repoachiever/model/ReadinessCheckApplication.class
+com/repoachiever/model/ContentApplication.class
+com/repoachiever/converter/HealthCheckResponseToReadinessCheckResult.class
+com/repoachiever/exception/ClusterOperationFailureException.class
+com/repoachiever/service/command/docker/availability/DockerAvailabilityCheckCommandService.class
+com/repoachiever/service/integration/diagnostics/template/TemplateConfigService$3$2.class
+com/repoachiever/service/command/nodeexporter/common/NodeExporterConfigurationHelper$1.class
+com/repoachiever/model/Provider.class
+com/repoachiever/exception/MetadataFileNotFoundException.class
+com/repoachiever/service/integration/diagnostics/template/TemplateConfigService$2$1.class
+com/repoachiever/service/integration/communication/cluster/topology/ClusterTopologyCommunicationConfigService.class
+com/repoachiever/mapping/CredentialsAreNotValidExceptionMapper.class
+com/repoachiever/mapping/WorkspaceUnitDirectoryNotFoundExceptionMapper.class
+com/repoachiever/entity/common/ConfigEntity$Resource$Cluster.class
+com/repoachiever/entity/common/ConfigEntity$Database.class
+com/repoachiever/model/VersionExternalApiInfoResult.class
+com/repoachiever/exception/ClusterDestructionFailureException.class
+com/repoachiever/api/HealthResourceApi.class
+com/repoachiever/dto/ClusterAllocationDto.class
+com/repoachiever/converter/ClusterContextToJsonConverter.class
+com/repoachiever/service/vendor/VendorFacade$1.class
+com/repoachiever/exception/ContentApplicationRetrievalFailureException.class
+com/repoachiever/service/workspace/facade/WorkspaceFacade.class
+com/repoachiever/exception/CommunicationConfigurationFailureException.class
+com/repoachiever/entity/common/ClusterContextEntity$Service.class
+com/repoachiever/service/telemetry/TelemetryService.class
+com/repoachiever/service/command/docker/network/create/DockerNetworkCreateCommandService.class
+com/repoachiever/entity/common/ConfigEntity$Content.class
+com/repoachiever/service/integration/state/StateConfigService.class
+com/repoachiever/service/command/cluster/destroy/ClusterDestroyCommandService$1.class
+com/repoachiever/service/integration/diagnostics/template/TemplateConfigService.class
+com/repoachiever/exception/ClusterUnhealthyReapplicationFailureException.class
+com/repoachiever/service/command/grafana/GrafanaDeployCommandService$1.class
+com/repoachiever/exception/ConfigValidationException.class
+com/repoachiever/exception/CredentialsFieldIsNotValidException.class
+com/repoachiever/service/healthcheck/readiness/ReadinessCheckService.class
+com/repoachiever/exception/ApiServerInstanceIsAlreadyRunningException.class
+com/repoachiever/exception/RepositoryContentDestructionFailureException.class
+com/repoachiever/service/telemetry/binding/TelemetryBinding.class
+com/repoachiever/exception/DockerInspectRemovalFailureException.class
+com/repoachiever/mapping/ClusterApplicationFailureExceptionMapper.class
+com/repoachiever/resource/StateResource.class
+com/repoachiever/service/workspace/WorkspaceService.class
+com/repoachiever/service/command/grafana/common/GrafanaConfigurationHelper$3.class
+com/repoachiever/entity/common/MetadataFileEntity.class
+com/repoachiever/service/workspace/facade/WorkspaceFacade$1.class
+com/repoachiever/entity/common/ConfigEntity$Resource$Worker.class
+com/repoachiever/service/vendor/common/VendorConfigurationHelper.class
+com/repoachiever/service/integration/http/HttpServerConfigService.class
+com/repoachiever/exception/ClusterWithdrawalFailureException.class
+com/repoachiever/service/integration/properties/git/GitPropertiesConfigService.class
+com/repoachiever/converter/HealthCheckResponseToReadinessCheckResult$1.class
+com/repoachiever/repository/common/RepositoryConfigurationHelper$1.class
+com/repoachiever/exception/WorkspaceUnitDirectoryRemovalFailureException.class
+com/repoachiever/entity/common/ClusterContextEntity$Service$Credentials.class
+com/repoachiever/service/client/smallrye/ISmallRyeHealthCheckClientService.class
+com/repoachiever/exception/DiagnosticsTemplateProcessingFailureException.class
+com/repoachiever/exception/LocationsFieldIsNotValidException.class
+com/repoachiever/service/command/common/CommandConfigurationHelper.class
+com/repoachiever/mapping/ClusterWithdrawalFailureExceptionMapper.class
+com/repoachiever/service/command/docker/inspect/remove/DockerInspectRemoveCommandService$1.class
+com/repoachiever/resource/common/ResourceConfigurationHelper$1.class
+META-INF/panache-archive.marker
+com/repoachiever/model/ReadinessCheckResult.class
+com/repoachiever/service/config/ConfigService$1.class
+com/repoachiever/service/healthcheck/health/HealthCheckService.class
+com/repoachiever/service/integration/properties/general/GeneralPropertiesConfigService.class
+com/repoachiever/exception/CredentialsAreNotValidException.class
+com/repoachiever/entity/common/ConfigEntity$Communication.class
+com/repoachiever/service/command/docker/network/create/DockerNetworkCreateCommandService$1.class
+com/repoachiever/model/CredentialsFieldsInternal.class
+com/repoachiever/service/command/nodeexporter/common/NodeExporterConfigurationHelper.class
+META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat
+com/repoachiever/RestResourceRoot.class
+com/repoachiever/service/command/nodeexporter/NodeExporterDeployCommandService.class
+com/repoachiever/service/integration/communication/apiserver/ApiServerCommunicationConfigService.class
+com/repoachiever/entity/repository/ConfigEntity.class
+com/repoachiever/exception/DockerIsNotAvailableException.class
+com/repoachiever/service/command/nodeexporter/common/NodeExporterConfigurationHelper$2.class
+com/repoachiever/service/vendor/VendorFacade.class
+com/repoachiever/service/command/grafana/common/GrafanaConfigurationHelper.class
+com/repoachiever/exception/PrometheusDeploymentFailureException.class
+com/repoachiever/model/ClusterInfoUnit.class
+com/repoachiever/entity/repository/ContentEntity.class
+com/repoachiever/service/command/grafana/common/GrafanaConfigurationHelper$2.class
+com/repoachiever/service/command/grafana/GrafanaDeployCommandService.class
+com/repoachiever/mapping/RepositoryContentDestructionFailureExceptionMapper.class
+com/repoachiever/entity/common/PropertiesEntity.class
+com/repoachiever/model/ContentWithdrawal.class
+com/repoachiever/service/command/prometheus/common/PrometheusConfigurationHelper$1.class
+com/repoachiever/exception/WorkspaceUnitDirectoryPresentException.class
+com/repoachiever/service/integration/diagnostics/DiagnosticsConfigService.class
+com/repoachiever/entity/common/ConfigEntity$Resource.class
+com/repoachiever/exception/DockerNetworkCreateFailureException.class
+com/repoachiever/entity/common/ConfigEntity$Diagnostics$Prometheus.class
+com/repoachiever/service/command/nodeexporter/common/NodeExporterConfigurationHelper$3.class
+com/repoachiever/repository/ContentRepository.class
+com/repoachiever/model/ReadinessCheckStatus.class
+com/repoachiever/converter/ContentProviderToClusterContextProviderConverter.class
+com/repoachiever/service/cluster/common/ClusterConfigurationHelper.class
+com/repoachiever/repository/executor/RepositoryExecutor.class
+com/repoachiever/service/command/nodeexporter/NodeExporterDeployCommandService$1.class
+com/repoachiever/service/command/prometheus/common/PrometheusConfigurationHelper.class
+com/repoachiever/service/vendor/git/github/GitGitHubVendorService.class
+com/repoachiever/repository/ConfigRepository.class
+com/repoachiever/entity/repository/SecretEntity.class
+com/repoachiever/service/state/StateService.class
+com/repoachiever/model/VersionInfoResult.class
+com/repoachiever/mapping/RepositoryContentApplicationFailureExceptionMapper.class
+com/repoachiever/service/executor/CommandExecutorService.class
+com/repoachiever/converter/ContentCredentialsToClusterContextCredentialsConverter.class
+com/repoachiever/api/StateResourceApi.class
+com/repoachiever/service/integration/communication/registry/RegistryCommunicationConfigService.class
+com/repoachiever/model/CredentialsFieldsExternal.class
+com/repoachiever/exception/ContentFileRemovalFailureException.class
+com/repoachiever/exception/ClusterApplicationFailureException.class
+com/repoachiever/model/ContentStateApplicationResult.class
+com/repoachiever/service/command/docker/availability/DockerAvailabilityCheckCommandService$1.class
+com/repoachiever/entity/common/ConfigEntity$Diagnostics$Metrics.class
+com/repoachiever/model/ContentRetrievalResult.class
+com/repoachiever/service/command/prometheus/PrometheusDeployCommandService.class
+com/repoachiever/resource/common/ResourceConfigurationHelper.class
+com/repoachiever/resource/InfoResource.class
+com/repoachiever/exception/TelemetryOperationFailureException.class
+com/repoachiever/service/communication/cluster/IClusterCommunicationService.class
+com/repoachiever/resource/communication/ApiServerCommunicationResource.class
+com/repoachiever/logging/FatalAppender.class
+com/repoachiever/service/command/docker/network/remove/DockerNetworkRemoveCommandService$1.class
+com/repoachiever/exception/ClusterRecreationFailureException.class
+com/repoachiever/service/communication/common/CommunicationProviderConfigurationHelper.class
+com/repoachiever/repository/ProviderRepository.class
+com/repoachiever/service/integration/diagnostics/telemetry/TelemetryConfigService.class
+com/repoachiever/service/integration/diagnostics/template/TemplateConfigService$1$2.class
+com/repoachiever/exception/ClusterDeploymentFailureException.class
+com/repoachiever/exception/MetadataFileWriteFailureException.class
+com/repoachiever/exception/CredentialsConversionException.class
+com/repoachiever/service/cluster/ClusterService.class
+com/repoachiever/service/command/cluster/deploy/ClusterDeployCommandService$1.class
+com/repoachiever/exception/ClusterFullDestructionFailureException.class
+com/repoachiever/entity/common/ConfigEntity$Diagnostics$NodeExporter.class
+com/repoachiever/mapping/LocationsFieldIsNotValidExceptionMapper.class
+com/repoachiever/service/communication/apiserver/IApiServerCommunicationService.class
+com/repoachiever/service/client/github/IGitHubClientService.class
+com/repoachiever/exception/WorkspaceUnitDirectoryCreationFailureException.class
+com/repoachiever/service/cluster/facade/ClusterFacade.class
+com/repoachiever/service/command/docker/inspect/remove/DockerInspectRemoveCommandService.class
+com/repoachiever/entity/common/ClusterContextEntity.class
+com/repoachiever/repository/SecretRepository.class
+com/repoachiever/service/integration/diagnostics/template/TemplateConfigService$1$1.class
diff --git a/api-server/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/api-server/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
new file mode 100644
index 0000000..12ced00
--- /dev/null
+++ b/api-server/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
@@ -0,0 +1,151 @@
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/converter/HealthCheckResponseToReadinessCheckResult.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/DockerInspectRemovalFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/communication/apiserver/IApiServerCommunicationService.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/CredentialsFieldsExternal.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentWithdrawal.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/entity/repository/SecretEntity.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/command/prometheus/PrometheusDeployCommandService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/CredentialsConversionException.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckUnit.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/client/github/IGitHubClientService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/ClusterDeploymentFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/repository/ConfigRepository.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/HealthCheckStatus.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/mapping/RepositoryContentDestructionFailureExceptionMapper.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/integration/diagnostics/telemetry/TelemetryConfigService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/entity/repository/ConfigEntity.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/integration/communication/cluster/healthcheck/ClusterHealthCheckCommunicationService.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentCleanup.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/RepositoryOperationFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentApplication.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/ContentResourceApi.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/HealthCheckUnit.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/entity/repository/ProviderEntity.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/repository/ContentRepository.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/RepositoryContentDestructionFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/ContentApplicationRetrievalFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/executor/CommandExecutorService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/integration/properties/general/GeneralPropertiesConfigService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/QueryEmptyResultException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/integration/communication/apiserver/ApiServerCommunicationConfigService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/cluster/common/ClusterConfigurationHelper.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ClusterInfoUnit.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/QueryExecutionFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentRetrievalResult.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/resource/InfoResource.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/Provider.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/WorkspaceContentDirectoryCreationFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/state/StateService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/command/common/CommandConfigurationHelper.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/integration/properties/git/GitPropertiesConfigService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/PrometheusDeploymentFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/repository/executor/RepositoryExecutor.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/integration/diagnostics/DiagnosticsConfigService.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/InfoResourceApi.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/resource/communication/ApiServerCommunicationResource.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/entity/common/ConfigEntity.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryCreationFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/entity/common/MetadataFileEntity.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/RestResourceRoot.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/entity/repository/ContentEntity.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/repository/ProviderRepository.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/integration/communication/registry/RegistryCommunicationConfigService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/converter/ContentProviderToClusterContextProviderConverter.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/workspace/WorkspaceService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryNotFoundException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/NodeExporterDeploymentFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/DockerIsNotAvailableException.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentRetrievalApplication.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/CommunicationConfigurationFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/ContentFileNotFoundException.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/GitGitHubCredentials.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/resource/HealthResource.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/cluster/ClusterService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryRemovalFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/entity/common/PropertiesEntity.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/telemetry/TelemetryService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/ConfigValidationException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/ClusterUnhealthyReapplicationFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/resource/common/ResourceConfigurationHelper.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/mapping/CredentialsFieldIsNotValidExceptionMapper.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckApplication.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/command/docker/availability/DockerAvailabilityCheckCommandService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/RepositoryContentApplicationFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/converter/ClusterContextToJsonConverter.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/ClusterDestructionFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/vendor/common/VendorConfigurationHelper.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/MetadataFileWriteFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/mapping/LocationsFieldIsNotValidExceptionMapper.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/DiagnosticsTemplateProcessingFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/cluster/resource/ClusterCommunicationResource.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/mapping/RepositoryContentApplicationFailureExceptionMapper.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/integration/communication/cluster/topology/ClusterTopologyCommunicationConfigService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/WorkspaceUnitDirectoryPresentException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/repository/common/RepositoryConfigurationHelper.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/repository/facade/RepositoryFacade.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/integration/http/HttpServerConfigService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/command/cluster/deploy/ClusterDeployCommandService.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/HealthResourceApi.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/workspace/facade/WorkspaceFacade.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/CommandExecutorException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/communication/cluster/IClusterCommunicationService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/healthcheck/readiness/ReadinessCheckService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/command/docker/inspect/remove/DockerInspectRemoveCommandService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/mapping/ClusterWithdrawalFailureExceptionMapper.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/ClusterApplicationTimeoutException.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentStateApplication.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/repository/SecretRepository.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/integration/diagnostics/template/TemplateConfigService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/communication/common/CommunicationProviderConfigurationHelper.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/TelemetryOperationFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/ClusterRecreationFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/VersionInfoResult.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/LocationsFieldIsNotValidException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/ClusterWithdrawalFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/resource/StateResource.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/ContentFileRemovalFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/command/nodeexporter/common/NodeExporterConfigurationHelper.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/mapping/ClusterApplicationFailureExceptionMapper.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/dto/ClusterAllocationDto.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/client/smallrye/ISmallRyeHealthCheckClientService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/command/prometheus/common/PrometheusConfigurationHelper.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/cluster/facade/ClusterFacade.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/CredentialsFieldsFull.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/command/nodeexporter/NodeExporterDeployCommandService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/command/cluster/destroy/ClusterDestroyCommandService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/vendor/VendorFacade.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/CredentialsFieldIsNotValidException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/MetadataFileNotFoundException.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/VersionExternalApiInfoResult.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/dto/RepositoryContentUnitDto.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/healthcheck/health/HealthCheckService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/command/docker/network/create/DockerNetworkCreateCommandService.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckStatus.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/command/docker/network/remove/DockerNetworkRemoveCommandService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/command/grafana/GrafanaDeployCommandService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/ContentFileWriteFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/command/cluster/common/ClusterConfigurationHelper.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/mapping/WorkspaceUnitDirectoryNotFoundExceptionMapper.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/entity/common/ClusterContextEntity.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/config/ConfigService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/telemetry/binding/TelemetryBinding.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/ClusterFullDestructionFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/CredentialsFieldsInternal.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/ClusterOperationFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/DockerNetworkRemoveFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/dto/CommandExecutorOutputDto.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/integration/state/StateConfigService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/vendor/git/github/GitGitHubVendorService.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/DockerNetworkCreateFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/logging/FatalAppender.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/api/StateResourceApi.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/service/command/grafana/common/GrafanaConfigurationHelper.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/ApiServerInstanceIsAlreadyRunningException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/CredentialsAreNotValidException.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ReadinessCheckResult.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/ContentStateApplicationResult.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/exception/ClusterApplicationFailureException.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/converter/ContentCredentialsToClusterContextCredentialsConverter.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/resource/ContentResource.java
+/Volumes/Files/java/RepoAchiever/api-server/target/generated-sources/openapi/src/main/java/com/repoachiever/model/HealthCheckResult.java
+/Volumes/Files/java/RepoAchiever/api-server/src/main/java/com/repoachiever/mapping/CredentialsAreNotValidExceptionMapper.java
diff --git a/api-server/target/quarkus-app/quarkus-app-dependencies.txt b/api-server/target/quarkus-app/quarkus-app-dependencies.txt
new file mode 100644
index 0000000..036c801
--- /dev/null
+++ b/api-server/target/quarkus-app/quarkus-app-dependencies.txt
@@ -0,0 +1,293 @@
+Shell-Command-Executor-Lib:Shell-Command-Executor-Lib::jar:0.5.0-SNAPSHOST
+com.aayushatharva.brotli4j:brotli4j::jar:1.7.1
+com.fasterxml.jackson.core:jackson-annotations::jar:2.15.3
+com.fasterxml.jackson.core:jackson-core::jar:2.15.3
+com.fasterxml.jackson.core:jackson-databind::jar:2.15.3
+com.fasterxml.jackson.dataformat:jackson-dataformat-yaml::jar:2.15.3
+com.fasterxml.jackson.datatype:jackson-datatype-jdk8::jar:2.15.3
+com.fasterxml.jackson.datatype:jackson-datatype-jsr310::jar:2.15.3
+com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-base::jar:2.15.3
+com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-json-provider::jar:2.15.3
+com.fasterxml.jackson.module:jackson-module-jakarta-xmlbind-annotations::jar:2.15.3
+com.fasterxml.jackson.module:jackson-module-parameter-names::jar:2.15.3
+com.fasterxml:classmate::jar:1.5.1
+com.github.ben-manes.caffeine:caffeine::jar:3.1.8
+com.github.java-json-tools:btf::jar:1.3
+com.github.java-json-tools:jackson-coreutils::jar:2.0
+com.github.java-json-tools:json-patch::jar:1.13
+com.github.java-json-tools:msg-simple::jar:1.2
+com.google.errorprone:error_prone_annotations::jar:2.21.1
+com.ibm.async:asyncutil::jar:0.1.0
+com.opencsv:opencsv::jar:5.6
+com.sun.istack:istack-commons-runtime::jar:4.1.2
+commons-beanutils:commons-beanutils::jar:1.9.4
+commons-codec:commons-codec::jar:1.15
+commons-collections:commons-collections::jar:3.2.2
+commons-io:commons-io::jar:2.11.0
+commons-logging:commons-logging::jar:1.2
+io.agroal:agroal-api::jar:2.1
+io.agroal:agroal-narayana::jar:2.1
+io.agroal:agroal-pool::jar:2.1
+io.github.crac:org-crac::jar:0.1.3
+io.micrometer:micrometer-commons::jar:1.11.5
+io.micrometer:micrometer-core::jar:1.11.5
+io.micrometer:micrometer-observation::jar:1.11.5
+io.micrometer:micrometer-registry-prometheus::jar:1.11.5
+io.mvnpm:importmap::jar:1.0.10
+io.netty:netty-buffer::jar:4.1.100.Final
+io.netty:netty-codec-dns::jar:4.1.100.Final
+io.netty:netty-codec-haproxy::jar:4.1.100.Final
+io.netty:netty-codec-http2::jar:4.1.100.Final
+io.netty:netty-codec-http::jar:4.1.100.Final
+io.netty:netty-codec-socks::jar:4.1.100.Final
+io.netty:netty-codec::jar:4.1.100.Final
+io.netty:netty-common::jar:4.1.100.Final
+io.netty:netty-handler-proxy::jar:4.1.100.Final
+io.netty:netty-handler::jar:4.1.100.Final
+io.netty:netty-resolver-dns::jar:4.1.100.Final
+io.netty:netty-resolver::jar:4.1.100.Final
+io.netty:netty-transport-native-unix-common::jar:4.1.100.Final
+io.netty:netty-transport::jar:4.1.100.Final
+io.pebbletemplates:pebble::jar:3.2.2
+io.prometheus:simpleclient::jar:0.16.0
+io.prometheus:simpleclient_common::jar:0.16.0
+io.prometheus:simpleclient_tracer_common::jar:0.16.0
+io.prometheus:simpleclient_tracer_otel::jar:0.16.0
+io.prometheus:simpleclient_tracer_otel_agent::jar:0.16.0
+io.quarkiverse.jdbc:quarkus-jdbc-sqlite::jar:3.0.7
+io.quarkus.arc:arc-processor::jar:3.4.3
+io.quarkus.arc:arc::jar:3.4.3
+io.quarkus.gizmo:gizmo::jar:1.6.1.Final
+io.quarkus.qute:qute-core::jar:3.4.3
+io.quarkus.resteasy.reactive:resteasy-reactive-common-types::jar:3.4.3
+io.quarkus.resteasy.reactive:resteasy-reactive-common::jar:3.4.3
+io.quarkus.resteasy.reactive:resteasy-reactive-jackson::jar:3.4.3
+io.quarkus.resteasy.reactive:resteasy-reactive-vertx::jar:3.4.3
+io.quarkus.resteasy.reactive:resteasy-reactive::jar:3.4.3
+io.quarkus.security:quarkus-security::jar:2.0.2.Final
+io.quarkus:quarkus-agroal::jar:3.4.3
+io.quarkus:quarkus-apache-httpclient::jar:3.4.3
+io.quarkus:quarkus-arc-deployment::jar:3.4.3
+io.quarkus:quarkus-arc::jar:3.4.3
+io.quarkus:quarkus-bootstrap-app-model::jar:3.4.3
+io.quarkus:quarkus-bootstrap-core::jar:3.4.3
+io.quarkus:quarkus-bootstrap-runner::jar:3.4.3
+io.quarkus:quarkus-builder::jar:3.4.3
+io.quarkus:quarkus-caffeine::jar:3.4.3
+io.quarkus:quarkus-class-change-agent::jar:3.4.3
+io.quarkus:quarkus-core-deployment::jar:3.4.3
+io.quarkus:quarkus-core::jar:3.4.3
+io.quarkus:quarkus-credentials::jar:3.4.3
+io.quarkus:quarkus-datasource-common::jar:3.4.3
+io.quarkus:quarkus-datasource::jar:3.4.3
+io.quarkus:quarkus-development-mode-spi::jar:3.4.3
+io.quarkus:quarkus-devtools-utilities::jar:3.4.3
+io.quarkus:quarkus-fs-util::jar:0.0.9
+io.quarkus:quarkus-hibernate-orm-panache-common::jar:3.4.3
+io.quarkus:quarkus-hibernate-orm-panache::jar:3.4.3
+io.quarkus:quarkus-hibernate-orm::jar:3.4.3
+io.quarkus:quarkus-hibernate-validator::jar:3.4.3
+io.quarkus:quarkus-jackson::jar:3.4.3
+io.quarkus:quarkus-jaxb::jar:3.4.3
+io.quarkus:quarkus-jaxp::jar:3.4.3
+io.quarkus:quarkus-jsonp::jar:3.4.3
+io.quarkus:quarkus-kubernetes-spi::jar:3.4.3
+io.quarkus:quarkus-liquibase::jar:3.4.3
+io.quarkus:quarkus-micrometer-registry-prometheus::jar:3.4.3
+io.quarkus:quarkus-micrometer::jar:3.4.3
+io.quarkus:quarkus-mutiny-deployment::jar:3.4.3
+io.quarkus:quarkus-mutiny::jar:3.4.3
+io.quarkus:quarkus-narayana-jta::jar:3.4.3
+io.quarkus:quarkus-netty-deployment::jar:3.4.3
+io.quarkus:quarkus-netty::jar:3.4.3
+io.quarkus:quarkus-panache-common::jar:3.4.3
+io.quarkus:quarkus-panache-hibernate-common::jar:3.4.3
+io.quarkus:quarkus-rest-client-config::jar:3.4.3
+io.quarkus:quarkus-rest-client-jackson::jar:3.4.3
+io.quarkus:quarkus-rest-client::jar:3.4.3
+io.quarkus:quarkus-resteasy-common::jar:3.4.3
+io.quarkus:quarkus-resteasy-reactive-common::jar:3.4.3
+io.quarkus:quarkus-resteasy-reactive-jackson-common::jar:3.4.3
+io.quarkus:quarkus-resteasy-reactive-jackson::jar:3.4.3
+io.quarkus:quarkus-resteasy-reactive::jar:3.4.3
+io.quarkus:quarkus-security-runtime-spi::jar:3.4.3
+io.quarkus:quarkus-smallrye-context-propagation-deployment::jar:3.4.3
+io.quarkus:quarkus-smallrye-context-propagation-spi::jar:3.4.3
+io.quarkus:quarkus-smallrye-context-propagation::jar:3.4.3
+io.quarkus:quarkus-smallrye-health::jar:3.4.3
+io.quarkus:quarkus-smallrye-openapi::jar:3.4.3
+io.quarkus:quarkus-swagger-ui::jar:3.4.3
+io.quarkus:quarkus-transaction-annotations::jar:3.4.3
+io.quarkus:quarkus-vertx-deployment::jar:3.4.3
+io.quarkus:quarkus-vertx-http-deployment-spi::jar:3.4.3
+io.quarkus:quarkus-vertx-http-deployment::jar:3.4.3
+io.quarkus:quarkus-vertx-http-dev-console-runtime-spi::jar:3.4.3
+io.quarkus:quarkus-vertx-http-dev-console-spi::jar:3.4.3
+io.quarkus:quarkus-vertx-http-dev-ui-resources::jar:3.4.3
+io.quarkus:quarkus-vertx-http-dev-ui-spi::jar:3.4.3
+io.quarkus:quarkus-vertx-http::jar:3.4.3
+io.quarkus:quarkus-vertx-latebound-mdc-provider::jar:3.4.3
+io.quarkus:quarkus-vertx::jar:3.4.3
+io.quarkus:quarkus-virtual-threads-deployment::jar:3.4.3
+io.quarkus:quarkus-virtual-threads::jar:3.4.3
+io.smallrye.common:smallrye-common-annotation::jar:2.1.2
+io.smallrye.common:smallrye-common-classloader::jar:2.1.0
+io.smallrye.common:smallrye-common-constraint::jar:2.1.2
+io.smallrye.common:smallrye-common-cpu::jar:2.1.0
+io.smallrye.common:smallrye-common-expression::jar:2.1.0
+io.smallrye.common:smallrye-common-function::jar:2.1.0
+io.smallrye.common:smallrye-common-io::jar:2.1.2
+io.smallrye.common:smallrye-common-net::jar:2.1.0
+io.smallrye.common:smallrye-common-os::jar:2.1.2
+io.smallrye.common:smallrye-common-ref::jar:2.1.0
+io.smallrye.common:smallrye-common-vertx-context::jar:2.1.2
+io.smallrye.config:smallrye-config-common::jar:3.3.4
+io.smallrye.config:smallrye-config-core::jar:3.3.4
+io.smallrye.config:smallrye-config-validator::jar:3.3.4
+io.smallrye.config:smallrye-config::jar:3.3.4
+io.smallrye.reactive:mutiny-smallrye-context-propagation::jar:2.3.1
+io.smallrye.reactive:mutiny-zero-flow-adapters::jar:1.0.0
+io.smallrye.reactive:mutiny::jar:2.3.1
+io.smallrye.reactive:smallrye-mutiny-vertx-auth-common::jar:3.5.0
+io.smallrye.reactive:smallrye-mutiny-vertx-bridge-common::jar:3.5.0
+io.smallrye.reactive:smallrye-mutiny-vertx-core::jar:3.5.0
+io.smallrye.reactive:smallrye-mutiny-vertx-runtime::jar:3.5.0
+io.smallrye.reactive:smallrye-mutiny-vertx-uri-template::jar:3.5.0
+io.smallrye.reactive:smallrye-mutiny-vertx-web-common::jar:3.5.0
+io.smallrye.reactive:smallrye-mutiny-vertx-web::jar:3.5.0
+io.smallrye.reactive:smallrye-reactive-converter-api::jar:3.0.0
+io.smallrye.reactive:smallrye-reactive-converter-mutiny::jar:3.0.0
+io.smallrye.reactive:vertx-mutiny-generator::jar:3.5.0
+io.smallrye:jandex::jar:3.1.5
+io.smallrye:smallrye-context-propagation-api::jar:2.1.0
+io.smallrye:smallrye-context-propagation-jta::jar:2.1.0
+io.smallrye:smallrye-context-propagation-storage::jar:2.1.0
+io.smallrye:smallrye-context-propagation::jar:2.1.0
+io.smallrye:smallrye-fault-tolerance-vertx::jar:6.2.6
+io.smallrye:smallrye-health-api::jar:4.0.4
+io.smallrye:smallrye-health-provided-checks::jar:4.0.4
+io.smallrye:smallrye-health::jar:4.0.4
+io.smallrye:smallrye-open-api-core::jar:3.5.2
+io.vertx:vertx-auth-common::jar:4.4.5
+io.vertx:vertx-bridge-common::jar:4.4.5
+io.vertx:vertx-codegen::jar:4.4.4
+io.vertx:vertx-core::jar:4.4.5
+io.vertx:vertx-uri-template::jar:4.4.4
+io.vertx:vertx-web-common::jar:4.4.5
+io.vertx:vertx-web::jar:4.4.5
+jakarta.activation:jakarta.activation-api::jar:2.1.2
+jakarta.annotation:jakarta.annotation-api::jar:2.1.1
+jakarta.ejb:jakarta.ejb-api::jar:4.0.1
+jakarta.el:jakarta.el-api::jar:5.0.0
+jakarta.enterprise:jakarta.enterprise.cdi-api::jar:4.0.1
+jakarta.enterprise:jakarta.enterprise.lang-model::jar:4.0.1
+jakarta.inject:jakarta.inject-api::jar:2.0.1
+jakarta.interceptor:jakarta.interceptor-api::jar:2.1.0
+jakarta.json:jakarta.json-api::jar:2.1.3
+jakarta.persistence:jakarta.persistence-api::jar:3.1.0
+jakarta.resource:jakarta.resource-api::jar:2.0.0
+jakarta.servlet:jakarta.servlet-api::jar:6.0.0
+jakarta.transaction:jakarta.transaction-api::jar:2.0.1
+jakarta.validation:jakarta.validation-api::jar:3.0.2
+jakarta.ws.rs:jakarta.ws.rs-api::jar:3.1.0
+jakarta.xml.bind:jakarta.xml.bind-api::jar:4.0.1
+net.bytebuddy:byte-buddy::jar:1.14.9
+org.aesh:aesh::jar:2.7
+org.aesh:readline::jar:2.4
+org.antlr:antlr4-runtime::jar:4.10.1
+org.apache.commons:commons-collections4::jar:4.4
+org.apache.commons:commons-lang3::jar:3.14.0
+org.apache.commons:commons-text::jar:1.9
+org.apache.httpcomponents:httpasyncclient::jar:4.1.5
+org.apache.httpcomponents:httpclient::jar:4.5.14
+org.apache.httpcomponents:httpcore-nio::jar:4.4.16
+org.apache.httpcomponents:httpcore::jar:4.4.16
+org.apache.logging.log4j:log4j-api::jar:2.23.1
+org.apache.logging.log4j:log4j-core::jar:2.23.1
+org.apiguardian:apiguardian-api::jar:1.1.2
+org.eclipse.angus:angus-activation::jar:2.0.1
+org.eclipse.microprofile.config:microprofile-config-api::jar:3.0.2
+org.eclipse.microprofile.context-propagation:microprofile-context-propagation-api::jar:1.3
+org.eclipse.microprofile.health:microprofile-health-api::jar:4.0.1
+org.eclipse.microprofile.openapi:microprofile-openapi-api::jar:3.1
+org.eclipse.microprofile.reactive-streams-operators:microprofile-reactive-streams-operators-api::jar:3.0
+org.eclipse.microprofile.rest.client:microprofile-rest-client-api::jar:3.0.1
+org.eclipse.parsson:parsson::jar:1.1.4
+org.eclipse.sisu:org.eclipse.sisu.inject::jar:0.3.5
+org.freemarker:freemarker::jar:2.3.32
+org.fusesource.jansi:jansi::jar:2.4.0
+org.glassfish.expressly:expressly::jar:5.0.0
+org.glassfish.jaxb:jaxb-core::jar:4.0.3
+org.glassfish.jaxb:jaxb-runtime::jar:4.0.3
+org.glassfish.jaxb:txw2::jar:4.0.3
+org.graalvm.sdk:graal-sdk::jar:23.0.1
+org.hdrhistogram:HdrHistogram::jar:2.1.12
+org.hibernate.common:hibernate-commons-annotations::jar:6.0.6.Final
+org.hibernate.orm:hibernate-community-dialects::jar:6.2.13.Final
+org.hibernate.orm:hibernate-core::jar:6.2.13.Final
+org.hibernate.orm:hibernate-graalvm::jar:6.2.13.Final
+org.hibernate.validator:hibernate-validator::jar:8.0.1.Final
+org.hibernate:quarkus-local-cache::jar:0.2.1
+org.jboss.invocation:jboss-invocation::jar:2.0.0.Final
+org.jboss.logging:commons-logging-jboss-logging::jar:1.0.0.Final
+org.jboss.logging:jboss-logging-annotations::jar:2.2.1.Final
+org.jboss.logging:jboss-logging::jar:3.4.3.Final
+org.jboss.logmanager:jboss-logmanager::jar:3.0.2.Final
+org.jboss.narayana.jta:narayana-jta::jar:7.0.0.Final
+org.jboss.narayana.jts:narayana-jts-integration::jar:7.0.0.Final
+org.jboss.resteasy.microprofile:microprofile-config::jar:2.1.4.Final
+org.jboss.resteasy.microprofile:microprofile-rest-client-base::jar:2.1.4.Final
+org.jboss.resteasy.microprofile:microprofile-rest-client::jar:2.1.4.Final
+org.jboss.resteasy:resteasy-client-api::jar:6.2.5.Final
+org.jboss.resteasy:resteasy-client::jar:6.2.5.Final
+org.jboss.resteasy:resteasy-core-spi::jar:6.2.5.Final
+org.jboss.resteasy:resteasy-core::jar:6.2.5.Final
+org.jboss.resteasy:resteasy-jackson2-provider::jar:6.2.5.Final
+org.jboss.slf4j:slf4j-jboss-logmanager::jar:2.0.0.Final
+org.jboss.threads:jboss-threads::jar:3.5.0.Final
+org.jboss:jboss-transaction-spi::jar:8.0.0.Final
+org.jetbrains:annotations::jar:24.1.0
+org.junit.jupiter:junit-jupiter-api::jar:5.9.3
+org.junit.jupiter:junit-jupiter-engine::jar:5.9.3
+org.junit.jupiter:junit-jupiter-params::jar:5.9.3
+org.junit.jupiter:junit-jupiter::jar:5.9.3
+org.junit.platform:junit-platform-commons::jar:1.9.3
+org.junit.platform:junit-platform-engine::jar:1.9.3
+org.junit.platform:junit-platform-launcher::jar:1.9.3
+org.latencyutils:LatencyUtils::jar:2.0.3
+org.liquibase:liquibase-core::jar:4.20.0
+org.mvnpm.at.lit-labs:ssr-dom-shim::jar:1.1.1
+org.mvnpm.at.lit:reactive-element::jar:1.6.3
+org.mvnpm.at.mvnpm:vaadin-webcomponents::jar:24.1.6
+org.mvnpm.at.open-wc:dedupe-mixin::jar:1.4.0
+org.mvnpm.at.polymer:polymer::jar:3.5.1
+org.mvnpm.at.types:trusted-types::jar:2.0.3
+org.mvnpm.at.vaadin:router::jar:1.7.5
+org.mvnpm.at.vaadin:vaadin-development-mode-detector::jar:2.0.6
+org.mvnpm.at.vaadin:vaadin-usage-statistics::jar:2.1.2
+org.mvnpm.at.vanillawc:wc-codemirror::jar:2.1.0
+org.mvnpm.at.webcomponents:shadycss::jar:1.11.2
+org.mvnpm:echarts::jar:5.4.3
+org.mvnpm:es-module-shims::jar:1.8.0
+org.mvnpm:lit-element-state::jar:1.7.0
+org.mvnpm:lit-element::jar:3.3.3
+org.mvnpm:lit-html::jar:2.8.0
+org.mvnpm:lit::jar:2.8.0
+org.mvnpm:path-to-regexp::jar:2.4.0
+org.mvnpm:tslib::jar:2.3.0
+org.mvnpm:zrender::jar:5.4.4
+org.opentest4j:opentest4j::jar:1.2.0
+org.osgi:osgi.core::jar:6.0.0
+org.ow2.asm:asm-analysis::jar:9.5
+org.ow2.asm:asm-commons::jar:9.5
+org.ow2.asm:asm-tree::jar:9.5
+org.ow2.asm:asm-util::jar:9.5
+org.ow2.asm:asm::jar:9.5
+org.reactivestreams:reactive-streams::jar:1.0.4
+org.slf4j:slf4j-api::jar:2.0.9
+org.springframework:spring-core::jar:6.1.6
+org.springframework:spring-jcl::jar:6.0.13
+org.unbescape:unbescape::jar:1.1.6.RELEASE
+org.wildfly.common:wildfly-common::jar:1.5.4.Final-format-001
+org.xerial:sqlite-jdbc::jar:3.41.2.2
+org.yaml:snakeyaml::jar:1.33
diff --git a/api-server/target/quarkus-app/quarkus/quarkus-application.dat b/api-server/target/quarkus-app/quarkus/quarkus-application.dat
new file mode 100644
index 0000000..2a72b4a
Binary files /dev/null and b/api-server/target/quarkus-app/quarkus/quarkus-application.dat differ
diff --git a/api-server/target/quarkus-artifact.properties b/api-server/target/quarkus-artifact.properties
new file mode 100644
index 0000000..5331494
--- /dev/null
+++ b/api-server/target/quarkus-artifact.properties
@@ -0,0 +1,4 @@
+#Generated by Quarkus - Do not edit manually
+#Sun May 12 16:25:48 CEST 2024
+path=quarkus-app/quarkus-run.jar
+type=jar
diff --git a/cli/.DS_Store b/cli/.DS_Store
new file mode 100644
index 0000000..4924e66
Binary files /dev/null and b/cli/.DS_Store differ
diff --git a/cli/cli.iml b/cli/cli.iml
new file mode 100644
index 0000000..448dd89
--- /dev/null
+++ b/cli/cli.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cli/pom.xml b/cli/pom.xml
new file mode 100644
index 0000000..c22453f
--- /dev/null
+++ b/cli/pom.xml
@@ -0,0 +1,224 @@
+
+ 4.0.0
+ cli
+ 1.0-SNAPSHOT
+ cli
+ CLI for ResourceTracker
+
+
+ com.repoachiever
+ base
+ 1.0-SNAPSHOT
+
+
+
+
+ com.repoachiever.CLI
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+
+
+ io.netty
+ netty-resolver-dns-native-macos
+ osx-aarch_64
+
+
+
+
+ io.swagger.core.v3
+ swagger-annotations
+
+
+ io.swagger.core.v3
+ swagger-models
+
+
+ org.openapitools
+ jackson-databind-nullable
+
+
+
+
+ info.picocli
+ picocli
+
+
+ info.picocli
+ picocli-spring-boot-starter
+
+
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+
+
+ jakarta.validation
+ jakarta.validation-api
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+
+
+ org.projectlombok
+ lombok
+
+
+ org.yaml
+ snakeyaml
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+
+
+ com.opencsv
+ opencsv
+
+
+ commons-io
+ commons-io
+
+
+ me.tongfei
+ progressbar
+
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+
+
+ junit
+ junit
+
+
+
+
+ cli
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+
+
+ com.coderplus.maven.plugins
+ copy-rename-maven-plugin
+
+
+
+ ${basedir}/target/cli.jar
+ ${main.basedir}/../bin/cli/cli.jar
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ build-info
+
+
+
+
+
+
+
+ org.openapitools
+ openapi-generator-maven-plugin
+
+
+
+ generate
+
+
+ ${project.basedir}/../api-server/src/main/openapi/openapi.yml
+ java
+ webclient
+ ${default.package}.api
+ ${default.package}.model
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+
+ @lombok.Data @lombok.AllArgsConstructor(staticName = "of")
+ src/main/java
+ true
+ false
+ true
+ true
+ false
+ true
+ java8
+ true
+ true
+
+
+
+
+
+
+ pl.project13.maven
+ git-commit-id-plugin
+
+
+
+
+
+
+ dev
+
+ true
+
+
+ dev
+
+
+
+ prod
+
+ prod
+
+
+
+
diff --git a/cli/src/.DS_Store b/cli/src/.DS_Store
new file mode 100644
index 0000000..d1aacd2
Binary files /dev/null and b/cli/src/.DS_Store differ
diff --git a/cli/src/main/java/com/repoachiever/App.java b/cli/src/main/java/com/repoachiever/App.java
new file mode 100644
index 0000000..b762dd6
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/App.java
@@ -0,0 +1,94 @@
+package com.repoachiever;
+
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.service.client.command.*;
+import com.repoachiever.service.command.BaseCommandService;
+// import com.repoachiever.service.KafkaConsumerWrapper;
+import com.repoachiever.service.command.external.start.StartExternalCommandService;
+import com.repoachiever.service.command.external.start.provider.aws.AWSStartExternalCommandService;
+import com.repoachiever.service.command.external.state.StateExternalCommandService;
+import com.repoachiever.service.command.external.state.provider.aws.AWSStateExternalCommandService;
+import com.repoachiever.service.command.external.stop.StopExternalCommandService;
+import com.repoachiever.service.command.external.stop.provider.aws.AWSStopExternalCommandService;
+import com.repoachiever.service.command.external.version.VersionExternalCommandService;
+import com.repoachiever.service.command.internal.health.HealthCheckInternalCommandService;
+import com.repoachiever.service.command.internal.readiness.ReadinessCheckInternalCommandService;
+import com.repoachiever.service.command.internal.readiness.provider.aws.AWSReadinessCheckInternalCommandService;
+import com.repoachiever.service.config.ConfigService;
+import com.repoachiever.service.config.common.ValidConfigService;
+import com.repoachiever.service.visualization.VisualizationService;
+import com.repoachiever.service.visualization.common.label.StartCommandVisualizationLabel;
+import com.repoachiever.service.visualization.common.label.StateCommandVisualizationLabel;
+import com.repoachiever.service.visualization.common.label.StopCommandVisualizationLabel;
+import com.repoachiever.service.visualization.common.label.VersionCommandVisualizationLabel;
+import com.repoachiever.service.visualization.state.VisualizationState;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.ExitCodeGenerator;
+import org.springframework.boot.info.BuildProperties;
+import org.springframework.context.annotation.Import;
+import org.springframework.stereotype.Component;
+import picocli.CommandLine;
+
+@Component
+@Import({
+ BaseCommandService.class,
+ StartExternalCommandService.class,
+ AWSStateExternalCommandService.class,
+ StateExternalCommandService.class,
+ StopExternalCommandService.class,
+ VersionExternalCommandService.class,
+ HealthCheckInternalCommandService.class,
+ ReadinessCheckInternalCommandService.class,
+ AWSReadinessCheckInternalCommandService.class,
+ ApplyClientCommandService.class,
+ DestroyClientCommandService.class,
+ HealthCheckClientCommandService.class,
+ ReadinessCheckClientCommandService.class,
+ LogsClientCommandService.class,
+ ScriptAcquireClientCommandService.class,
+ SecretsAcquireClientCommandService.class,
+ VersionClientCommandService.class,
+ AWSStartExternalCommandService.class,
+ AWSStopExternalCommandService.class,
+ ConfigService.class,
+ ValidConfigService.class,
+ BuildProperties.class,
+ PropertiesEntity.class,
+ StartCommandVisualizationLabel.class,
+ StopCommandVisualizationLabel.class,
+ StateCommandVisualizationLabel.class,
+ VersionCommandVisualizationLabel.class,
+ VisualizationService.class,
+ VisualizationState.class
+})
+public class App implements ApplicationRunner, ExitCodeGenerator {
+ private static final Logger logger = LogManager.getLogger(App.class);
+
+ private int exitCode;
+
+ @Autowired private ValidConfigService validConfigService;
+
+ @Autowired private BaseCommandService baseCommandService;
+
+ @Override
+ public void run(ApplicationArguments args) {
+ try {
+ validConfigService.validate();
+ } catch (Exception e) {
+ logger.fatal(e.getMessage());
+ return;
+ }
+
+ CommandLine cmd = new CommandLine(baseCommandService);
+ exitCode = cmd.execute(args.getSourceArgs());
+ }
+
+ @Override
+ public int getExitCode() {
+ return exitCode;
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/CLI.java b/cli/src/main/java/com/repoachiever/CLI.java
new file mode 100644
index 0000000..02ee6ef
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/CLI.java
@@ -0,0 +1,13 @@
+package com.repoachiever;
+
+import com.repoachiever.service.client.command.*;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class CLI {
+ public static void main(String[] args) {
+ SpringApplication application = new SpringApplication(App.class);
+ System.exit(SpringApplication.exit(application.run(args)));
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/converter/CredentialsConverter.java b/cli/src/main/java/com/repoachiever/converter/CredentialsConverter.java
new file mode 100644
index 0000000..e6abb88
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/converter/CredentialsConverter.java
@@ -0,0 +1,11 @@
+package com.repoachiever.converter;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class CredentialsConverter {
+ @SuppressWarnings("unchecked")
+ public static T convert(Object input, Class stub) {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.convertValue(input, stub);
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/dto/ValidationScriptApplicationDto.java b/cli/src/main/java/com/repoachiever/dto/ValidationScriptApplicationDto.java
new file mode 100644
index 0000000..1749e85
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/dto/ValidationScriptApplicationDto.java
@@ -0,0 +1,12 @@
+package com.repoachiever.dto;
+
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/** Represents script validation application used for script acquiring process. */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class ValidationScriptApplicationDto {
+ private List fileContent;
+}
diff --git a/cli/src/main/java/com/repoachiever/dto/ValidationSecretsApplicationDto.java b/cli/src/main/java/com/repoachiever/dto/ValidationSecretsApplicationDto.java
new file mode 100644
index 0000000..288b23c
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/dto/ValidationSecretsApplicationDto.java
@@ -0,0 +1,14 @@
+package com.repoachiever.dto;
+
+import com.repoachiever.model.Provider;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/** Represents secrets validation application used for secrets acquiring process. */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class ValidationSecretsApplicationDto {
+ private Provider provider;
+
+ private String filePath;
+}
diff --git a/cli/src/main/java/com/repoachiever/dto/VisualizationLabelDto.java b/cli/src/main/java/com/repoachiever/dto/VisualizationLabelDto.java
new file mode 100644
index 0000000..b971900
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/dto/VisualizationLabelDto.java
@@ -0,0 +1,26 @@
+package com.repoachiever.dto;
+
+import lombok.AllArgsConstructor;
+
+/** */
+@AllArgsConstructor(staticName = "of")
+public class VisualizationLabelDto {
+ private final String message;
+
+ private final Integer percentage;
+
+ @Override
+ public String toString() {
+ int filledLength = (int) Math.round((double) percentage / 100 * 20);
+ int emptyLength = 20 - filledLength;
+
+ return "["
+ + "#".repeat(Math.max(0, filledLength))
+ + "-".repeat(Math.max(0, emptyLength))
+ + "] "
+ + percentage
+ + "%"
+ + " "
+ + message;
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/entity/ConfigEntity.java b/cli/src/main/java/com/repoachiever/entity/ConfigEntity.java
new file mode 100644
index 0000000..ee87ff5
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/entity/ConfigEntity.java
@@ -0,0 +1,72 @@
+package com.repoachiever.entity;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import java.util.List;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+/** Represents configuration model used for ResourceTracker deployment operation. */
+@Getter
+public class ConfigEntity {
+ /** Represents request to be executed in remote environment. */
+ @Getter
+ public static class Request {
+ @NotBlank public String name;
+
+ @Pattern(regexp = "^(((./)?)|((~/.)?)|((/?))?)([a-zA-Z/]*)((\\.([a-z]+))?)$")
+ public String file;
+
+ @Pattern(
+ regexp =
+ "(((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)|(([\\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))|([\\?])|([\\*]))[\\s](((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)|(([\\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))|([\\?])|([\\*]))[\\s](((([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?,)*([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?)|(([\\*]|[0-9]|[0-1][0-9]|[2][0-3])/([0-9]|[0-1][0-9]|[2][0-3]))|([\\?])|([\\*]))[\\s](((([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?,)*([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?(C)?)|(([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])/([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(C)?)|(L(-[0-9])?)|(L(-[1-2][0-9])?)|(L(-[3][0-1])?)|(LW)|([1-9]W)|([1-3][0-9]W)|([\\?])|([\\*]))[\\s](((([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?,)*([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?)|(([1-9]|0[1-9]|1[0-2])/([1-9]|0[1-9]|1[0-2]))|(((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?,)*(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)|((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)/(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))|([\\?])|([\\*]))[\\s]((([1-7](-([1-7]))?,)*([1-7])(-([1-7]))?)|([1-7]/([1-7]))|(((MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?,)*(MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?(C)?)|((MON|TUE|WED|THU|FRI|SAT|SUN)/(MON|TUE|WED|THU|FRI|SAT|SUN)(C)?)|(([1-7]|(MON|TUE|WED|THU|FRI|SAT|SUN))?(L|LW)?)|(([1-7]|MON|TUE|WED|THU|FRI|SAT|SUN)#([1-7])?)|([\\?])|([\\*]))([\\s]?(([\\*])?|(19[7-9][0-9])|(20[0-9][0-9]))?|"
+ + " (((19[7-9][0-9])|(20[0-9][0-9]))/((19[7-9][0-9])|(20[0-9][0-9])))?|"
+ + " ((((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?,)*((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?)?)")
+ public String frequency;
+ }
+
+ @Valid @NotNull public List requests;
+
+ /**
+ * Represents remove cloud infrastructure configuration properties used for further deployment
+ * related operations.
+ */
+ @Getter
+ public static class Cloud {
+ @JsonFormat(shape = JsonFormat.Shape.OBJECT)
+ public enum Provider {
+ @JsonProperty("aws")
+ AWS,
+ }
+
+ @NotNull public Provider provider;
+
+ @Getter
+ @NoArgsConstructor
+ public static class AWSCredentials {
+ @Pattern(regexp = "^(((./)?)|((~/.)?)|((/?))?)([a-zA-Z/]*)((\\.([a-z]+))?)$")
+ public String file;
+
+ @NotBlank public String region;
+ }
+
+ @NotNull public Object credentials;
+ }
+
+ @Valid @NotNull public Cloud cloud;
+
+ /** Represents API Server configuration used for further connection establishment. */
+ @Getter
+ public static class APIServer {
+ @NotBlank public String host;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("api-server")
+ public APIServer apiServer;
+}
diff --git a/cli/src/main/java/com/repoachiever/entity/PropertiesEntity.java b/cli/src/main/java/com/repoachiever/entity/PropertiesEntity.java
new file mode 100644
index 0000000..772f766
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/entity/PropertiesEntity.java
@@ -0,0 +1,79 @@
+package com.repoachiever.entity;
+
+import lombok.Getter;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+import org.springframework.core.io.ClassPathResource;
+
+/** Represents application properties used for application configuration. */
+@Getter
+@Configuration
+public class PropertiesEntity {
+ private static final String GIT_CONFIG_PROPERTIES_FILE = "git.properties";
+
+ @Value(value = "${git.commit.id.abbrev}")
+ private String gitCommitId;
+
+ @Value(value = "${config.root}")
+ private String configRootPath;
+
+ @Value(value = "${config.user.file}")
+ private String configUserFilePath;
+
+ @Value(value = "${progress.visualization.period}")
+ private Integer progressVisualizationPeriod;
+
+ @Value(value = "${progress.visualization.secrets-acquire-request}")
+ private String progressVisualizationSecretsAcquireRequestLabel;
+
+ @Value(value = "${progress.visualization.script-acquire-request}")
+ private String progressVisualizationScriptAcquireRequestLabel;
+
+ @Value(value = "${progress.visualization.apply-request}")
+ private String progressVisualizationApplyRequestLabel;
+
+ @Value(value = "${progress.visualization.apply-response}")
+ private String progressVisualizationApplyResponseLabel;
+
+ @Value(value = "${progress.visualization.destroy-request}")
+ private String progressVisualizationDestroyRequestLabel;
+
+ @Value(value = "${progress.visualization.destroy-response}")
+ private String progressVisualizationDestroyResponseLabel;
+
+ @Value(value = "${progress.visualization.state-request}")
+ private String progressVisualizationStateRequestLabel;
+
+ @Value(value = "${progress.visualization.state-response}")
+ private String progressVisualizationStateResponseLabel;
+
+ @Value(value = "${progress.visualization.version-info-request}")
+ private String progressVisualizationVersionInfoRequestLabel;
+
+ @Value(value = "${progress.visualization.health-check-request}")
+ private String progressVisualizationHealthCheckRequestLabel;
+
+ @Value(value = "${progress.visualization.readiness-check-request}")
+ private String progressVisualizationReadinessCheckRequestLabel;
+
+ @Bean
+ private static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
+ PropertySourcesPlaceholderConfigurer propsConfig = new PropertySourcesPlaceholderConfigurer();
+ propsConfig.setLocation(new ClassPathResource(GIT_CONFIG_PROPERTIES_FILE));
+ propsConfig.setIgnoreResourceNotFound(true);
+ propsConfig.setIgnoreUnresolvablePlaceholders(true);
+ return propsConfig;
+ }
+
+ /**
+ * Removes the last symbol in git commit id of the repository.
+ *
+ * @return chopped repository git commit id.
+ */
+ public String getGitCommitId() {
+ return StringUtils.chop(gitCommitId);
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/exception/ApiServerException.java b/cli/src/main/java/com/repoachiever/exception/ApiServerException.java
new file mode 100644
index 0000000..d89b655
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/exception/ApiServerException.java
@@ -0,0 +1,18 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+public class ApiServerException extends IOException {
+ public ApiServerException() {
+ this("");
+ }
+
+ public ApiServerException(Object... message) {
+ super(
+ new Formatter()
+ .format("API Server exception: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/exception/ApiServerNotAvailableException.java b/cli/src/main/java/com/repoachiever/exception/ApiServerNotAvailableException.java
new file mode 100644
index 0000000..471bacb
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/exception/ApiServerNotAvailableException.java
@@ -0,0 +1,18 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+public class ApiServerNotAvailableException extends IOException {
+ public ApiServerNotAvailableException() {
+ this("");
+ }
+
+ public ApiServerNotAvailableException(Object... message) {
+ super(
+ new Formatter()
+ .format("API Server is not available: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/exception/CloudCredentialsFileNotFoundException.java b/cli/src/main/java/com/repoachiever/exception/CloudCredentialsFileNotFoundException.java
new file mode 100644
index 0000000..43961c6
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/exception/CloudCredentialsFileNotFoundException.java
@@ -0,0 +1,19 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+public class CloudCredentialsFileNotFoundException extends IOException {
+ public CloudCredentialsFileNotFoundException() {
+ this("");
+ }
+
+ public CloudCredentialsFileNotFoundException(Object... message) {
+ super(
+ new Formatter()
+ .format(
+ "Given cloud credentials file is not found: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/exception/CloudCredentialsValidationException.java b/cli/src/main/java/com/repoachiever/exception/CloudCredentialsValidationException.java
new file mode 100644
index 0000000..713b34f
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/exception/CloudCredentialsValidationException.java
@@ -0,0 +1,18 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+public class CloudCredentialsValidationException extends IOException {
+ public CloudCredentialsValidationException() {
+ this("");
+ }
+
+ public CloudCredentialsValidationException(Object... message) {
+ super(
+ new Formatter()
+ .format("Given cloud credentials are not valid!: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/exception/ConfigValidationException.java b/cli/src/main/java/com/repoachiever/exception/ConfigValidationException.java
new file mode 100644
index 0000000..9b78871
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/exception/ConfigValidationException.java
@@ -0,0 +1,14 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+public class ConfigValidationException extends IOException {
+ public ConfigValidationException(Object... message) {
+ super(
+ new Formatter()
+ .format("Config file content is not valid: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/exception/ScriptDataFileNotFoundException.java b/cli/src/main/java/com/repoachiever/exception/ScriptDataFileNotFoundException.java
new file mode 100644
index 0000000..753feee
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/exception/ScriptDataFileNotFoundException.java
@@ -0,0 +1,18 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+public class ScriptDataFileNotFoundException extends IOException {
+ public ScriptDataFileNotFoundException() {
+ this("");
+ }
+
+ public ScriptDataFileNotFoundException(Object... message) {
+ super(
+ new Formatter()
+ .format("Given explicit script file is not found: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/exception/ScriptDataValidationException.java b/cli/src/main/java/com/repoachiever/exception/ScriptDataValidationException.java
new file mode 100644
index 0000000..17250fb
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/exception/ScriptDataValidationException.java
@@ -0,0 +1,19 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/** Represents exception, when given file is not valid. */
+public class ScriptDataValidationException extends IOException {
+ public ScriptDataValidationException() {
+ this("");
+ }
+
+ public ScriptDataValidationException(Object... message) {
+ super(
+ new Formatter()
+ .format("Given explicit script file is not valid: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/exception/VersionMismatchException.java b/cli/src/main/java/com/repoachiever/exception/VersionMismatchException.java
new file mode 100644
index 0000000..b8ffe86
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/exception/VersionMismatchException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/** Represents exception, when API Server version is different from the version of the client. */
+public class VersionMismatchException extends IOException {
+ public VersionMismatchException() {
+ this("");
+ }
+
+ public VersionMismatchException(Object... message) {
+ super(
+ new Formatter()
+ .format(
+ "API Server version is different from the version of the client: %s",
+ Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/logging/FatalAppender.java b/cli/src/main/java/com/repoachiever/logging/FatalAppender.java
new file mode 100644
index 0000000..53ff3e2
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/logging/FatalAppender.java
@@ -0,0 +1,48 @@
+package com.repoachiever.logging;
+
+import java.time.Instant;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+
+@Component
+@Plugin(name = "FatalAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
+public class FatalAppender extends AbstractAppender {
+ @Autowired private ApplicationContext context;
+
+ private ConcurrentMap eventMap = new ConcurrentHashMap<>();
+
+ @SuppressWarnings("deprecation")
+ protected FatalAppender(String name, Filter filter) {
+ super(name, filter, null);
+ }
+
+ @PluginFactory
+ public static FatalAppender createAppender(
+ @PluginAttribute("name") String name, @PluginElement("Filter") Filter filter) {
+ return new FatalAppender(name, filter);
+ }
+
+ @Override
+ public void append(LogEvent event) {
+ if (event.getLevel().equals(Level.FATAL)) {
+ SpringApplication.exit(context, () -> 1);
+ System.exit(1);
+ }
+
+ eventMap.put(Instant.now().toString(), event);
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/client/IClientCommand.java b/cli/src/main/java/com/repoachiever/service/client/IClientCommand.java
new file mode 100644
index 0000000..a81043d
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/client/IClientCommand.java
@@ -0,0 +1,19 @@
+package com.repoachiever.service.client;
+
+import com.repoachiever.exception.ApiServerException;
+
+/**
+ * Represents external resource command interface.
+ *
+ * @param type of the command response.
+ * @param type of the command request.
+ */
+public interface IClientCommand {
+ /**
+ * Processes certain request for an external command.
+ *
+ * @param input input to be given as request body.
+ * @return command response.
+ */
+ T process(K input) throws ApiServerException;
+}
diff --git a/cli/src/main/java/com/repoachiever/service/client/command/ApplyClientCommandService.java b/cli/src/main/java/com/repoachiever/service/client/command/ApplyClientCommandService.java
new file mode 100644
index 0000000..02db9c4
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/client/command/ApplyClientCommandService.java
@@ -0,0 +1,44 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.TerraformResourceApi;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.TerraformDeploymentApplication;
+import com.repoachiever.model.TerraformDeploymentApplicationResult;
+import com.repoachiever.service.client.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents apply client command service. */
+@Service
+public class ApplyClientCommandService
+ implements IClientCommand<
+ TerraformDeploymentApplicationResult, TerraformDeploymentApplication> {
+ private final TerraformResourceApi terraformResourceApi;
+
+ public ApplyClientCommandService(@Autowired ConfigService configService) {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.terraformResourceApi = new TerraformResourceApi(apiClient);
+ }
+
+ /**
+ * @see IClientCommand
+ */
+ @Override
+ public TerraformDeploymentApplicationResult process(TerraformDeploymentApplication input)
+ throws ApiServerException {
+ try {
+ return terraformResourceApi.v1TerraformApplyPost(input).block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/client/command/DestroyClientCommandService.java b/cli/src/main/java/com/repoachiever/service/client/command/DestroyClientCommandService.java
new file mode 100644
index 0000000..4a1d297
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/client/command/DestroyClientCommandService.java
@@ -0,0 +1,40 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.TerraformResourceApi;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.TerraformDestructionApplication;
+import com.repoachiever.service.client.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents destroy client command service. */
+@Service
+public class DestroyClientCommandService
+ implements IClientCommand {
+ private final TerraformResourceApi terraformResourceApi;
+
+ public DestroyClientCommandService(@Autowired ConfigService configService) {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.terraformResourceApi = new TerraformResourceApi(apiClient);
+ }
+
+ /**
+ * @see IClientCommand
+ */
+ public Void process(TerraformDestructionApplication input) throws ApiServerException {
+ try {
+ return terraformResourceApi.v1TerraformDestroyPost(input).block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/client/command/HealthCheckClientCommandService.java b/cli/src/main/java/com/repoachiever/service/client/command/HealthCheckClientCommandService.java
new file mode 100644
index 0000000..99ec67e
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/client/command/HealthCheckClientCommandService.java
@@ -0,0 +1,39 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.HealthResourceApi;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.HealthCheckResult;
+import com.repoachiever.service.client.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents health check client command service. */
+@Service
+public class HealthCheckClientCommandService implements IClientCommand {
+ private final HealthResourceApi healthResourceApi;
+
+ public HealthCheckClientCommandService(@Autowired ConfigService configService) {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.healthResourceApi = new HealthResourceApi(apiClient);
+ }
+
+ /**
+ * @see IClientCommand
+ */
+ public HealthCheckResult process(Void input) throws ApiServerException {
+ try {
+ return healthResourceApi.v1HealthGet().block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/client/command/LogsClientCommandService.java b/cli/src/main/java/com/repoachiever/service/client/command/LogsClientCommandService.java
new file mode 100644
index 0000000..57dd5e8
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/client/command/LogsClientCommandService.java
@@ -0,0 +1,42 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.TopicResourceApi;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.TopicLogsApplication;
+import com.repoachiever.model.TopicLogsResult;
+import com.repoachiever.service.client.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents logs topic client command service. */
+@Service
+public class LogsClientCommandService
+ implements IClientCommand {
+ private final TopicResourceApi topicResourceApi;
+
+ public LogsClientCommandService(@Autowired ConfigService configService) {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.topicResourceApi = new TopicResourceApi(apiClient);
+ }
+
+ /**
+ * @see IClientCommand
+ */
+ @Override
+ public TopicLogsResult process(TopicLogsApplication input) throws ApiServerException {
+ try {
+ return topicResourceApi.v1TopicLogsPost(input).block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/client/command/ReadinessCheckClientCommandService.java b/cli/src/main/java/com/repoachiever/service/client/command/ReadinessCheckClientCommandService.java
new file mode 100644
index 0000000..4ab1ea2
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/client/command/ReadinessCheckClientCommandService.java
@@ -0,0 +1,41 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.HealthResourceApi;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.ReadinessCheckApplication;
+import com.repoachiever.model.ReadinessCheckResult;
+import com.repoachiever.service.client.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents readiness check client command service. */
+@Service
+public class ReadinessCheckClientCommandService
+ implements IClientCommand {
+ private final HealthResourceApi healthResourceApi;
+
+ public ReadinessCheckClientCommandService(@Autowired ConfigService configService) {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.healthResourceApi = new HealthResourceApi(apiClient);
+ }
+
+ /**
+ * @see IClientCommand
+ */
+ public ReadinessCheckResult process(ReadinessCheckApplication input) throws ApiServerException {
+ try {
+ return healthResourceApi.v1ReadinessPost(input).block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/client/command/ScriptAcquireClientCommandService.java b/cli/src/main/java/com/repoachiever/service/client/command/ScriptAcquireClientCommandService.java
new file mode 100644
index 0000000..2907b28
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/client/command/ScriptAcquireClientCommandService.java
@@ -0,0 +1,46 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.ValidationResourceApi;
+import com.repoachiever.dto.ValidationScriptApplicationDto;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.ValidationScriptApplication;
+import com.repoachiever.model.ValidationScriptApplicationResult;
+import com.repoachiever.service.client.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents script validation client command service. */
+@Service
+public class ScriptAcquireClientCommandService
+ implements IClientCommand {
+ private final ValidationResourceApi validationResourceApi;
+
+ public ScriptAcquireClientCommandService(@Autowired ConfigService configService) {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.validationResourceApi = new ValidationResourceApi(apiClient);
+ }
+
+ /**
+ * @see IClientCommand
+ */
+ @Override
+ public ValidationScriptApplicationResult process(ValidationScriptApplicationDto input)
+ throws ApiServerException {
+ try {
+ return validationResourceApi
+ .v1ScriptAcquirePost(ValidationScriptApplication.of(input.getFileContent()))
+ .block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/client/command/SecretsAcquireClientCommandService.java b/cli/src/main/java/com/repoachiever/service/client/command/SecretsAcquireClientCommandService.java
new file mode 100644
index 0000000..cc93841
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/client/command/SecretsAcquireClientCommandService.java
@@ -0,0 +1,66 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.ValidationResourceApi;
+import com.repoachiever.dto.ValidationSecretsApplicationDto;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.exception.CloudCredentialsFileNotFoundException;
+import com.repoachiever.model.Provider;
+import com.repoachiever.model.ValidationSecretsApplication;
+import com.repoachiever.model.ValidationSecretsApplicationResult;
+import com.repoachiever.service.client.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents secrets validation client command service. */
+@Service
+public class SecretsAcquireClientCommandService
+ implements IClientCommand {
+ private final ValidationResourceApi validationResourceApi;
+
+ public SecretsAcquireClientCommandService(@Autowired ConfigService configService) {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.validationResourceApi = new ValidationResourceApi(apiClient);
+ }
+
+ /**
+ * @see IClientCommand
+ */
+ @Override
+ public ValidationSecretsApplicationResult process(ValidationSecretsApplicationDto input)
+ throws ApiServerException {
+ Path filePath = Paths.get(input.getFilePath());
+
+ if (Files.notExists(filePath)) {
+ throw new ApiServerException(new CloudCredentialsFileNotFoundException().getMessage());
+ }
+
+ String content;
+
+ try {
+ content = Files.readString(filePath);
+ } catch (IOException e) {
+ throw new ApiServerException(e.getMessage());
+ }
+
+ try {
+ return validationResourceApi
+ .v1SecretsAcquirePost(ValidationSecretsApplication.of(Provider.AWS, content))
+ .block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getHeaders()).getMessage());
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/client/command/VersionClientCommandService.java b/cli/src/main/java/com/repoachiever/service/client/command/VersionClientCommandService.java
new file mode 100644
index 0000000..e80dec6
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/client/command/VersionClientCommandService.java
@@ -0,0 +1,39 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.InfoResourceApi;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.ApplicationInfoResult;
+import com.repoachiever.service.client.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents version information client command service. */
+@Service
+public class VersionClientCommandService implements IClientCommand {
+ private final InfoResourceApi infoResourceApi;
+
+ public VersionClientCommandService(@Autowired ConfigService configService) {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.infoResourceApi = new InfoResourceApi(apiClient);
+ }
+
+ /**
+ * @see IClientCommand
+ */
+ public ApplicationInfoResult process(Void input) throws ApiServerException {
+ try {
+ return infoResourceApi.v1InfoVersionGet().block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/command/BaseCommandService.java b/cli/src/main/java/com/repoachiever/service/command/BaseCommandService.java
new file mode 100644
index 0000000..85dcc37
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/command/BaseCommandService.java
@@ -0,0 +1,134 @@
+package com.repoachiever.service.command;
+
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.service.command.external.start.StartExternalCommandService;
+import com.repoachiever.service.command.external.state.StateExternalCommandService;
+import com.repoachiever.service.command.external.stop.StopExternalCommandService;
+import com.repoachiever.service.command.external.version.VersionExternalCommandService;
+import com.repoachiever.service.command.internal.health.HealthCheckInternalCommandService;
+import com.repoachiever.service.command.internal.readiness.ReadinessCheckInternalCommandService;
+import com.repoachiever.service.visualization.VisualizationService;
+import com.repoachiever.service.visualization.common.label.StartCommandVisualizationLabel;
+import com.repoachiever.service.visualization.common.label.StateCommandVisualizationLabel;
+import com.repoachiever.service.visualization.common.label.StopCommandVisualizationLabel;
+import com.repoachiever.service.visualization.common.label.VersionCommandVisualizationLabel;
+import com.repoachiever.service.visualization.state.VisualizationState;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import picocli.CommandLine.Command;
+
+/** Represents general command management service. */
+@Service
+@Command(
+ name = "help",
+ mixinStandardHelpOptions = true,
+ description = "Cloud-based remote resource tracker",
+ version = "1.0")
+public class BaseCommandService {
+ private static final Logger logger = LogManager.getLogger(BaseCommandService.class);
+
+ @Autowired private StartExternalCommandService startCommandService;
+
+ @Autowired private StateExternalCommandService stateCommandService;
+
+ @Autowired private StopExternalCommandService stopCommandService;
+
+ @Autowired private VersionExternalCommandService versionCommandService;
+
+ @Autowired private HealthCheckInternalCommandService healthCheckInternalCommandService;
+
+ @Autowired private ReadinessCheckInternalCommandService readinessCheckInternalCommandService;
+
+ @Autowired private StartCommandVisualizationLabel startCommandVisualizationLabel;
+
+ @Autowired private StopCommandVisualizationLabel stopCommandVisualizationLabel;
+
+ @Autowired private StateCommandVisualizationLabel stateCommandVisualizationLabel;
+
+ @Autowired private VersionCommandVisualizationLabel versionCommandVisualizationLabel;
+
+ @Autowired private VisualizationService visualizationService;
+
+ @Autowired private VisualizationState visualizationState;
+
+ /** Provides access to start command service. */
+ @Command(description = "Start remote requests execution")
+ private void start() {
+ visualizationState.setLabel(startCommandVisualizationLabel);
+
+ visualizationService.process();
+
+ try {
+ healthCheckInternalCommandService.process();
+
+ startCommandService.process();
+ } catch (ApiServerException e) {
+ logger.fatal(e.getMessage());
+ return;
+ }
+
+ visualizationService.await();
+ }
+
+ /** Provides access to state command service. */
+ @Command(description = "Retrieve state of remote requests executions")
+ private void state() {
+ visualizationState.setLabel(stateCommandVisualizationLabel);
+
+ visualizationService.process();
+
+ try {
+ healthCheckInternalCommandService.process();
+ readinessCheckInternalCommandService.process();
+
+ stateCommandService.process();
+ } catch (ApiServerException e) {
+ logger.fatal(e.getMessage());
+ return;
+ }
+
+ visualizationService.await();
+ }
+
+ /** Provides access to stop command service. */
+ @Command(description = "Stop remote requests execution")
+ private void stop() {
+ visualizationState.setLabel(stopCommandVisualizationLabel);
+
+ visualizationService.process();
+
+ try {
+ healthCheckInternalCommandService.process();
+
+ stopCommandService.process();
+ } catch (ApiServerException e) {
+ logger.fatal(e.getMessage());
+ return;
+ }
+
+ visualizationService.await();
+ }
+
+ /** Provides access to version command service. */
+ @Command(
+ description =
+ "Retrieve version of ResourceTracker CLI and ResourceTracker API Server(if available)")
+ private void version() {
+ visualizationState.setLabel(versionCommandVisualizationLabel);
+
+ visualizationService.process();
+
+ try {
+ healthCheckInternalCommandService.process();
+
+ versionCommandService.process();
+ } catch (ApiServerException e) {
+ logger.fatal(e.getMessage());
+ return;
+ }
+
+ visualizationService.await();
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/command/common/ICommand.java b/cli/src/main/java/com/repoachiever/service/command/common/ICommand.java
new file mode 100644
index 0000000..0fc0e70
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/command/common/ICommand.java
@@ -0,0 +1,9 @@
+package com.repoachiever.service.command.common;
+
+import com.repoachiever.exception.ApiServerException;
+
+/** Represents common command interface. */
+public interface ICommand {
+ /** Processes certain request for an external command. */
+ void process() throws ApiServerException;
+}
diff --git a/cli/src/main/java/com/repoachiever/service/command/external/start/StartExternalCommandService.java b/cli/src/main/java/com/repoachiever/service/command/external/start/StartExternalCommandService.java
new file mode 100644
index 0000000..7e64cea
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/command/external/start/StartExternalCommandService.java
@@ -0,0 +1,26 @@
+package com.repoachiever.service.command.external.start;
+
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.model.*;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.command.external.start.provider.aws.AWSStartExternalCommandService;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents start external command service. */
+@Service
+public class StartExternalCommandService implements ICommand {
+ @Autowired private ConfigService configService;
+
+ @Autowired private AWSStartExternalCommandService awsStartExternalCommandService;
+
+ /**
+ * @see ICommand
+ */
+ public void process() throws ApiServerException {
+ switch (configService.getConfig().getCloud().getProvider()) {
+ case AWS -> awsStartExternalCommandService.process();
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/command/external/start/provider/aws/AWSStartExternalCommandService.java b/cli/src/main/java/com/repoachiever/service/command/external/start/provider/aws/AWSStartExternalCommandService.java
new file mode 100644
index 0000000..2926fdc
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/command/external/start/provider/aws/AWSStartExternalCommandService.java
@@ -0,0 +1,120 @@
+package com.repoachiever.service.command.external.start.provider.aws;
+
+import com.repoachiever.converter.CredentialsConverter;
+import com.repoachiever.dto.ValidationScriptApplicationDto;
+import com.repoachiever.dto.ValidationSecretsApplicationDto;
+import com.repoachiever.entity.ConfigEntity;
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.CloudCredentialsValidationException;
+import com.repoachiever.exception.ScriptDataValidationException;
+import com.repoachiever.exception.VersionMismatchException;
+import com.repoachiever.model.*;
+import com.repoachiever.service.client.command.ApplyClientCommandService;
+import com.repoachiever.service.client.command.ScriptAcquireClientCommandService;
+import com.repoachiever.service.client.command.SecretsAcquireClientCommandService;
+import com.repoachiever.service.client.command.VersionClientCommandService;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.config.ConfigService;
+import com.repoachiever.service.visualization.state.VisualizationState;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents start external command service for AWS provider. */
+@Service
+public class AWSStartExternalCommandService implements ICommand {
+ private static final Logger logger = LogManager.getLogger(AWSStartExternalCommandService.class);
+
+ @Autowired private ConfigService configService;
+
+ @Autowired private PropertiesEntity properties;
+
+ @Autowired private ApplyClientCommandService applyClientCommandService;
+
+ @Autowired private VersionClientCommandService versionClientCommandService;
+
+ @Autowired private SecretsAcquireClientCommandService secretsAcquireClientCommandService;
+
+ @Autowired private ScriptAcquireClientCommandService scriptAcquireClientCommandService;
+
+ @Autowired private VisualizationState visualizationState;
+
+ /**
+ * @see ICommand
+ */
+ @Override
+ public void process() throws ApiServerException {
+ visualizationState.getLabel().pushNext();
+
+ ConfigEntity.Cloud.AWSCredentials credentials =
+ CredentialsConverter.convert(
+ configService.getConfig().getCloud().getCredentials(),
+ ConfigEntity.Cloud.AWSCredentials.class);
+
+ ValidationSecretsApplicationDto validationSecretsApplicationDto =
+ ValidationSecretsApplicationDto.of(Provider.AWS, credentials.getFile());
+
+ ValidationSecretsApplicationResult validationSecretsApplicationResult =
+ secretsAcquireClientCommandService.process(validationSecretsApplicationDto);
+
+ if (validationSecretsApplicationResult.getValid()) {
+ visualizationState.getLabel().pushNext();
+
+ ApplicationInfoResult applicationInfoResult = versionClientCommandService.process(null);
+
+ if (!applicationInfoResult.getExternalApi().getHash().equals(properties.getGitCommitId())) {
+ throw new ApiServerException(new VersionMismatchException().getMessage());
+ }
+
+ List requests =
+ configService.getConfig().getRequests().stream()
+ .map(
+ element -> {
+ try {
+ return DeploymentRequest.of(
+ element.getName(),
+ Files.readString(Paths.get(element.getFile())),
+ element.getFrequency());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ })
+ .toList();
+
+ ValidationScriptApplicationDto validationScriptApplicationDto =
+ ValidationScriptApplicationDto.of(
+ requests.stream().map(DeploymentRequest::getScript).toList());
+
+ ValidationScriptApplicationResult validationScriptApplicationResult =
+ scriptAcquireClientCommandService.process(validationScriptApplicationDto);
+
+ if (validationScriptApplicationResult.getValid()) {
+ visualizationState.getLabel().pushNext();
+
+ CredentialsFields credentialsFields =
+ CredentialsFields.of(
+ AWSSecrets.of(
+ validationSecretsApplicationResult.getSecrets().getAccessKey(),
+ validationSecretsApplicationResult.getSecrets().getSecretKey()),
+ credentials.getRegion());
+
+ TerraformDeploymentApplication terraformDeploymentApplication =
+ TerraformDeploymentApplication.of(requests, Provider.AWS, credentialsFields);
+
+ applyClientCommandService.process(terraformDeploymentApplication);
+
+ visualizationState.getLabel().pushNext();
+ } else {
+ logger.fatal(new ScriptDataValidationException().getMessage());
+ }
+ } else {
+ logger.fatal(new CloudCredentialsValidationException().getMessage());
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/command/external/state/StateExternalCommandService.java b/cli/src/main/java/com/repoachiever/service/command/external/state/StateExternalCommandService.java
new file mode 100644
index 0000000..233e319
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/command/external/state/StateExternalCommandService.java
@@ -0,0 +1,25 @@
+package com.repoachiever.service.command.external.state;
+
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.command.external.state.provider.aws.AWSStateExternalCommandService;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents state external command service. */
+@Service
+public class StateExternalCommandService implements ICommand {
+ @Autowired private ConfigService configService;
+
+ @Autowired private AWSStateExternalCommandService awsStateExternalCommandService;
+
+ /**
+ * @see ICommand
+ */
+ public void process() throws ApiServerException {
+ switch (configService.getConfig().getCloud().getProvider()) {
+ case AWS -> awsStateExternalCommandService.process();
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/command/external/state/provider/aws/AWSStateExternalCommandService.java b/cli/src/main/java/com/repoachiever/service/command/external/state/provider/aws/AWSStateExternalCommandService.java
new file mode 100644
index 0000000..f01cc5f
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/command/external/state/provider/aws/AWSStateExternalCommandService.java
@@ -0,0 +1,86 @@
+package com.repoachiever.service.command.external.state.provider.aws;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.repoachiever.converter.CredentialsConverter;
+import com.repoachiever.dto.ValidationSecretsApplicationDto;
+import com.repoachiever.entity.ConfigEntity;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.CloudCredentialsValidationException;
+import com.repoachiever.model.*;
+import com.repoachiever.service.client.command.LogsClientCommandService;
+import com.repoachiever.service.client.command.SecretsAcquireClientCommandService;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.config.ConfigService;
+import com.repoachiever.service.visualization.state.VisualizationState;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents start external command service for AWS provider. */
+@Service
+public class AWSStateExternalCommandService implements ICommand {
+ private static final Logger logger = LogManager.getLogger(AWSStateExternalCommandService.class);
+
+ @Autowired private ConfigService configService;
+
+ @Autowired private SecretsAcquireClientCommandService secretsAcquireClientCommandService;
+
+ @Autowired private LogsClientCommandService logsClientCommandService;
+
+ @Autowired private VisualizationState visualizationState;
+
+ /**
+ * @see ICommand
+ */
+ @Override
+ public void process() throws ApiServerException {
+ visualizationState.getLabel().pushNext();
+
+ ConfigEntity.Cloud.AWSCredentials credentials =
+ CredentialsConverter.convert(
+ configService.getConfig().getCloud().getCredentials(),
+ ConfigEntity.Cloud.AWSCredentials.class);
+
+ ValidationSecretsApplicationDto validationSecretsApplicationDto =
+ ValidationSecretsApplicationDto.of(Provider.AWS, credentials.getFile());
+
+ ValidationSecretsApplicationResult validationSecretsApplicationResult =
+ secretsAcquireClientCommandService.process(validationSecretsApplicationDto);
+
+ if (validationSecretsApplicationResult.getValid()) {
+ visualizationState.getLabel().pushNext();
+
+ CredentialsFields credentialsFields =
+ CredentialsFields.of(
+ AWSSecrets.of(
+ validationSecretsApplicationResult.getSecrets().getAccessKey(),
+ validationSecretsApplicationResult.getSecrets().getSecretKey()),
+ credentials.getRegion());
+
+ TopicLogsResult topicLogsResult;
+
+ TopicLogsApplication topicLogsApplication =
+ TopicLogsApplication.of(Provider.AWS, credentialsFields);
+
+ topicLogsResult = logsClientCommandService.process(topicLogsApplication);
+
+ visualizationState.getLabel().pushNext();
+
+ ObjectMapper mapper = new ObjectMapper();
+ topicLogsResult
+ .getResult()
+ .forEach(
+ element -> {
+ try {
+ visualizationState.addResult(mapper.writeValueAsString(element));
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ } else {
+ logger.fatal(new CloudCredentialsValidationException().getMessage());
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/command/external/stop/StopExternalCommandService.java b/cli/src/main/java/com/repoachiever/service/command/external/stop/StopExternalCommandService.java
new file mode 100644
index 0000000..1a393c7
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/command/external/stop/StopExternalCommandService.java
@@ -0,0 +1,26 @@
+package com.repoachiever.service.command.external.stop;
+
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.model.*;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.command.external.stop.provider.aws.AWSStopExternalCommandService;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents stop external command service. */
+@Service
+public class StopExternalCommandService implements ICommand {
+ @Autowired private ConfigService configService;
+
+ @Autowired private AWSStopExternalCommandService stopExternalCommandService;
+
+ /**
+ * @see ICommand
+ */
+ public void process() throws ApiServerException {
+ switch (configService.getConfig().getCloud().getProvider()) {
+ case AWS -> stopExternalCommandService.process();
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/command/external/stop/provider/aws/AWSStopExternalCommandService.java b/cli/src/main/java/com/repoachiever/service/command/external/stop/provider/aws/AWSStopExternalCommandService.java
new file mode 100644
index 0000000..7677ed5
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/command/external/stop/provider/aws/AWSStopExternalCommandService.java
@@ -0,0 +1,80 @@
+package com.repoachiever.service.command.external.stop.provider.aws;
+
+import com.repoachiever.converter.CredentialsConverter;
+import com.repoachiever.dto.ValidationSecretsApplicationDto;
+import com.repoachiever.entity.ConfigEntity;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.CloudCredentialsValidationException;
+import com.repoachiever.model.AWSSecrets;
+import com.repoachiever.model.CredentialsFields;
+import com.repoachiever.model.DestructionRequest;
+import com.repoachiever.model.Provider;
+import com.repoachiever.model.TerraformDestructionApplication;
+import com.repoachiever.model.ValidationSecretsApplicationResult;
+import com.repoachiever.service.client.command.DestroyClientCommandService;
+import com.repoachiever.service.client.command.SecretsAcquireClientCommandService;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.config.ConfigService;
+import com.repoachiever.service.visualization.state.VisualizationState;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** */
+@Service
+public class AWSStopExternalCommandService implements ICommand {
+ private static final Logger logger = LogManager.getLogger(AWSStopExternalCommandService.class);
+
+ @Autowired private ConfigService configService;
+
+ @Autowired private DestroyClientCommandService destroyClientCommandService;
+
+ @Autowired private SecretsAcquireClientCommandService secretsAcquireClientCommandService;
+
+ @Autowired private VisualizationState visualizationState;
+
+ /**
+ * @see ICommand
+ */
+ @Override
+ public void process() throws ApiServerException {
+ visualizationState.getLabel().pushNext();
+
+ ConfigEntity.Cloud.AWSCredentials credentials =
+ CredentialsConverter.convert(
+ configService.getConfig().getCloud().getCredentials(),
+ ConfigEntity.Cloud.AWSCredentials.class);
+
+ ValidationSecretsApplicationDto validationSecretsApplicationDto =
+ ValidationSecretsApplicationDto.of(Provider.AWS, credentials.getFile());
+
+ ValidationSecretsApplicationResult validationSecretsApplicationResult =
+ secretsAcquireClientCommandService.process(validationSecretsApplicationDto);
+
+ if (validationSecretsApplicationResult.getValid()) {
+ visualizationState.getLabel().pushNext();
+
+ CredentialsFields credentialsFields =
+ CredentialsFields.of(
+ AWSSecrets.of(
+ validationSecretsApplicationResult.getSecrets().getAccessKey(),
+ validationSecretsApplicationResult.getSecrets().getSecretKey()),
+ credentials.getRegion());
+
+ TerraformDestructionApplication terraformDestructionApplication =
+ TerraformDestructionApplication.of(
+ configService.getConfig().getRequests().stream()
+ .map(element -> DestructionRequest.of(element.getName()))
+ .toList(),
+ Provider.AWS,
+ credentialsFields);
+
+ destroyClientCommandService.process(terraformDestructionApplication);
+
+ visualizationState.getLabel().pushNext();
+ } else {
+ logger.fatal(new CloudCredentialsValidationException().getMessage());
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/command/external/version/VersionExternalCommandService.java b/cli/src/main/java/com/repoachiever/service/command/external/version/VersionExternalCommandService.java
new file mode 100644
index 0000000..54c7bb6
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/command/external/version/VersionExternalCommandService.java
@@ -0,0 +1,41 @@
+package com.repoachiever.service.command.external.version;
+
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.model.ApplicationInfoResult;
+import com.repoachiever.service.client.command.HealthCheckClientCommandService;
+import com.repoachiever.service.client.command.VersionClientCommandService;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.visualization.state.VisualizationState;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents version external command service. */
+@Service
+public class VersionExternalCommandService implements ICommand {
+ @Autowired PropertiesEntity properties;
+
+ @Autowired private VersionClientCommandService versionClientCommandService;
+
+ @Autowired private HealthCheckClientCommandService healthCheckClientCommandService;
+
+ @Autowired private VisualizationState visualizationState;
+
+ /**
+ * @see ICommand
+ */
+ public void process() throws ApiServerException {
+ visualizationState.getLabel().pushNext();
+
+ try {
+ ApplicationInfoResult applicationInfoResult = versionClientCommandService.process(null);
+
+ visualizationState.addResult(
+ String.format(
+ "API Server version: %s", applicationInfoResult.getExternalApi().getHash()));
+ } finally {
+ visualizationState.addResult(
+ String.format("Client version: %s", properties.getGitCommitId()));
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/command/internal/health/HealthCheckInternalCommandService.java b/cli/src/main/java/com/repoachiever/service/command/internal/health/HealthCheckInternalCommandService.java
new file mode 100644
index 0000000..90b6b2d
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/command/internal/health/HealthCheckInternalCommandService.java
@@ -0,0 +1,34 @@
+package com.repoachiever.service.command.internal.health;
+
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.HealthCheckResult;
+import com.repoachiever.model.HealthCheckStatus;
+import com.repoachiever.service.client.command.HealthCheckClientCommandService;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.visualization.state.VisualizationState;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class HealthCheckInternalCommandService implements ICommand {
+ @Autowired private HealthCheckClientCommandService healthCheckClientCommandService;
+
+ @Autowired private VisualizationState visualizationState;
+
+ /**
+ * @see ICommand
+ */
+ @Override
+ public void process() throws ApiServerException {
+ visualizationState.getLabel().pushNext();
+
+ HealthCheckResult healthCheckResult = healthCheckClientCommandService.process(null);
+
+ if (healthCheckResult.getStatus() == HealthCheckStatus.DOWN) {
+ throw new ApiServerException(
+ new ApiServerNotAvailableException(healthCheckResult.getChecks().toString())
+ .getMessage());
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/command/internal/readiness/ReadinessCheckInternalCommandService.java b/cli/src/main/java/com/repoachiever/service/command/internal/readiness/ReadinessCheckInternalCommandService.java
new file mode 100644
index 0000000..b164049
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/command/internal/readiness/ReadinessCheckInternalCommandService.java
@@ -0,0 +1,28 @@
+package com.repoachiever.service.command.internal.readiness;
+
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.model.*;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.command.internal.readiness.provider.aws.AWSReadinessCheckInternalCommandService;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents readiness check internal command service. */
+@Service
+public class ReadinessCheckInternalCommandService implements ICommand {
+ @Autowired private ConfigService configService;
+
+ @Autowired
+ private AWSReadinessCheckInternalCommandService awsReadinessCheckInternalCommandService;
+
+ /**
+ * @see ICommand
+ */
+ @Override
+ public void process() throws ApiServerException {
+ switch (configService.getConfig().getCloud().getProvider()) {
+ case AWS -> awsReadinessCheckInternalCommandService.process();
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/command/internal/readiness/provider/aws/AWSReadinessCheckInternalCommandService.java b/cli/src/main/java/com/repoachiever/service/command/internal/readiness/provider/aws/AWSReadinessCheckInternalCommandService.java
new file mode 100644
index 0000000..114cb86
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/command/internal/readiness/provider/aws/AWSReadinessCheckInternalCommandService.java
@@ -0,0 +1,74 @@
+package com.repoachiever.service.command.internal.readiness.provider.aws;
+
+import com.repoachiever.converter.CredentialsConverter;
+import com.repoachiever.dto.ValidationSecretsApplicationDto;
+import com.repoachiever.entity.ConfigEntity;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.exception.CloudCredentialsValidationException;
+import com.repoachiever.model.*;
+import com.repoachiever.service.client.command.ReadinessCheckClientCommandService;
+import com.repoachiever.service.client.command.SecretsAcquireClientCommandService;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.config.ConfigService;
+import com.repoachiever.service.visualization.state.VisualizationState;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AWSReadinessCheckInternalCommandService implements ICommand {
+ private static final Logger logger =
+ LogManager.getLogger(AWSReadinessCheckInternalCommandService.class);
+
+ @Autowired private ConfigService configService;
+
+ @Autowired private SecretsAcquireClientCommandService secretsAcquireClientCommandService;
+
+ @Autowired private ReadinessCheckClientCommandService readinessCheckClientCommandService;
+
+ @Autowired private VisualizationState visualizationState;
+
+ /**
+ * @see ICommand
+ */
+ @Override
+ public void process() throws ApiServerException {
+ visualizationState.getLabel().pushNext();
+
+ ConfigEntity.Cloud.AWSCredentials credentials =
+ CredentialsConverter.convert(
+ configService.getConfig().getCloud().getCredentials(),
+ ConfigEntity.Cloud.AWSCredentials.class);
+
+ ValidationSecretsApplicationDto validationSecretsApplicationDto =
+ ValidationSecretsApplicationDto.of(Provider.AWS, credentials.getFile());
+
+ ValidationSecretsApplicationResult validationSecretsApplicationResult =
+ secretsAcquireClientCommandService.process(validationSecretsApplicationDto);
+
+ if (validationSecretsApplicationResult.getValid()) {
+ CredentialsFields credentialsFields =
+ CredentialsFields.of(
+ AWSSecrets.of(
+ validationSecretsApplicationResult.getSecrets().getAccessKey(),
+ validationSecretsApplicationResult.getSecrets().getSecretKey()),
+ credentials.getRegion());
+
+ ReadinessCheckApplication readinessCheckApplication =
+ ReadinessCheckApplication.of(Provider.AWS, credentialsFields);
+
+ ReadinessCheckResult readinessCheckResult =
+ readinessCheckClientCommandService.process(readinessCheckApplication);
+
+ if (readinessCheckResult.getStatus() == ReadinessCheckStatus.DOWN) {
+ throw new ApiServerException(
+ new ApiServerNotAvailableException(readinessCheckResult.getData().toString())
+ .getMessage());
+ }
+ } else {
+ logger.fatal(new CloudCredentialsValidationException().getMessage());
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/config/ConfigService.java b/cli/src/main/java/com/repoachiever/service/config/ConfigService.java
new file mode 100644
index 0000000..941d806
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/config/ConfigService.java
@@ -0,0 +1,89 @@
+package com.repoachiever.service.config;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.repoachiever.entity.ConfigEntity;
+import com.repoachiever.entity.PropertiesEntity;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+
+import java.io.*;
+import java.nio.file.Paths;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * Service for processing configuration file.
+ */
+@Service
+public class ConfigService {
+ private static final Logger logger = LogManager.getLogger(ConfigService.class);
+
+ private InputStream configFile;
+
+ private ConfigEntity parsedConfigFile;
+
+ /**
+ * Default constructor, which opens configuration file at the given path.
+ *
+ * @param properties common application properties
+ */
+ public ConfigService(@Autowired PropertiesEntity properties) {
+ try {
+ configFile =
+ new FileInputStream(
+ Paths.get(
+ System.getProperty("user.home"),
+ properties.getConfigRootPath(),
+ properties.getConfigUserFilePath())
+ .toString());
+ } catch (FileNotFoundException e) {
+ logger.fatal(e.getMessage());
+ }
+ }
+
+ /**
+ * Reads configuration from the opened configuration file using mapping with a configuration
+ * entity.
+ */
+ @PostConstruct
+ private void configure() {
+ ObjectMapper mapper =
+ new ObjectMapper(new YAMLFactory())
+ .configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, true)
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
+ .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ ObjectReader reader = mapper.reader().forType(new TypeReference() {
+ });
+
+ try {
+ parsedConfigFile = reader.readValues(configFile).readAll().getFirst();
+ } catch (IOException e) {
+ logger.fatal(e.getMessage());
+ }
+ }
+
+ /**
+ * @return Parsed configuration entity
+ */
+ public ConfigEntity getConfig() {
+ return parsedConfigFile;
+ }
+
+ @PreDestroy
+ private void close() {
+ try {
+ configFile.close();
+ } catch (IOException e) {
+ logger.fatal(e.getMessage());
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/config/common/ValidConfigService.java b/cli/src/main/java/com/repoachiever/service/config/common/ValidConfigService.java
new file mode 100644
index 0000000..0f146a2
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/config/common/ValidConfigService.java
@@ -0,0 +1,40 @@
+package com.repoachiever.service.config.common;
+
+import com.repoachiever.entity.ConfigEntity;
+import com.repoachiever.exception.ConfigValidationException;
+import com.repoachiever.service.config.ConfigService;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.ValidatorFactory;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents service for config validation. */
+@Service
+public class ValidConfigService {
+ @Autowired private ConfigService configService;
+
+ /**
+ * Validates parsed local configuration file.
+ *
+ * @throws ConfigValidationException if the configuration validation is not passed.
+ */
+ public void validate() throws ConfigValidationException {
+ try (ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory()) {
+ Validator validator = validatorFactory.getValidator();
+
+ Set> validationResult =
+ validator.validate(configService.getConfig());
+
+ if (!validationResult.isEmpty()) {
+ throw new ConfigValidationException(
+ validationResult.stream()
+ .map(ConstraintViolation::getMessage)
+ .collect(Collectors.joining(", ")));
+ }
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/visualization/VisualizationService.java b/cli/src/main/java/com/repoachiever/service/visualization/VisualizationService.java
new file mode 100644
index 0000000..242e3c5
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/visualization/VisualizationService.java
@@ -0,0 +1,50 @@
+package com.repoachiever.service.visualization;
+
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.service.visualization.state.VisualizationState;
+import java.util.concurrent.*;
+import lombok.SneakyThrows;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents visualization service used to indicate current execution steps. */
+@Service
+public class VisualizationService {
+ @Autowired private PropertiesEntity properties;
+
+ @Autowired private VisualizationState visualizationState;
+
+ private final ScheduledExecutorService scheduledExecutorService =
+ Executors.newScheduledThreadPool(2);
+
+ private final CountDownLatch latch = new CountDownLatch(1);
+
+ /** Starts progress visualization processor. */
+ public void process() {
+ scheduledExecutorService.scheduleAtFixedRate(
+ () -> {
+ if (visualizationState.getLabel().isNext()) {
+ System.out.println(visualizationState.getLabel().getCurrent());
+ }
+
+ if (visualizationState.getLabel().isEmpty() && !visualizationState.getLabel().isNext()) {
+ latch.countDown();
+ }
+ },
+ 0,
+ properties.getProgressVisualizationPeriod(),
+ TimeUnit.MILLISECONDS);
+ }
+
+ /** Awaits for visualization service to end its processes. */
+ @SneakyThrows
+ public void await() {
+ latch.await();
+
+ if (!visualizationState.getResult().isEmpty()) {
+ System.out.print("\n");
+ }
+
+ visualizationState.getResult().forEach(System.out::println);
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/visualization/common/IVisualizationLabel.java b/cli/src/main/java/com/repoachiever/service/visualization/common/IVisualizationLabel.java
new file mode 100644
index 0000000..3b56108
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/visualization/common/IVisualizationLabel.java
@@ -0,0 +1,28 @@
+package com.repoachiever.service.visualization.common;
+
+/** Represents iterative interface for visualization label. */
+public interface IVisualizationLabel {
+ /**
+ * Checks if there are steps available in the storage.
+ *
+ * @return result of the check.
+ */
+ boolean isEmpty();
+
+ /**
+ * Checks if there is next step in the specified label.
+ *
+ * @return result of the check.
+ */
+ boolean isNext();
+
+ /** Pushes next step in the specified label. */
+ void pushNext();
+
+ /**
+ * Returns string interpretation of the current step.
+ *
+ * @return string interpretation of the current step.
+ */
+ String getCurrent();
+}
diff --git a/cli/src/main/java/com/repoachiever/service/visualization/common/label/StartCommandVisualizationLabel.java b/cli/src/main/java/com/repoachiever/service/visualization/common/label/StartCommandVisualizationLabel.java
new file mode 100644
index 0000000..dd0ed06
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/visualization/common/label/StartCommandVisualizationLabel.java
@@ -0,0 +1,82 @@
+package com.repoachiever.service.visualization.common.label;
+
+import com.repoachiever.dto.VisualizationLabelDto;
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.service.visualization.common.IVisualizationLabel;
+import java.util.ArrayDeque;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents label set used for apply command service. */
+@Service
+public class StartCommandVisualizationLabel implements IVisualizationLabel {
+ private final ArrayDeque stepsQueue = new ArrayDeque<>();
+
+ private final ArrayDeque batchQueue = new ArrayDeque<>();
+
+ private final ReentrantLock mutex = new ReentrantLock();
+
+ public StartCommandVisualizationLabel(@Autowired PropertiesEntity properties) {
+ stepsQueue.addAll(
+ List.of(
+ VisualizationLabelDto.of(
+ properties.getProgressVisualizationHealthCheckRequestLabel(), 10),
+ VisualizationLabelDto.of(
+ properties.getProgressVisualizationSecretsAcquireRequestLabel(), 30),
+ VisualizationLabelDto.of(
+ properties.getProgressVisualizationScriptAcquireRequestLabel(), 60),
+ VisualizationLabelDto.of(properties.getProgressVisualizationApplyRequestLabel(), 90),
+ VisualizationLabelDto.of(
+ properties.getProgressVisualizationApplyResponseLabel(), 100)));
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public boolean isEmpty() {
+ return stepsQueue.isEmpty();
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public boolean isNext() {
+ mutex.lock();
+
+ try {
+ return !batchQueue.isEmpty();
+ } finally {
+ mutex.unlock();
+ }
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public void pushNext() {
+ mutex.lock();
+
+ batchQueue.push(stepsQueue.pop().toString());
+
+ mutex.unlock();
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public String getCurrent() {
+ mutex.lock();
+
+ try {
+ return batchQueue.pollLast();
+ } finally {
+ mutex.unlock();
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/visualization/common/label/StateCommandVisualizationLabel.java b/cli/src/main/java/com/repoachiever/service/visualization/common/label/StateCommandVisualizationLabel.java
new file mode 100644
index 0000000..fff547a
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/visualization/common/label/StateCommandVisualizationLabel.java
@@ -0,0 +1,82 @@
+package com.repoachiever.service.visualization.common.label;
+
+import com.repoachiever.dto.VisualizationLabelDto;
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.service.visualization.common.IVisualizationLabel;
+import java.util.ArrayDeque;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents label set used for apply command service. */
+@Service
+public class StateCommandVisualizationLabel implements IVisualizationLabel {
+ private final ArrayDeque stepsQueue = new ArrayDeque<>();
+
+ private final ArrayDeque batchQueue = new ArrayDeque<>();
+
+ private final ReentrantLock mutex = new ReentrantLock();
+
+ public StateCommandVisualizationLabel(@Autowired PropertiesEntity properties) {
+ stepsQueue.addAll(
+ List.of(
+ VisualizationLabelDto.of(
+ properties.getProgressVisualizationHealthCheckRequestLabel(), 10),
+ VisualizationLabelDto.of(
+ properties.getProgressVisualizationReadinessCheckRequestLabel(), 30),
+ VisualizationLabelDto.of(
+ properties.getProgressVisualizationSecretsAcquireRequestLabel(), 50),
+ VisualizationLabelDto.of(properties.getProgressVisualizationStateRequestLabel(), 90),
+ VisualizationLabelDto.of(
+ properties.getProgressVisualizationStateResponseLabel(), 100)));
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public boolean isEmpty() {
+ return stepsQueue.isEmpty();
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public boolean isNext() {
+ mutex.lock();
+
+ try {
+ return !batchQueue.isEmpty();
+ } finally {
+ mutex.unlock();
+ }
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public void pushNext() {
+ mutex.lock();
+
+ batchQueue.push(stepsQueue.pop().toString());
+
+ mutex.unlock();
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public String getCurrent() {
+ mutex.lock();
+
+ try {
+ return batchQueue.pollLast();
+ } finally {
+ mutex.unlock();
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/visualization/common/label/StopCommandVisualizationLabel.java b/cli/src/main/java/com/repoachiever/service/visualization/common/label/StopCommandVisualizationLabel.java
new file mode 100644
index 0000000..136b59d
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/visualization/common/label/StopCommandVisualizationLabel.java
@@ -0,0 +1,80 @@
+package com.repoachiever.service.visualization.common.label;
+
+import com.repoachiever.dto.VisualizationLabelDto;
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.service.visualization.common.IVisualizationLabel;
+import java.util.ArrayDeque;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents label set used for destroy command service. */
+@Service
+public class StopCommandVisualizationLabel implements IVisualizationLabel {
+ private final ArrayDeque stepsQueue = new ArrayDeque<>();
+
+ private final ArrayDeque batchQueue = new ArrayDeque<>();
+
+ private final ReentrantLock mutex = new ReentrantLock();
+
+ public StopCommandVisualizationLabel(@Autowired PropertiesEntity properties) {
+ stepsQueue.addAll(
+ List.of(
+ VisualizationLabelDto.of(
+ properties.getProgressVisualizationHealthCheckRequestLabel(), 10),
+ VisualizationLabelDto.of(
+ properties.getProgressVisualizationSecretsAcquireRequestLabel(), 50),
+ VisualizationLabelDto.of(properties.getProgressVisualizationDestroyRequestLabel(), 90),
+ VisualizationLabelDto.of(
+ properties.getProgressVisualizationDestroyResponseLabel(), 100)));
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public boolean isEmpty() {
+ return stepsQueue.isEmpty();
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public boolean isNext() {
+ mutex.lock();
+
+ try {
+ return !batchQueue.isEmpty();
+ } finally {
+ mutex.unlock();
+ }
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public void pushNext() {
+ mutex.lock();
+
+ batchQueue.push(stepsQueue.pop().toString());
+
+ mutex.unlock();
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public String getCurrent() {
+ mutex.lock();
+
+ try {
+ return batchQueue.pollLast();
+ } finally {
+ mutex.unlock();
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/visualization/common/label/VersionCommandVisualizationLabel.java b/cli/src/main/java/com/repoachiever/service/visualization/common/label/VersionCommandVisualizationLabel.java
new file mode 100644
index 0000000..3b88448
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/visualization/common/label/VersionCommandVisualizationLabel.java
@@ -0,0 +1,77 @@
+package com.repoachiever.service.visualization.common.label;
+
+import com.repoachiever.dto.VisualizationLabelDto;
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.service.visualization.common.IVisualizationLabel;
+import java.util.ArrayDeque;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents label set used for version command service. */
+@Service
+public class VersionCommandVisualizationLabel implements IVisualizationLabel {
+ private final ArrayDeque stepsQueue = new ArrayDeque<>();
+
+ private final ArrayDeque batchQueue = new ArrayDeque<>();
+
+ private final ReentrantLock mutex = new ReentrantLock();
+
+ public VersionCommandVisualizationLabel(@Autowired PropertiesEntity properties) {
+ stepsQueue.addAll(
+ List.of(
+ VisualizationLabelDto.of(
+ properties.getProgressVisualizationHealthCheckRequestLabel(), 10),
+ VisualizationLabelDto.of(
+ properties.getProgressVisualizationVersionInfoRequestLabel(), 100)));
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public boolean isEmpty() {
+ return stepsQueue.isEmpty();
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public boolean isNext() {
+ mutex.lock();
+
+ try {
+ return !batchQueue.isEmpty();
+ } finally {
+ mutex.unlock();
+ }
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public void pushNext() {
+ mutex.lock();
+
+ batchQueue.push(stepsQueue.pop().toString());
+
+ mutex.unlock();
+ }
+
+ /**
+ * @see IVisualizationLabel
+ */
+ @Override
+ public String getCurrent() {
+ mutex.lock();
+
+ try {
+ return batchQueue.pollLast();
+ } finally {
+ mutex.unlock();
+ }
+ }
+}
diff --git a/cli/src/main/java/com/repoachiever/service/visualization/state/VisualizationState.java b/cli/src/main/java/com/repoachiever/service/visualization/state/VisualizationState.java
new file mode 100644
index 0000000..a8b6b0f
--- /dev/null
+++ b/cli/src/main/java/com/repoachiever/service/visualization/state/VisualizationState.java
@@ -0,0 +1,28 @@
+package com.repoachiever.service.visualization.state;
+
+import com.repoachiever.service.visualization.common.IVisualizationLabel;
+import java.util.*;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.stereotype.Service;
+
+/**
+ * Represents general visualization state used to gather output values to be processed by
+ * visualization service.
+ */
+@Getter
+@Service
+public class VisualizationState {
+ @Setter private IVisualizationLabel label;
+
+ private final List result = new ArrayList<>();
+
+ /**
+ * Adds given message to the result array.
+ *
+ * @param message given message to be added to result array.
+ */
+ public void addResult(String message) {
+ result.add(message);
+ }
+}
diff --git a/cli/src/main/resources/application.properties b/cli/src/main/resources/application.properties
new file mode 100644
index 0000000..07e434b
--- /dev/null
+++ b/cli/src/main/resources/application.properties
@@ -0,0 +1,43 @@
+# Describes Spring related properties.
+spring.main.banner-mode=off
+spring.main.web-application-type=NONE
+
+# Describes the path and name of a configuration file.
+config.root=.resourcetracker/config
+config.user.file=user.yaml
+
+# Describes visualizer state update period
+progress.visualization.period=1000
+
+# Describes visualization label used for secrets validation request process.
+progress.visualization.secrets-acquire-request=Checking if the given cloud credentials are valid
+
+# Describes visualization label used for script validation request process.
+progress.visualization.script-acquire-request=Checking if the given script is allowed to be used
+
+# Describes visualization label used for infrastructure deployment request process.
+progress.visualization.apply-request=Sending infrastructure deployment request
+
+# Describes visualization label used for infrastructure deployment response.
+progress.visualization.apply-response=Application of deployment infrastructure has been completed
+
+# Describes visualization label used for infrastructure destruction request process.
+progress.visualization.destroy-request=Sending infrastructure destruction request
+
+# Describes visualization label used for infrastructure destruction response.
+progress.visualization.destroy-response=Destruction of deployed infrastructure has been completed
+
+# Describes visualization label used for general state retrieval request process.
+progress.visualization.state-request=Sending general state retrieval request
+
+# Describes visualization label used for general state retrieval response.
+progress.visualization.state-response=General state has been retrieved successfully
+
+# Describes visualization label used for version request process.
+progress.visualization.version-info-request=Sending request to API Server to retrieve its version information
+
+# Describes visualization label used for health check request.
+progress.visualization.health-check-request=Sending health check request
+
+# Describes visualization label used for readiness check request.
+progress.visualization.readiness-check-request=Sending readiness check request
\ No newline at end of file
diff --git a/cli/src/main/resources/log4j2.xml b/cli/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..e47463a
--- /dev/null
+++ b/cli/src/main/resources/log4j2.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+ %d %p %c{1.} [%t] %m%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cluster/pom.xml b/cluster/pom.xml
new file mode 100644
index 0000000..c8c484a
--- /dev/null
+++ b/cluster/pom.xml
@@ -0,0 +1,176 @@
+
+ 4.0.0
+ cluster
+ 1.0-SNAPSHOT
+ cluster
+ Worker for RepoAchiever
+
+
+ com.repoachiever
+ base
+ 1.0-SNAPSHOT
+
+
+
+ true
+ allow
+
+ com.repoachiever.Cluster
+
+
+
+
+
+
+ Shell-Command-Executor-Lib
+ Shell-Command-Executor-Lib
+ system
+ ${basedir}/../lib/Shell-Command-Executor-Lib-0.5.0-SNAPSHOT.jar
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-log4j2
+
+
+ org.springframework.integration
+ spring-integration-rmi
+
+
+ org.springframework
+ spring-remoting
+
+
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+
+
+ jakarta.validation
+ jakarta.validation-api
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+
+
+ org.projectlombok
+ lombok
+
+
+ org.yaml
+ snakeyaml
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+
+
+ commons-io
+ commons-io
+
+
+ com.google.guava
+ guava
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+
+
+ junit
+ junit
+
+
+
+
+ cluster
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+
+
+
+ com.google.cloud.tools
+ jib-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 19
+
+
+
+ com.coderplus.maven.plugins
+ copy-rename-maven-plugin
+
+
+
+ ${basedir}/target/cluster.jar
+ ${main.basedir}/../bin/cluster/cluster.jar
+
+
+
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+
+
+
+
+
+
+ dev
+
+ true
+
+
+ dev
+
+
+
+ prod
+
+ prod
+
+
+
+
diff --git a/cluster/src/main/java/com/repoachiever/App.java b/cluster/src/main/java/com/repoachiever/App.java
new file mode 100644
index 0000000..6a011e4
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/App.java
@@ -0,0 +1,37 @@
+package com.repoachiever;
+
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.service.apiserver.resource.ApiServerCommunicationResource;
+import com.repoachiever.service.config.ConfigService;
+import com.repoachiever.service.integration.communication.cluster.ClusterCommunicationConfigService;
+import com.repoachiever.service.integration.logging.state.LoggingStateService;
+import com.repoachiever.service.executor.CommandExecutorService;
+import com.repoachiever.service.integration.logging.transfer.LoggingTransferService;
+import com.repoachiever.service.waiter.WaiterHelper;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.context.annotation.Import;
+import org.springframework.stereotype.Component;
+
+/**
+ * Represents initialization point for the RepoAchiever Cluster application.
+ */
+@Component
+@Import({
+ ConfigService.class,
+ CommandExecutorService.class,
+ PropertiesEntity.class,
+ ClusterCommunicationConfigService.class,
+ ApiServerCommunicationResource.class,
+ LoggingStateService.class,
+ LoggingTransferService.class
+})
+public class App implements ApplicationRunner {
+ /**
+ * @see ApplicationRunner
+ */
+ @Override
+ public void run(ApplicationArguments args) {
+ WaiterHelper.waitForExit();
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/Cluster.java b/cluster/src/main/java/com/repoachiever/Cluster.java
new file mode 100644
index 0000000..ef757f8
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/Cluster.java
@@ -0,0 +1,15 @@
+package com.repoachiever;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Represents entry point for the RepoAchiever Cluster application.
+ */
+@SpringBootApplication
+public class Cluster {
+ public static void main(String[] args) {
+ SpringApplication application = new SpringApplication(App.class);
+ System.exit(SpringApplication.exit(application.run(args)));
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/converter/CronExpressionConverter.java b/cluster/src/main/java/com/repoachiever/converter/CronExpressionConverter.java
new file mode 100644
index 0000000..24b4451
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/converter/CronExpressionConverter.java
@@ -0,0 +1,28 @@
+package com.repoachiever.converter;
+
+import com.repoachiever.exception.CronExpressionException;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.Objects;
+import org.springframework.scheduling.support.CronExpression;
+
+/**
+ * Represents converter used for cron expression parsing operation.
+ */
+public class CronExpressionConverter {
+ /**
+ * Converts frequency from cron expression to milliseconds.
+ *
+ * @param src cron expression to be converted
+ * @return frequency in milliseconds
+ */
+ public static Long convert(String src) throws CronExpressionException {
+ CronExpression cronExpression = CronExpression.parse(src);
+ LocalDateTime nextExecutionTime = cronExpression.next(LocalDateTime.now());
+ if (Objects.isNull(nextExecutionTime)) {
+ throw new CronExpressionException();
+ }
+ LocalDateTime afterNextExecutionTime = cronExpression.next(nextExecutionTime);
+ return Duration.between(nextExecutionTime, afterNextExecutionTime).toMillis();
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/dto/CommandExecutorOutputDto.java b/cluster/src/main/java/com/repoachiever/dto/CommandExecutorOutputDto.java
new file mode 100644
index 0000000..0051755
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/dto/CommandExecutorOutputDto.java
@@ -0,0 +1,13 @@
+package com.repoachiever.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/** Represents gathered output of the executed command. */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class CommandExecutorOutputDto {
+ private String normalOutput;
+
+ private String errorOutput;
+}
diff --git a/cluster/src/main/java/com/repoachiever/entity/ConfigEntity.java b/cluster/src/main/java/com/repoachiever/entity/ConfigEntity.java
new file mode 100644
index 0000000..790ce5f
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/entity/ConfigEntity.java
@@ -0,0 +1,170 @@
+package com.repoachiever.entity;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
+import java.util.List;
+
+import jakarta.validation.constraints.Pattern;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Service used to perform RepoAchiever Cluster processing operation.
+ */
+@Getter
+public class ConfigEntity {
+ /**
+ * Contains metadata for a specific RepoAchiever Cluster allocation.
+ */
+ @Getter
+ public static class Metadata {
+ @JsonProperty("name")
+ public String name;
+
+ @JsonProperty("workspace_unit_key")
+ public String workspaceUnitKey;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("metadata")
+ public Metadata metadata;
+
+ /**
+ * Represents filter section elected for a specific RepoAchiever Cluster allocation.
+ */
+ @Getter
+ public static class Filter {
+ @JsonProperty("locations")
+ public List locations;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("filter")
+ public Filter filter;
+
+ /**
+ * Represents external service configurations for RepoAchiever Cluster allocation used to retrieve content.
+ */
+ @Getter
+ public static class Service {
+ /**
+ * Represents all supported service providers, which can be used by RepoAchiever Cluster allocation.
+ */
+ public enum Provider {
+ LOCAL("git-local"),
+ GITHUB("git-github");
+
+ private final String value;
+
+ Provider(String value) {
+ this.value = value;
+ }
+
+ public String toString() {
+ return value;
+ }
+ }
+
+ @JsonProperty("provider")
+ public Provider provider;
+
+ /**
+ * Represents credentials used for external service communication by RepoAchiever Cluster allocation.
+ */
+ @Getter
+ public static class Credentials {
+ @JsonProperty("token")
+ public String token;
+ }
+
+ @JsonProperty("credentials")
+ public Credentials credentials;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("service")
+ public Service service;
+
+ /** Represents RepoAchiever Cluster configuration used for internal communication infrastructure setup. */
+ @Getter
+ public static class Communication {
+ @NotNull
+ @JsonProperty("api_server_name")
+ public String apiServerName;
+
+ @NotNull
+ @JsonProperty("port")
+ public Integer port;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("communication")
+ public Communication communication;
+
+ /** Represents RepoAchiever Cluster configuration used for content management. */
+ @Getter
+ public static class Content {
+ @NotNull
+ @Pattern(regexp = "(^zip$)|(^tar$)")
+ @JsonProperty("format")
+ public String format;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("content")
+ public Content content;
+
+ /**
+ * Represents RepoAchiever API Server resources configuration section.
+ */
+ @Getter
+ public static class Resource {
+ /**
+ * Represents RepoAchiever API Server configuration used for RepoAchiever Cluster.
+ */
+ @Getter
+ public static class Cluster {
+ @NotNull
+ @JsonProperty("max-workers")
+ public Integer maxWorkers;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("cluster")
+ public Cluster cluster;
+
+ /**
+ * Represents RepoAchiever API Server configuration used for RepoAchiever Worker.
+ */
+ @Getter
+ public static class Worker {
+ @NotNull
+ @Pattern(
+ regexp =
+ "(((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)|(([\\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))|([\\?])|([\\*]))[\\s](((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)|(([\\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))|([\\?])|([\\*]))[\\s](((([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?,)*([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?)|(([\\*]|[0-9]|[0-1][0-9]|[2][0-3])/([0-9]|[0-1][0-9]|[2][0-3]))|([\\?])|([\\*]))[\\s](((([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?,)*([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?(C)?)|(([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])/([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(C)?)|(L(-[0-9])?)|(L(-[1-2][0-9])?)|(L(-[3][0-1])?)|(LW)|([1-9]W)|([1-3][0-9]W)|([\\?])|([\\*]))[\\s](((([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?,)*([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?)|(([1-9]|0[1-9]|1[0-2])/([1-9]|0[1-9]|1[0-2]))|(((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?,)*(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)|((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)/(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))|([\\?])|([\\*]))[\\s]((([1-7](-([1-7]))?,)*([1-7])(-([1-7]))?)|([1-7]/([1-7]))|(((MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?,)*(MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?(C)?)|((MON|TUE|WED|THU|FRI|SAT|SUN)/(MON|TUE|WED|THU|FRI|SAT|SUN)(C)?)|(([1-7]|(MON|TUE|WED|THU|FRI|SAT|SUN))?(L|LW)?)|(([1-7]|MON|TUE|WED|THU|FRI|SAT|SUN)#([1-7])?)|([\\?])|([\\*]))([\\s]?(([\\*])?|(19[7-9][0-9])|(20[0-9][0-9]))?|"
+ + " (((19[7-9][0-9])|(20[0-9][0-9]))/((19[7-9][0-9])|(20[0-9][0-9])))?|"
+ + " ((((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?,)*((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?)?)")
+ @JsonProperty("frequency")
+ public String frequency;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("worker")
+ public Worker worker;
+ }
+
+ @Valid
+ @NotNull
+ @JsonProperty("resource")
+ public Resource resource;
+}
diff --git a/cluster/src/main/java/com/repoachiever/entity/PropertiesEntity.java b/cluster/src/main/java/com/repoachiever/entity/PropertiesEntity.java
new file mode 100644
index 0000000..223d3af
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/entity/PropertiesEntity.java
@@ -0,0 +1,56 @@
+package com.repoachiever.entity;
+
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+import org.springframework.core.io.ClassPathResource;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Exposes access to properties setup to be used for further configuration.
+ */
+@Getter
+@Configuration
+public class PropertiesEntity {
+ private static final String GIT_CONFIG_PROPERTIES_FILE = "git.properties";
+
+ @Value(value = "${REPOACHIEVER_CLUSTER_CONTEXT:null}")
+ private String clusterContext;
+
+ @Value(value = "${logging.transfer.frequency}")
+ private Integer loggingTransferFrequency;
+
+ @Value(value = "${logging.state.frequency}")
+ private Integer loggingStateFrequency;
+
+ @Value(value = "${logging.state-finalizer.frequency}")
+ private Integer loggingStateFinalizerFrequency;
+
+ @Value(value = "${git.commit.id.abbrev}")
+ private String gitCommitId;
+
+ /**
+ * Adds custom properties to resource configurations.
+ *
+ * @return modified property sources configurer.
+ */
+ @Bean
+ private static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
+ PropertySourcesPlaceholderConfigurer propsConfig = new PropertySourcesPlaceholderConfigurer();
+ propsConfig.setLocation(new ClassPathResource(GIT_CONFIG_PROPERTIES_FILE));
+ propsConfig.setIgnoreResourceNotFound(true);
+ propsConfig.setIgnoreUnresolvablePlaceholders(true);
+ return propsConfig;
+ }
+
+ /**
+ * Removes the last symbol in git commit id of the repository.
+ *
+ * @return chopped repository git commit id.
+ */
+ public String getGitCommitId() {
+ return StringUtils.chop(gitCommitId);
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/exception/ApiServerOperationFailureException.java b/cluster/src/main/java/com/repoachiever/exception/ApiServerOperationFailureException.java
new file mode 100644
index 0000000..3376590
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/exception/ApiServerOperationFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when RepoAchiever API Server operation fails.
+ */
+public class ApiServerOperationFailureException extends IOException {
+ public ApiServerOperationFailureException() {
+ this("");
+ }
+
+ public ApiServerOperationFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("RepoAchiever API Server operation failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/exception/CommandExecutorException.java b/cluster/src/main/java/com/repoachiever/exception/CommandExecutorException.java
new file mode 100644
index 0000000..1033e07
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/exception/CommandExecutorException.java
@@ -0,0 +1,17 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used to indicate command executor failure.
+ */
+public class CommandExecutorException extends IOException {
+ public CommandExecutorException(Object... message) {
+ super(
+ new Formatter()
+ .format("Invalid command executor behaviour: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/exception/CommunicationConfigurationFailureException.java b/cluster/src/main/java/com/repoachiever/exception/CommunicationConfigurationFailureException.java
new file mode 100644
index 0000000..a6672f1
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/exception/CommunicationConfigurationFailureException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when communication configuration fails.
+ */
+public class CommunicationConfigurationFailureException extends IOException {
+ public CommunicationConfigurationFailureException() {
+ this("");
+ }
+
+ public CommunicationConfigurationFailureException(Object... message) {
+ super(
+ new Formatter()
+ .format("Communication configuration operation failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/exception/ConfigNotGivenException.java b/cluster/src/main/java/com/repoachiever/exception/ConfigNotGivenException.java
new file mode 100644
index 0000000..8ad7455
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/exception/ConfigNotGivenException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when config file is not provided.
+ */
+public class ConfigNotGivenException extends IOException {
+ public ConfigNotGivenException() {
+ this("");
+ }
+
+ public ConfigNotGivenException(Object... message) {
+ super(
+ new Formatter()
+ .format("Config file is not given: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
\ No newline at end of file
diff --git a/cluster/src/main/java/com/repoachiever/exception/ConfigValidationException.java b/cluster/src/main/java/com/repoachiever/exception/ConfigValidationException.java
new file mode 100644
index 0000000..aa2f396
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/exception/ConfigValidationException.java
@@ -0,0 +1,21 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used when config file is not valid.
+ */
+public class ConfigValidationException extends IOException {
+ public ConfigValidationException() {
+ this("");
+ }
+
+ public ConfigValidationException(Object... message) {
+ super(
+ new Formatter()
+ .format("Config file content is not valid: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/exception/CronExpressionException.java b/cluster/src/main/java/com/repoachiever/exception/CronExpressionException.java
new file mode 100644
index 0000000..d2befeb
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/exception/CronExpressionException.java
@@ -0,0 +1,17 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/**
+ * Represents exception used to indicate cron expression conversion failure.
+ */
+public class CronExpressionException extends IOException {
+ public CronExpressionException(Object... message) {
+ super(
+ new Formatter()
+ .format("Invalid cron exception: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/logging/FatalAppender.java b/cluster/src/main/java/com/repoachiever/logging/FatalAppender.java
new file mode 100644
index 0000000..e7a1df3
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/logging/FatalAppender.java
@@ -0,0 +1,36 @@
+package com.repoachiever.logging;
+
+import com.repoachiever.service.state.StateService;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * Service used for logging fatal level application state changes.
+ */
+@Plugin(name = "fatalappender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
+public class FatalAppender extends AbstractAppender {
+ protected FatalAppender(String name, Filter filter) {
+ super(name, filter, null, false, null);
+ }
+
+ @PluginFactory
+ public static FatalAppender createAppender(
+ @PluginAttribute("name") String name, @PluginElement("Filter") Filter filter) {
+ return new FatalAppender(name, filter);
+ }
+
+ @Override
+ public void append(LogEvent event) {
+ if (event.getLevel().equals(Level.FATAL)) {
+ StateService.setExit(true);
+ }
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/logging/TransferAppender.java b/cluster/src/main/java/com/repoachiever/logging/TransferAppender.java
new file mode 100644
index 0000000..393c5d2
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/logging/TransferAppender.java
@@ -0,0 +1,39 @@
+package com.repoachiever.logging;
+
+import com.repoachiever.logging.common.LoggingConfigurationHelper;
+import com.repoachiever.service.state.StateService;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * Service used for logging message transfer to RepoAchiever API Server allocation.
+ */
+@Plugin(name = "transferappender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
+public class TransferAppender extends AbstractAppender {
+ protected TransferAppender(String name, Filter filter) {
+ super(name, filter, null, false, null);
+ }
+
+ @PluginFactory
+ public static TransferAppender createAppender(
+ @PluginAttribute("name") String name, @PluginElement("Filter") Filter filter) {
+ return new TransferAppender(name, filter);
+ }
+
+ @Override
+ public void append(LogEvent event) {
+ String message = event.getMessage().getFormattedMessage();
+
+ if (LoggingConfigurationHelper.isMessageTransferable(message)) {
+ StateService.addLogMessage(
+ LoggingConfigurationHelper.extractTransferableMessage(message));
+ }
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/logging/common/LoggingConfigurationHelper.java b/cluster/src/main/java/com/repoachiever/logging/common/LoggingConfigurationHelper.java
new file mode 100644
index 0000000..a6d1f00
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/logging/common/LoggingConfigurationHelper.java
@@ -0,0 +1,38 @@
+package com.repoachiever.logging.common;
+
+/**
+ * Contains helpful tools used for logging configuration.
+ */
+public class LoggingConfigurationHelper {
+ private static final String TRANSFERABLE_MESSAGE_PREFIX = "!transferable!";
+
+ /**
+ * Checks if the given log message contains given prefix.
+ *
+ * @param message given log message.
+ * @return result of the check.
+ */
+ public static Boolean isMessageTransferable(String message) {
+ return message.contains(TRANSFERABLE_MESSAGE_PREFIX);
+ }
+
+ /**
+ * Formats transferable message with the given prefix.
+ *
+ * @param message given formatted transferable log message.
+ * @return formatted transferable message.
+ */
+ public static String extractTransferableMessage(String message) {
+ return message.replaceAll(TRANSFERABLE_MESSAGE_PREFIX, "");
+ }
+
+ /**
+ * Formats transferable message with the given prefix.
+ *
+ * @param message given log message.
+ * @return formatted transferable message.
+ */
+ public static String getTransferableMessage(String message) {
+ return String.format("%s %s", TRANSFERABLE_MESSAGE_PREFIX, message);
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/resource/communication/ClusterCommunicationResource.java b/cluster/src/main/java/com/repoachiever/resource/communication/ClusterCommunicationResource.java
new file mode 100644
index 0000000..7408b2c
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/resource/communication/ClusterCommunicationResource.java
@@ -0,0 +1,56 @@
+package com.repoachiever.resource.communication;
+
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.service.communication.cluster.IClusterCommunicationService;
+import com.repoachiever.service.state.StateService;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+/**
+ * Contains implementation of communication provider for RepoAchiever Cluster.
+ */
+public class ClusterCommunicationResource extends UnicastRemoteObject implements IClusterCommunicationService {
+ private final PropertiesEntity properties;
+
+ public ClusterCommunicationResource(PropertiesEntity properties) throws RemoteException {
+ this.properties = properties;
+ }
+
+ /**
+ * @see IClusterCommunicationService
+ */
+ @Override
+ public void performSuspend() throws RemoteException {
+ StateService.setSuspended(true);
+ }
+
+ /**
+ * @see IClusterCommunicationService
+ */
+ @Override
+ public void performServe() throws RemoteException {
+ StateService.setSuspended(false);
+ }
+
+ /**
+ * @see IClusterCommunicationService
+ */
+ @Override
+ public Boolean retrieveHealthCheck() throws RemoteException {
+ return true;
+ }
+
+ /**
+ * @see IClusterCommunicationService
+ */
+ @Override
+ public String retrieveVersion() throws RemoteException {
+ return properties.getGitCommitId();
+ }
+
+ @Override
+ public Integer retrieveWorkerAmount() throws RemoteException {
+ return 10;
+ }
+}
\ No newline at end of file
diff --git a/cluster/src/main/java/com/repoachiever/service/apiserver/resource/ApiServerCommunicationResource.java b/cluster/src/main/java/com/repoachiever/service/apiserver/resource/ApiServerCommunicationResource.java
new file mode 100644
index 0000000..d2ab784
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/service/apiserver/resource/ApiServerCommunicationResource.java
@@ -0,0 +1,128 @@
+package com.repoachiever.service.apiserver.resource;
+
+import com.repoachiever.exception.ApiServerOperationFailureException;
+import com.repoachiever.exception.CommunicationConfigurationFailureException;
+import com.repoachiever.service.communication.apiserver.IApiServerCommunicationService;
+import com.repoachiever.service.communication.common.CommunicationProviderConfigurationHelper;
+import com.repoachiever.service.config.ConfigService;
+import jakarta.annotation.PostConstruct;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.InputStream;
+import java.rmi.NotBoundException;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.util.Arrays;
+
+/**
+ * Represents implementation for RepoAchiever API Server remote API.
+ */
+@Service
+public class ApiServerCommunicationResource {
+ private static final Logger logger = LogManager.getLogger(ApiServerCommunicationResource.class);
+
+ @Autowired
+ private ConfigService configService;
+
+ private Registry registry;
+
+ @PostConstruct
+ private void configure() {
+ try {
+ this.registry = LocateRegistry.getRegistry(
+ configService.getConfig().getCommunication().getPort());
+ } catch (RemoteException e) {
+ logger.fatal(new CommunicationConfigurationFailureException(e.getMessage()).getMessage());
+ }
+ }
+
+ /**
+ * Retrieves remote RepoAchiever API Server allocation.
+ *
+ * @return retrieved RepoAchiever API Server allocation.
+ * @throws ApiServerOperationFailureException if RepoAchiever API Server operation fails.
+ */
+ private IApiServerCommunicationService retrieveAllocation() throws ApiServerOperationFailureException {
+ try {
+ return (IApiServerCommunicationService) registry.lookup(
+ CommunicationProviderConfigurationHelper.getBindName(
+ configService.getConfig().getCommunication().getPort(),
+ configService.getConfig().getCommunication().getApiServerName()));
+ } catch (RemoteException | NotBoundException e) {
+ throw new ApiServerOperationFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Performs raw content upload operation.
+ *
+ * @param content given content to be uploaded.
+ * @throws ApiServerOperationFailureException if RepoAchiever API Server operation fails.
+ */
+ public void performRawContentUpload(InputStream content) throws ApiServerOperationFailureException {
+ IApiServerCommunicationService allocation = retrieveAllocation();
+
+ try {
+ allocation.performRawContentUpload(
+ configService.getConfig().getMetadata().getWorkspaceUnitKey(),
+ content);
+ } catch (RemoteException e) {
+ throw new ApiServerOperationFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Performs additional content(issues, prs, releases) upload operation, initiated by RepoAchiever Cluster.
+ *
+ * @param content given content to be uploaded.
+ * @throws ApiServerOperationFailureException if RepoAchiever API Server operation fails.
+ */
+ public void performAdditionalContentUpload(String content) throws ApiServerOperationFailureException {
+ IApiServerCommunicationService allocation = retrieveAllocation();
+
+ try {
+ allocation.performAdditionalContentUpload(
+ configService.getConfig().getMetadata().getWorkspaceUnitKey(),
+ content);
+ } catch (RemoteException e) {
+ throw new ApiServerOperationFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Handles incoming log messages related to the RepoAchiever Cluster allocation.
+ *
+ * @param message given RepoAchiever Cluster log message.
+ * @throws ApiServerOperationFailureException if RepoAchiever API Server operation fails.
+ */
+ public void performLogsTransfer(String message) throws ApiServerOperationFailureException {
+ IApiServerCommunicationService allocation = retrieveAllocation();
+
+ try {
+ allocation.performLogsTransfer(
+ configService.getConfig().getMetadata().getName(), message);
+ } catch (RemoteException e) {
+ throw new ApiServerOperationFailureException(e.getMessage());
+ }
+ }
+
+ /**
+ * Retrieves health check status of RepoAchiever API Server allocation.
+ *
+ * @return result of the check.
+ * @throws ApiServerOperationFailureException if RepoAchiever API Server operation fails.
+ */
+ public Boolean retrieveHealthCheck() throws ApiServerOperationFailureException {
+ IApiServerCommunicationService allocation = retrieveAllocation();
+
+ try {
+ return allocation.retrieveHealthCheck();
+ } catch (RemoteException e) {
+ throw new ApiServerOperationFailureException(e.getMessage());
+ }
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/service/communication/apiserver/IApiServerCommunicationService.java b/cluster/src/main/java/com/repoachiever/service/communication/apiserver/IApiServerCommunicationService.java
new file mode 100644
index 0000000..e7e7a15
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/service/communication/apiserver/IApiServerCommunicationService.java
@@ -0,0 +1,45 @@
+package com.repoachiever.service.communication.apiserver;
+
+import java.io.InputStream;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Represents communication provider for RepoAchiever API Server.
+ */
+public interface IApiServerCommunicationService extends Remote {
+ /**
+ * Performs raw content upload operation, initiated by RepoAchiever Cluster.
+ *
+ * @param workspaceUnitKey given user workspace unit key.
+ * @param content given content to be uploaded.
+ * @throws RemoteException if remote request fails.
+ */
+ void performRawContentUpload(String workspaceUnitKey, InputStream content) throws RemoteException;
+
+ /**
+ * Performs additional content(issues, prs, releases) upload operation, initiated by RepoAchiever Cluster.
+ *
+ * @param workspaceUnitKey given user workspace unit key.
+ * @param content given content to be uploaded.
+ * @throws RemoteException if remote request fails.
+ */
+ void performAdditionalContentUpload(String workspaceUnitKey, String content) throws RemoteException;
+
+ /**
+ * Handles incoming log messages related to the given RepoAchiever Cluster allocation.
+ *
+ * @param name given RepoAchiever Cluster allocation name.
+ * @param message given RepoAchiever Cluster log message.
+ * @throws RemoteException if remote request fails.
+ */
+ void performLogsTransfer(String name, String message) throws RemoteException;
+
+ /**
+ * Retrieves latest RepoAchiever API Server health check states.
+ *
+ * @return RepoAchiever API Server health check status.
+ * @throws RemoteException if remote request fails.
+ */
+ Boolean retrieveHealthCheck() throws RemoteException;
+}
\ No newline at end of file
diff --git a/cluster/src/main/java/com/repoachiever/service/communication/cluster/IClusterCommunicationService.java b/cluster/src/main/java/com/repoachiever/service/communication/cluster/IClusterCommunicationService.java
new file mode 100644
index 0000000..92fc69b
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/service/communication/cluster/IClusterCommunicationService.java
@@ -0,0 +1,50 @@
+package com.repoachiever.service.communication.cluster;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/** Represents client for RepoAchiever Cluster remote API. */
+public interface IClusterCommunicationService extends Remote {
+ /**
+ * Performs RepoAchiever Cluster suspend operation. Has no effect if RepoAchiever Cluster was
+ * already suspended previously.
+ *
+ * @throws RemoteException if remote request fails.
+ */
+ void performSuspend() throws RemoteException;
+
+ /**
+ * Performs RepoAchiever Cluster serve operation. Has no effect if RepoAchiever Cluster was not
+ * suspended previously.
+ *
+ * @throws RemoteException if remote request fails.
+ */
+ void performServe() throws RemoteException;
+
+ /**
+ * Retrieves latest RepoAchiever Cluster health check states.
+ *
+ * @return RepoAchiever Cluster health check status.
+ * @throws RemoteException if remote request fails.
+ */
+ Boolean retrieveHealthCheck() throws RemoteException;
+
+ /**
+ * Retrieves version of the allocated RepoAchiever Cluster instance allowing to confirm API
+ * compatability.
+ *
+ * @return RepoAchiever Cluster version.
+ * @throws RemoteException if remote request fails.
+ */
+ String retrieveVersion() throws RemoteException;
+
+ /**
+ * Retrieves amount of allocated workers.
+ *
+ * @return amount of allocated workers.
+ * @throws RemoteException if remote request fails.
+ */
+ Integer retrieveWorkerAmount() throws RemoteException;
+}
+
+// TODO: LOCATE ALL RMI RELATED CLASSES AT THE SAME PATH
\ No newline at end of file
diff --git a/cluster/src/main/java/com/repoachiever/service/communication/common/CommunicationProviderConfigurationHelper.java b/cluster/src/main/java/com/repoachiever/service/communication/common/CommunicationProviderConfigurationHelper.java
new file mode 100644
index 0000000..0a5462c
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/service/communication/common/CommunicationProviderConfigurationHelper.java
@@ -0,0 +1,15 @@
+package com.repoachiever.service.communication.common;
+
+/** Contains helpful tools used for communication provider configuration. */
+public class CommunicationProviderConfigurationHelper {
+ /**
+ * Composes binding URI declaration for RMI.
+ *
+ * @param registryPort given registry port.
+ * @param suffix given binding suffix.
+ * @return composed binding URI declaration for RMI.
+ */
+ public static String getBindName(Integer registryPort, String suffix) {
+ return String.format("//localhost:%d/%s", registryPort, suffix);
+ }
+}
\ No newline at end of file
diff --git a/cluster/src/main/java/com/repoachiever/service/config/ConfigService.java b/cluster/src/main/java/com/repoachiever/service/config/ConfigService.java
new file mode 100644
index 0000000..3f94c23
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/service/config/ConfigService.java
@@ -0,0 +1,106 @@
+package com.repoachiever.service.config;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.repoachiever.entity.ConfigEntity;
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.exception.ConfigNotGivenException;
+import com.repoachiever.exception.ConfigValidationException;
+import jakarta.annotation.PostConstruct;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.ValidatorFactory;
+import org.apache.commons.io.IOUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * Service used to perform RepoAchiever Cluster configuration processing operation.
+ */
+@Component
+public class ConfigService {
+ private static final Logger logger = LogManager.getLogger(ConfigService.class);
+
+ @Autowired
+ private PropertiesEntity properties;
+
+ private ConfigEntity parsedConfigFile;
+
+ /**
+ * Performs configuration file parsing operation.
+ */
+ @PostConstruct
+ private void configure() {
+ String clusterContext = properties.getClusterContext();
+
+ if (Objects.equals(clusterContext, "null")) {
+ logger.fatal(new ConfigNotGivenException().getMessage());
+ return;
+ }
+
+ InputStream configFile = null;
+
+ try {
+ configFile = IOUtils.toInputStream(clusterContext, "UTF-8");
+
+ ObjectMapper mapper =
+ new ObjectMapper(new JsonFactory())
+ .configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, true)
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
+ .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ ObjectReader reader = mapper.reader().forType(new TypeReference() {
+ });
+
+ try {
+ parsedConfigFile = reader.readValues(configFile).readAll().getFirst();
+ } catch (IOException e) {
+ logger.fatal(e.getMessage());
+ return;
+ }
+
+ try (ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory()) {
+ Validator validator = validatorFactory.getValidator();
+
+ Set> validationResult =
+ validator.validate(parsedConfigFile);
+
+ if (!validationResult.isEmpty()) {
+ logger.fatal(new ConfigValidationException(
+ validationResult.stream()
+ .map(ConstraintViolation::getMessage)
+ .collect(Collectors.joining(", "))).getMessage());
+ }
+ }
+ } finally {
+ try {
+ configFile.close();
+ } catch (IOException e) {
+ logger.fatal(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Retrieves parsed configuration file entity.
+ *
+ * @return retrieved parsed configuration file entity.
+ */
+ public ConfigEntity getConfig() {
+ return parsedConfigFile;
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/service/executor/CommandExecutorService.java b/cluster/src/main/java/com/repoachiever/service/executor/CommandExecutorService.java
new file mode 100644
index 0000000..8de0220
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/service/executor/CommandExecutorService.java
@@ -0,0 +1,59 @@
+package com.repoachiever.service.executor;
+
+import com.repoachiever.dto.CommandExecutorOutputDto;
+import com.repoachiever.exception.CommandExecutorException;
+import java.io.IOException;
+import org.springframework.stereotype.Service;
+import process.SProcess;
+import process.SProcessExecutor;
+import process.exceptions.NonMatchingOSException;
+import process.exceptions.SProcessNotYetStartedException;
+
+/** CommandExecutorService provides command execution service. */
+@Service
+public class CommandExecutorService {
+ private final SProcessExecutor processExecutor;
+
+ CommandExecutorService() {
+ this.processExecutor = SProcessExecutor.getCommandExecutor();
+ }
+
+ /**
+ * Executes given command and gathers its output.
+ *
+ * @param command command to be executed
+ * @return CommandExecutorOutputEntity output, which consists of both stdout and stderr
+ * @throws CommandExecutorException when command execution fails or output is not gathered
+ */
+ public CommandExecutorOutputDto executeCommand(SProcess command) throws CommandExecutorException {
+ try {
+ processExecutor.executeCommand(command);
+ } catch (IOException | NonMatchingOSException e) {
+ throw new CommandExecutorException(e.getMessage());
+ }
+
+ try {
+ command.waitForCompletion();
+ } catch (SProcessNotYetStartedException | InterruptedException e) {
+ throw new CommandExecutorException(e.getMessage());
+ }
+
+ String commandErrorOutput;
+
+ try {
+ commandErrorOutput = command.getErrorOutput();
+ } catch (SProcessNotYetStartedException e) {
+ throw new CommandExecutorException(e.getMessage());
+ }
+
+ String commandNormalOutput;
+
+ try {
+ commandNormalOutput = command.getNormalOutput();
+ } catch (SProcessNotYetStartedException e) {
+ throw new CommandExecutorException(e.getMessage());
+ }
+
+ return CommandExecutorOutputDto.of(commandNormalOutput, commandErrorOutput);
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/service/integration/communication/cluster/ClusterCommunicationConfigService.java b/cluster/src/main/java/com/repoachiever/service/integration/communication/cluster/ClusterCommunicationConfigService.java
new file mode 100644
index 0000000..9a6b8d0
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/service/integration/communication/cluster/ClusterCommunicationConfigService.java
@@ -0,0 +1,58 @@
+package com.repoachiever.service.integration.communication.cluster;
+
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.exception.CommunicationConfigurationFailureException;
+import com.repoachiever.resource.communication.ClusterCommunicationResource;
+import com.repoachiever.service.config.ConfigService;
+import com.repoachiever.service.communication.common.CommunicationProviderConfigurationHelper;
+import jakarta.annotation.PostConstruct;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+
+/**
+ * Service used to perform RepoAchiever Cluster communication provider configuration.
+ */
+@Component
+public class ClusterCommunicationConfigService {
+ private static final Logger logger = LogManager.getLogger(ClusterCommunicationConfigService.class);
+
+ @Autowired
+ private ConfigService configService;
+
+ @Autowired
+ private PropertiesEntity properties;
+
+ /**
+ * Performs setup of RepoAchiever Cluster communication provider.
+ */
+ @PostConstruct
+ private void process() {
+ Registry registry;
+
+ try {
+ registry = LocateRegistry.getRegistry(
+ configService.getConfig().getCommunication().getPort());
+ } catch (RemoteException e) {
+ logger.fatal(new CommunicationConfigurationFailureException(e.getMessage()).getMessage());
+ return;
+ }
+
+ Thread.ofPlatform().start(() -> {
+ try {
+ registry.rebind(
+ CommunicationProviderConfigurationHelper.getBindName(
+ configService.getConfig().getCommunication().getPort(),
+ configService.getConfig().getMetadata().getName()),
+ new ClusterCommunicationResource(properties));
+ } catch (RemoteException e) {
+ logger.fatal(e.getMessage());
+ }
+ });
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/service/integration/logging/state/LoggingStateService.java b/cluster/src/main/java/com/repoachiever/service/integration/logging/state/LoggingStateService.java
new file mode 100644
index 0000000..66e3bdf
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/service/integration/logging/state/LoggingStateService.java
@@ -0,0 +1,57 @@
+package com.repoachiever.service.integration.logging.state;
+
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.service.state.StateService;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.*;
+
+/**
+ * Service used to handle incoming logging related Ä…pplication state changes.
+ */
+@Component
+public class LoggingStateService {
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Autowired
+ private PropertiesEntity properties;
+
+ private final ScheduledExecutorService scheduledExecutorService =
+ Executors.newScheduledThreadPool(2);
+
+ /**
+ * Performs application exit if the required state has been changed.
+ */
+ @PostConstruct
+ private void process() {
+ scheduledExecutorService.scheduleAtFixedRate(() -> {
+ if (StateService.getExit()) {
+ CountDownLatch finalizer = new CountDownLatch(1);
+
+ ScheduledFuture> finalizerFeature =
+ scheduledExecutorService.scheduleAtFixedRate(() -> {
+ if (StateService.getLogMessagesQueue().isEmpty()) {
+ finalizer.countDown();
+ }
+ }, 0,
+ properties.getLoggingStateFinalizerFrequency(),
+ TimeUnit.MILLISECONDS);
+
+ try {
+ finalizer.await();
+ } catch (InterruptedException ignored) {
+ }
+
+ finalizerFeature.cancel(true);
+
+ ((ConfigurableApplicationContext) applicationContext).close();
+ System.exit(1);
+ }
+ }, 0, properties.getLoggingStateFrequency(), TimeUnit.MILLISECONDS);
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/service/integration/logging/transfer/LoggingTransferService.java b/cluster/src/main/java/com/repoachiever/service/integration/logging/transfer/LoggingTransferService.java
new file mode 100644
index 0000000..a7f4cde
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/service/integration/logging/transfer/LoggingTransferService.java
@@ -0,0 +1,53 @@
+package com.repoachiever.service.integration.logging.transfer;
+
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.exception.ApiServerOperationFailureException;
+import com.repoachiever.logging.common.LoggingConfigurationHelper;
+import com.repoachiever.service.apiserver.resource.ApiServerCommunicationResource;
+import com.repoachiever.service.state.StateService;
+import jakarta.annotation.PostConstruct;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.*;
+
+/**
+ * Service used to handle incoming logging messages to be transferred to RepoAchiever API Server allocation.
+ */
+@Component
+public class LoggingTransferService {
+ private static final Logger logger = LogManager.getLogger(LoggingTransferService.class);
+
+ @Autowired
+ private PropertiesEntity properties;
+
+ @Autowired
+ private ApiServerCommunicationResource apiServerCommunicationResource;
+
+ private final ScheduledExecutorService scheduledExecutorService =
+ Executors.newScheduledThreadPool(2);
+
+ /**
+ * Performs application logs transfer to RepoAchiever API Server allocation.
+ */
+ @PostConstruct
+ private void process() {
+ logger.info(LoggingConfigurationHelper.getTransferableMessage("it works"));
+
+ scheduledExecutorService.scheduleAtFixedRate(() -> {
+ if (!StateService.getExit()) {
+ while (!StateService.getLogMessagesQueue().isEmpty()) {
+ try {
+ apiServerCommunicationResource.performLogsTransfer(
+ StateService.getLogMessagesQueue().poll());
+
+ } catch (ApiServerOperationFailureException e) {
+ logger.fatal(e.getMessage());
+ }
+ }
+ }
+ }, 0, properties.getLoggingTransferFrequency(), TimeUnit.MILLISECONDS);
+ }
+}
\ No newline at end of file
diff --git a/cluster/src/main/java/com/repoachiever/service/state/StateService.java b/cluster/src/main/java/com/repoachiever/service/state/StateService.java
new file mode 100644
index 0000000..e41bdb6
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/service/state/StateService.java
@@ -0,0 +1,42 @@
+package com.repoachiever.service.state;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Service used to operate as a collection of application state properties.
+ */
+public class StateService {
+ /**
+ * Represents exit state used to indicate requested application shutdown.
+ */
+ @Getter
+ @Setter
+ private static Boolean exit = false;
+
+ /**
+ * Represents suspended state used to temporary halt execution of RepoAchiever Cluster allocation. By default
+ * RepoAchiever Cluster is considered to be suspended.
+ */
+ @Getter
+ @Setter
+ private static Boolean suspended = true;
+
+ /**
+ * Represents log message queue used to handle RepoAchiever API Server log message transfer.
+ */
+ @Getter
+ private final static ConcurrentLinkedQueue logMessagesQueue = new ConcurrentLinkedQueue<>();
+
+ /**
+ * Adds new log message to log message queue.
+ *
+ * @param message given log message to be added.
+ */
+ public static void addLogMessage(String message) {
+ logMessagesQueue.add(message);
+ }
+}
diff --git a/cluster/src/main/java/com/repoachiever/service/waiter/WaiterHelper.java b/cluster/src/main/java/com/repoachiever/service/waiter/WaiterHelper.java
new file mode 100644
index 0000000..ef8d99f
--- /dev/null
+++ b/cluster/src/main/java/com/repoachiever/service/waiter/WaiterHelper.java
@@ -0,0 +1,17 @@
+package com.repoachiever.service.waiter;
+
+import java.util.concurrent.CountDownLatch;
+
+/** Represents waiter helper for general usage. */
+public class WaiterHelper {
+ private static final CountDownLatch latch = new CountDownLatch(1);
+
+ /** Indefinitely waits for manual program execution. */
+ public static void waitForExit() {
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/cluster/src/main/resources/application.properties b/cluster/src/main/resources/application.properties
new file mode 100644
index 0000000..be1e7f5
--- /dev/null
+++ b/cluster/src/main/resources/application.properties
@@ -0,0 +1,12 @@
+# Describes Spring related properties.
+spring.main.banner-mode=off
+spring.main.web-application-type=NONE
+
+# Describes frequency used to perform logs transfers to RepoAchiever API Server allocation.
+logging.transfer.frequency=500
+
+# Describes frequency used to perform logging state check.
+logging.state.frequency=10
+
+# Describes frequency used to perform logging state finalizer check.
+logging.state-finalizer.frequency=10
\ No newline at end of file
diff --git a/cluster/src/main/resources/log4j2.xml b/cluster/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..026a4f3
--- /dev/null
+++ b/cluster/src/main/resources/log4j2.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/grafana/dashboards/dashboard.yml b/config/grafana/dashboards/dashboard.yml
new file mode 100644
index 0000000..60d2246
--- /dev/null
+++ b/config/grafana/dashboards/dashboard.yml
@@ -0,0 +1,11 @@
+apiVersion: 1
+
+providers:
+ - name: 'Default'
+ orgId: 1
+ folder: ''
+ type: file
+ disableDeletion: false
+ editable: true
+ options:
+ path: /etc/grafana/provisioning/dashboards
\ No newline at end of file
diff --git a/config/grafana/dashboards/diagnostics.tmpl b/config/grafana/dashboards/diagnostics.tmpl
new file mode 100644
index 0000000..1214948
--- /dev/null
+++ b/config/grafana/dashboards/diagnostics.tmpl
@@ -0,0 +1,1831 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": "-- Grafana --",
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "type": "dashboard"
+ }
+ ]
+ },
+ "description": "RepoAchiever API Server: ${(info.version)}",
+ "editable": true,
+ "gnetId": 179,
+ "graphTooltip": 1,
+ "id": 1,
+ "iteration": 1571330223815,
+ "links": [],
+ "panels": [
+ {
+ "collapsed": false,
+ "datasource": null,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 17,
+ "panels": [],
+ "title": "Host Info",
+ "type": "row"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": false,
+ "colorValue": false,
+ "colors": [
+ "#299c46",
+ "rgba(237, 129, 40, 0.89)",
+ "#d44a3a"
+ ],
+ "datasource": "Default",
+ "decimals": null,
+ "format": "s",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": false,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 3,
+ "x": 0,
+ "y": 1
+ },
+ "id": 15,
+ "interval": null,
+ "links": [],
+ "mappingType": 1,
+ "mappingTypes": [
+ {
+ "name": "value to text",
+ "value": 1
+ },
+ {
+ "name": "range to text",
+ "value": 2
+ }
+ ],
+ "maxDataPoints": 100,
+ "nullPointMode": "connected",
+ "nullText": null,
+ "options": {},
+ "postfix": "",
+ "postfixFontSize": "50%",
+ "prefix": "",
+ "prefixFontSize": "50%",
+ "rangeMaps": [
+ {
+ "from": "null",
+ "text": "N/A",
+ "to": "null"
+ }
+ ],
+ "sparkline": {
+ "fillColor": "rgba(31, 118, 189, 0.18)",
+ "full": false,
+ "lineColor": "rgb(31, 120, 193)",
+ "show": false
+ },
+ "tableColumn": "",
+ "targets": [
+ {
+ "expr": "time() - process_start_time_seconds{job=\"prometheus\"}",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "refId": "A"
+ }
+ ],
+ "thresholds": "",
+ "title": "Uptime",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ }
+ ],
+ "valueName": "current"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": false,
+ "colorValue": false,
+ "colors": [
+ "#299c46",
+ "rgba(237, 129, 40, 0.89)",
+ "#d44a3a"
+ ],
+ "datasource": "Default",
+ "format": "short",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": false,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 3,
+ "x": 3,
+ "y": 1
+ },
+ "id": 35,
+ "interval": null,
+ "links": [],
+ "mappingType": 1,
+ "mappingTypes": [
+ {
+ "name": "value to text",
+ "value": 1
+ },
+ {
+ "name": "range to text",
+ "value": 2
+ }
+ ],
+ "maxDataPoints": 100,
+ "nullPointMode": "connected",
+ "nullText": null,
+ "options": {},
+ "postfix": "",
+ "postfixFontSize": "50%",
+ "prefix": "",
+ "prefixFontSize": "50%",
+ "rangeMaps": [
+ {
+ "from": "null",
+ "text": "N/A",
+ "to": "null"
+ }
+ ],
+ "sparkline": {
+ "fillColor": "rgba(31, 118, 189, 0.18)",
+ "full": false,
+ "lineColor": "rgb(31, 120, 193)",
+ "show": false,
+ "ymax": null,
+ "ymin": null
+ },
+ "tableColumn": "",
+ "targets": [
+ {
+ "expr": "count(count(node_cpu_seconds_total{instance=~\"$node\", mode='system'}) by (cpu))",
+ "instant": true,
+ "refId": "A"
+ }
+ ],
+ "thresholds": "",
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "CPU Cores",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ }
+ ],
+ "valueName": "current"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": true,
+ "colorValue": false,
+ "colors": [
+ "#299c46",
+ "rgba(237, 129, 40, 0.89)",
+ "#d44a3a"
+ ],
+ "datasource": "Default",
+ "format": "none",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": false,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 5,
+ "x": 6,
+ "y": 1
+ },
+ "id": 13,
+ "interval": null,
+ "links": [],
+ "mappingType": 1,
+ "mappingTypes": [
+ {
+ "name": "value to text",
+ "value": 1
+ },
+ {
+ "name": "range to text",
+ "value": 2
+ }
+ ],
+ "maxDataPoints": 100,
+ "nullPointMode": "connected",
+ "nullText": null,
+ "options": {},
+ "postfix": "",
+ "postfixFontSize": "50%",
+ "prefix": "",
+ "prefixFontSize": "50%",
+ "rangeMaps": [
+ {
+ "from": "null",
+ "text": "N/A",
+ "to": "null"
+ }
+ ],
+ "sparkline": {
+ "fillColor": "rgba(31, 118, 189, 0.18)",
+ "full": false,
+ "lineColor": "rgb(31, 120, 193)",
+ "show": false
+ },
+ "tableColumn": "",
+ "targets": [
+ {
+ "expr": "sum(ALERTS)",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "refId": "A"
+ }
+ ],
+ "thresholds": "0,1",
+ "title": "Alerts",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "0"
+ }
+ ],
+ "valueName": "avg"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": true,
+ "colorValue": false,
+ "colors": [
+ "#d44a3a",
+ "rgba(237, 129, 40, 0.89)",
+ "#299c46"
+ ],
+ "datasource": "Default",
+ "format": "none",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": false,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 4,
+ "x": 11,
+ "y": 1
+ },
+ "id": 11,
+ "interval": null,
+ "links": [],
+ "mappingType": 1,
+ "mappingTypes": [
+ {
+ "name": "value to text",
+ "value": 1
+ },
+ {
+ "name": "range to text",
+ "value": 2
+ }
+ ],
+ "maxDataPoints": 100,
+ "nullPointMode": "connected",
+ "nullText": null,
+ "options": {},
+ "postfix": "",
+ "postfixFontSize": "50%",
+ "prefix": "",
+ "prefixFontSize": "50%",
+ "rangeMaps": [
+ {
+ "from": "null",
+ "text": "N/A",
+ "to": "null"
+ }
+ ],
+ "sparkline": {
+ "fillColor": "rgba(31, 118, 189, 0.18)",
+ "full": false,
+ "lineColor": "rgb(31, 120, 193)",
+ "show": false
+ },
+ "tableColumn": "",
+ "targets": [
+ {
+ "expr": "sum(up)",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "refId": "A"
+ }
+ ],
+ "thresholds": "0,1",
+ "title": "Targets Online",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ }
+ ],
+ "valueName": "current"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": false,
+ "colorValue": false,
+ "colors": [
+ "#d44a3a",
+ "rgba(237, 129, 40, 0.89)",
+ "#299c46"
+ ],
+ "datasource": "Default",
+ "format": "none",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": false,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 4,
+ "x": 15,
+ "y": 1
+ },
+ "id": 31,
+ "interval": null,
+ "links": [],
+ "mappingType": 1,
+ "mappingTypes": [
+ {
+ "name": "value to text",
+ "value": 1
+ },
+ {
+ "name": "range to text",
+ "value": 2
+ }
+ ],
+ "maxDataPoints": 100,
+ "nullPointMode": "connected",
+ "nullText": null,
+ "options": {},
+ "postfix": "",
+ "postfixFontSize": "50%",
+ "prefix": "",
+ "prefixFontSize": "50%",
+ "rangeMaps": [
+ {
+ "from": "null",
+ "text": "N/A",
+ "to": "null"
+ }
+ ],
+ "sparkline": {
+ "fillColor": "rgba(31, 118, 189, 0.18)",
+ "full": false,
+ "lineColor": "rgb(31, 120, 193)",
+ "show": true
+ },
+ "tableColumn": "",
+ "targets": [
+ {
+ "expr": "count(rate(container_last_seen{job=\"cadvisor\", name!=\"\"}[5m]))",
+ "format": "time_series",
+ "intervalFactor": 1,
+ "refId": "A"
+ }
+ ],
+ "thresholds": "0,1",
+ "title": "Running Containers",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ }
+ ],
+ "valueName": "current"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": false,
+ "colorValue": false,
+ "colors": [
+ "#299c46",
+ "rgba(237, 129, 40, 0.89)",
+ "#d44a3a"
+ ],
+ "datasource": null,
+ "decimals": null,
+ "format": "decbytes",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": false,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 3,
+ "x": 3,
+ "y": 5
+ },
+ "id": 37,
+ "interval": null,
+ "links": [],
+ "mappingType": 1,
+ "mappingTypes": [
+ {
+ "name": "value to text",
+ "value": 1
+ },
+ {
+ "name": "range to text",
+ "value": 2
+ }
+ ],
+ "maxDataPoints": 100,
+ "nullPointMode": "connected",
+ "nullText": null,
+ "options": {},
+ "postfix": "",
+ "postfixFontSize": "50%",
+ "prefix": "",
+ "prefixFontSize": "50%",
+ "rangeMaps": [
+ {
+ "from": "null",
+ "text": "N/A",
+ "to": "null"
+ }
+ ],
+ "sparkline": {
+ "fillColor": "rgba(31, 118, 189, 0.18)",
+ "full": false,
+ "lineColor": "rgb(31, 120, 193)",
+ "show": false,
+ "ymax": null,
+ "ymin": null
+ },
+ "tableColumn": "",
+ "targets": [
+ {
+ "expr": "node_memory_MemTotal_bytes{instance=~\"$node\"}",
+ "refId": "A"
+ }
+ ],
+ "thresholds": "",
+ "timeFrom": null,
+ "timeShift": null,
+ "title": "Host Memory",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ }
+ ],
+ "valueName": "current"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": false,
+ "colorValue": false,
+ "colors": [
+ "rgba(50, 172, 45, 0.97)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(245, 54, 54, 0.9)"
+ ],
+ "datasource": "Default",
+ "editable": true,
+ "error": false,
+ "format": "percent",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": true,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 6,
+ "x": 0,
+ "y": 8
+ },
+ "id": 4,
+ "interval": null,
+ "isNew": true,
+ "links": [],
+ "mappingType": 1,
+ "mappingTypes": [
+ {
+ "name": "value to text",
+ "value": 1
+ },
+ {
+ "name": "range to text",
+ "value": 2
+ }
+ ],
+ "maxDataPoints": 100,
+ "nullPointMode": "connected",
+ "nullText": null,
+ "options": {},
+ "postfix": "",
+ "postfixFontSize": "50%",
+ "prefix": "",
+ "prefixFontSize": "50%",
+ "rangeMaps": [
+ {
+ "from": "null",
+ "text": "N/A",
+ "to": "null"
+ }
+ ],
+ "sparkline": {
+ "fillColor": "rgba(31, 118, 189, 0.18)",
+ "full": false,
+ "lineColor": "rgb(31, 120, 193)",
+ "show": false
+ },
+ "tableColumn": "",
+ "targets": [
+ {
+ "expr": "(sum(node_memory_MemTotal_bytes) - sum(node_memory_MemFree_bytes +node_memory_Buffers_bytes + node_memory_Cached_bytes) ) / sum(node_memory_MemTotal_bytes) * 100",
+ "format": "time_series",
+ "interval": "10s",
+ "intervalFactor": 1,
+ "refId": "A",
+ "step": 10
+ }
+ ],
+ "thresholds": "65, 90",
+ "title": "Memory usage",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ }
+ ],
+ "valueName": "current"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": false,
+ "colorValue": false,
+ "colors": [
+ "rgba(50, 172, 45, 0.97)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(245, 54, 54, 0.9)"
+ ],
+ "datasource": "Default",
+ "decimals": 2,
+ "editable": true,
+ "error": false,
+ "format": "percent",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": true,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 6,
+ "x": 6,
+ "y": 8
+ },
+ "id": 6,
+ "interval": null,
+ "isNew": true,
+ "links": [],
+ "mappingType": 1,
+ "mappingTypes": [
+ {
+ "name": "value to text",
+ "value": 1
+ },
+ {
+ "name": "range to text",
+ "value": 2
+ }
+ ],
+ "maxDataPoints": 100,
+ "nullPointMode": "connected",
+ "nullText": null,
+ "options": {},
+ "postfix": "",
+ "postfixFontSize": "50%",
+ "prefix": "",
+ "prefixFontSize": "50%",
+ "rangeMaps": [
+ {
+ "from": "null",
+ "text": "N/A",
+ "to": "null"
+ }
+ ],
+ "sparkline": {
+ "fillColor": "rgba(31, 118, 189, 0.18)",
+ "full": false,
+ "lineColor": "rgb(31, 120, 193)",
+ "show": false
+ },
+ "tableColumn": "",
+ "targets": [
+ {
+ "expr": "100 - (avg(irate(node_cpu_seconds_total{instance=~\"$node\",mode=\"idle\"}[5m])) * 100)",
+ "format": "time_series",
+ "interval": "1m",
+ "intervalFactor": 1,
+ "legendFormat": "",
+ "refId": "A",
+ "step": 10
+ }
+ ],
+ "thresholds": "65, 90",
+ "title": "CPU usage",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ }
+ ],
+ "valueName": "current"
+ },
+ {
+ "cacheTimeout": null,
+ "colorBackground": false,
+ "colorValue": false,
+ "colors": [
+ "rgba(50, 172, 45, 0.97)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(245, 54, 54, 0.9)"
+ ],
+ "datasource": "Default",
+ "decimals": 2,
+ "editable": true,
+ "error": false,
+ "format": "percent",
+ "gauge": {
+ "maxValue": 100,
+ "minValue": 0,
+ "show": true,
+ "thresholdLabels": false,
+ "thresholdMarkers": true
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 7,
+ "x": 12,
+ "y": 8
+ },
+ "id": 7,
+ "interval": null,
+ "isNew": true,
+ "links": [],
+ "mappingType": 1,
+ "mappingTypes": [
+ {
+ "name": "value to text",
+ "value": 1
+ },
+ {
+ "name": "range to text",
+ "value": 2
+ }
+ ],
+ "maxDataPoints": 100,
+ "nullPointMode": "connected",
+ "nullText": null,
+ "options": {},
+ "postfix": "",
+ "postfixFontSize": "50%",
+ "prefix": "",
+ "prefixFontSize": "50%",
+ "rangeMaps": [
+ {
+ "from": "null",
+ "text": "N/A",
+ "to": "null"
+ }
+ ],
+ "sparkline": {
+ "fillColor": "rgba(31, 118, 189, 0.18)",
+ "full": false,
+ "lineColor": "rgb(31, 120, 193)",
+ "show": false
+ },
+ "tableColumn": "",
+ "targets": [
+ {
+ "expr": "avg( node_filesystem_avail_bytes {mountpoint=\"/\"} / node_filesystem_size_bytes{mountpoint=\"/\"})",
+ "interval": "10s",
+ "intervalFactor": 1,
+ "metric": "",
+ "refId": "A",
+ "step": 10
+ }
+ ],
+ "thresholds": "65, 90",
+ "title": "Filesystem usage",
+ "type": "singlestat",
+ "valueFontSize": "80%",
+ "valueMaps": [
+ {
+ "op": "=",
+ "text": "N/A",
+ "value": "null"
+ }
+ ],
+ "valueName": "current"
+ },
+ {
+ "aliasColors": {
+ "RECEIVE": "#ea6460",
+ "SENT": "#1f78c1",
+ "TRANSMIT": "#1f78c1"
+ },
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Default",
+ "fill": 4,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 9,
+ "w": 6,
+ "x": 0,
+ "y": 14
+ },
+ "id": 25,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "dataLinks": []
+ },
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sum(rate(container_network_receive_bytes_total{id=\"/\"}[$interval])) by (id)",
+ "format": "time_series",
+ "interval": "2m",
+ "intervalFactor": 2,
+ "legendFormat": "RECEIVE",
+ "refId": "A"
+ },
+ {
+ "expr": "- sum(rate(container_network_transmit_bytes_total{id=\"/\"}[$interval])) by (id)",
+ "format": "time_series",
+ "interval": "2m",
+ "intervalFactor": 2,
+ "legendFormat": "TRANSMIT",
+ "refId": "B"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Node Network Traffic",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "Bps",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "s",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {
+ "Available Memory": "#508642",
+ "Used Memory": "#bf1b00"
+ },
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Default",
+ "fill": 3,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 9,
+ "w": 6,
+ "x": 6,
+ "y": 14
+ },
+ "id": 27,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "dataLinks": []
+ },
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sum(node_memory_MemTotal_bytes) - sum(node_memory_MemAvailable_bytes)",
+ "format": "time_series",
+ "interval": "2m",
+ "intervalFactor": 2,
+ "legendFormat": "Used Memory",
+ "refId": "B"
+ },
+ {
+ "expr": "sum(node_memory_MemAvailable_bytes)",
+ "format": "time_series",
+ "interval": "2m",
+ "intervalFactor": 2,
+ "legendFormat": "Available Memory",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Node Mermory",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "decbytes",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "s",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {
+ "Available Memory": "#508642",
+ "Free Storage": "#447ebc",
+ "Total Storage Available": "#508642",
+ "Used Memory": "#bf1b00",
+ "Used Storage": "#bf1b00"
+ },
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Default",
+ "fill": 3,
+ "fillGradient": 0,
+ "gridPos": {
+ "h": 9,
+ "w": 7,
+ "x": 12,
+ "y": 14
+ },
+ "id": 28,
+ "legend": {
+ "avg": false,
+ "current": false,
+ "max": false,
+ "min": false,
+ "show": true,
+ "total": false,
+ "values": false
+ },
+ "lines": true,
+ "linewidth": 1,
+ "links": [],
+ "nullPointMode": "null",
+ "options": {
+ "dataLinks": []
+ },
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": true,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sum(node_filesystem_free_bytes {job=\"node-exporter\", instance=~\".*${(nodeexporter.port)}\", device=~\"/dev/.*\", mountpoint!=\"/var/lib/docker/aufs\"}) ",
+ "format": "time_series",
+ "interval": "2m",
+ "intervalFactor": 2,
+ "legendFormat": "Free Storage",
+ "refId": "A"
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Filesystem Available",
+ "tooltip": {
+ "shared": true,
+ "sort": 0,
+ "value_type": "individual"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "decimals": null,
+ "format": "decbytes",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "s",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "collapsed": false,
+ "datasource": null,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 23
+ },
+ "id": 19,
+ "panels": [],
+ "repeat": null,
+ "title": "Container Performance",
+ "type": "row"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Default",
+ "decimals": 3,
+ "editable": true,
+ "error": false,
+ "fill": 0,
+ "fillGradient": 0,
+ "grid": {},
+ "gridPos": {
+ "h": 10,
+ "w": 6,
+ "x": 0,
+ "y": 24
+ },
+ "id": 3,
+ "isNew": true,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": false,
+ "min": false,
+ "rightSide": false,
+ "show": true,
+ "sort": "current",
+ "sortDesc": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "connected",
+ "options": {
+ "dataLinks": []
+ },
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sum(rate(container_cpu_usage_seconds_total{image!=\"\"}[1m])) by (id,name)",
+ "format": "time_series",
+ "interval": "10s",
+ "intervalFactor": 1,
+ "legendFormat": "{{ name }}",
+ "metric": "container_cpu_user_seconds_total",
+ "refId": "A",
+ "step": 10
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Container CPU usage",
+ "tooltip": {
+ "msResolution": true,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "percentunit",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Default",
+ "decimals": 2,
+ "editable": true,
+ "error": false,
+ "fill": 0,
+ "fillGradient": 0,
+ "grid": {},
+ "gridPos": {
+ "h": 10,
+ "w": 6,
+ "x": 6,
+ "y": 24
+ },
+ "id": 2,
+ "isNew": true,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": false,
+ "min": false,
+ "rightSide": false,
+ "show": true,
+ "sort": "current",
+ "sortDesc": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "connected",
+ "options": {
+ "dataLinks": []
+ },
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "container_memory_max_usage_bytes{image!=\"\"}",
+ "format": "time_series",
+ "interval": "10s",
+ "intervalFactor": 1,
+ "legendFormat": "{{ name }}",
+ "metric": "container_memory_usage:sort_desc",
+ "refId": "A",
+ "step": 10
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Container Memory Usage",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "bytes",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "columns": [],
+ "datasource": "Default",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 13,
+ "w": 10,
+ "x": 12,
+ "y": 24
+ },
+ "id": 23,
+ "links": [],
+ "options": {},
+ "pageSize": null,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 0,
+ "desc": true
+ },
+ "styles": [
+ {
+ "alias": "Time",
+ "dateFormat": "YYYY-MM-DD HH:mm:ss",
+ "pattern": "Time",
+ "type": "date"
+ },
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "number",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "expr": "ALERTS",
+ "format": "table",
+ "intervalFactor": 1,
+ "refId": "A"
+ }
+ ],
+ "title": "Alerts",
+ "transform": "table",
+ "type": "table"
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Default",
+ "decimals": 2,
+ "editable": true,
+ "error": false,
+ "fill": 0,
+ "fillGradient": 0,
+ "grid": {},
+ "gridPos": {
+ "h": 14,
+ "w": 6,
+ "x": 0,
+ "y": 34
+ },
+ "id": 8,
+ "isNew": true,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": false,
+ "min": false,
+ "rightSide": false,
+ "show": true,
+ "sort": "current",
+ "sortDesc": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "connected",
+ "options": {
+ "dataLinks": []
+ },
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sort_desc(sum by (name) (rate(container_network_receive_bytes_total{image!=\"\"}[1m] ) ))",
+ "interval": "10s",
+ "intervalFactor": 1,
+ "legendFormat": "{{ name }}",
+ "metric": "container_network_receive_bytes_total",
+ "refId": "A",
+ "step": 10
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Container Network Input",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "bytes",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "aliasColors": {},
+ "bars": false,
+ "dashLength": 10,
+ "dashes": false,
+ "datasource": "Default",
+ "decimals": 2,
+ "editable": true,
+ "error": false,
+ "fill": 0,
+ "fillGradient": 0,
+ "grid": {},
+ "gridPos": {
+ "h": 14,
+ "w": 6,
+ "x": 6,
+ "y": 34
+ },
+ "id": 9,
+ "isNew": true,
+ "legend": {
+ "alignAsTable": true,
+ "avg": true,
+ "current": true,
+ "max": false,
+ "min": false,
+ "rightSide": false,
+ "show": true,
+ "sort": "current",
+ "sortDesc": true,
+ "total": false,
+ "values": true
+ },
+ "lines": true,
+ "linewidth": 2,
+ "links": [],
+ "nullPointMode": "connected",
+ "options": {
+ "dataLinks": []
+ },
+ "percentage": false,
+ "pointradius": 5,
+ "points": false,
+ "renderer": "flot",
+ "seriesOverrides": [],
+ "spaceLength": 10,
+ "stack": false,
+ "steppedLine": false,
+ "targets": [
+ {
+ "expr": "sort_desc(sum by (name) (rate(container_network_transmit_bytes_total{image!=\"\"}[1m] ) ))",
+ "format": "time_series",
+ "intervalFactor": 2,
+ "legendFormat": "{{ name }}",
+ "metric": "container_network_transmit_bytes_total",
+ "refId": "B",
+ "step": 4
+ }
+ ],
+ "thresholds": [],
+ "timeFrom": null,
+ "timeRegions": [],
+ "timeShift": null,
+ "title": "Container Network Output",
+ "tooltip": {
+ "msResolution": false,
+ "shared": true,
+ "sort": 0,
+ "value_type": "cumulative"
+ },
+ "type": "graph",
+ "xaxis": {
+ "buckets": null,
+ "mode": "time",
+ "name": null,
+ "show": true,
+ "values": []
+ },
+ "yaxes": [
+ {
+ "format": "bytes",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": true
+ },
+ {
+ "format": "short",
+ "label": null,
+ "logBase": 1,
+ "max": null,
+ "min": null,
+ "show": false
+ }
+ ],
+ "yaxis": {
+ "align": false,
+ "alignLevel": null
+ }
+ },
+ {
+ "columns": [],
+ "datasource": "Default",
+ "fontSize": "100%",
+ "gridPos": {
+ "h": 10,
+ "w": 10,
+ "x": 12,
+ "y": 37
+ },
+ "id": 30,
+ "links": [],
+ "options": {},
+ "pageSize": 10,
+ "scroll": true,
+ "showHeader": true,
+ "sort": {
+ "col": 0,
+ "desc": true
+ },
+ "styles": [
+ {
+ "alias": "Time",
+ "dateFormat": "YYYY-MM-DD HH:mm:ss",
+ "link": false,
+ "linkUrl": "",
+ "pattern": "Time",
+ "type": "date"
+ },
+ {
+ "alias": "",
+ "colorMode": null,
+ "colors": [
+ "rgba(245, 54, 54, 0.9)",
+ "rgba(237, 129, 40, 0.89)",
+ "rgba(50, 172, 45, 0.97)"
+ ],
+ "decimals": 2,
+ "pattern": "/.*/",
+ "thresholds": [],
+ "type": "number",
+ "unit": "short"
+ }
+ ],
+ "targets": [
+ {
+ "expr": "cadvisor_version_info",
+ "format": "table",
+ "instant": false,
+ "interval": "15m",
+ "intervalFactor": 2,
+ "legendFormat": "cAdvisor Version: {{cadvisorVersion}}",
+ "refId": "A"
+ },
+ {
+ "expr": "prometheus_build_info",
+ "format": "table",
+ "interval": "15m",
+ "intervalFactor": 2,
+ "legendFormat": "Prometheus Version: {{version}}",
+ "refId": "B"
+ },
+ {
+ "expr": "node_exporter_build_info",
+ "format": "table",
+ "interval": "15m",
+ "intervalFactor": 2,
+ "legendFormat": "Node-Exporter Version: {{version}}",
+ "refId": "C"
+ }
+ ],
+ "title": "Running Versions",
+ "transform": "table",
+ "type": "table"
+ }
+ ],
+ "refresh": "10s",
+ "schemaVersion": 20,
+ "style": "dark",
+ "tags": [
+ "docker",
+ "prometheus, ",
+ "node-exporter",
+ "cadvisor"
+ ],
+ "templating": {
+ "list": [
+ {
+ "auto": false,
+ "auto_count": 30,
+ "auto_min": "10s",
+ "current": {
+ "text": "1m",
+ "value": "1m"
+ },
+ "hide": 0,
+ "label": "interval",
+ "name": "interval",
+ "options": [
+ {
+ "selected": true,
+ "text": "1m",
+ "value": "1m"
+ },
+ {
+ "selected": false,
+ "text": "10m",
+ "value": "10m"
+ },
+ {
+ "selected": false,
+ "text": "30m",
+ "value": "30m"
+ },
+ {
+ "selected": false,
+ "text": "1h",
+ "value": "1h"
+ },
+ {
+ "selected": false,
+ "text": "6h",
+ "value": "6h"
+ },
+ {
+ "selected": false,
+ "text": "12h",
+ "value": "12h"
+ },
+ {
+ "selected": false,
+ "text": "1d",
+ "value": "1d"
+ },
+ {
+ "selected": false,
+ "text": "7d",
+ "value": "7d"
+ },
+ {
+ "selected": false,
+ "text": "14d",
+ "value": "14d"
+ },
+ {
+ "selected": false,
+ "text": "30d",
+ "value": "30d"
+ }
+ ],
+ "query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d",
+ "refresh": 2,
+ "skipUrlSync": false,
+ "type": "interval"
+ },
+ {
+ "allValue": null,
+ "current": {
+ "text": "All",
+ "value": "$__all"
+ },
+ "datasource": "Default",
+ "definition": "label_values(node_exporter_build_info{name=~'$name'},instance)",
+ "hide": 0,
+ "includeAll": true,
+ "label": "IP",
+ "multi": true,
+ "name": "node",
+ "options": [],
+ "query": "label_values(node_exporter_build_info{name=~'$name'},instance)",
+ "refresh": 2,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 1,
+ "tagValuesQuery": "",
+ "tags": [],
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ },
+ {
+ "allValue": null,
+ "current": {
+ "text": "All",
+ "value": "$__all"
+ },
+ "datasource": "Default",
+ "definition": "label_values(node_exporter_build_info,env)",
+ "hide": 0,
+ "includeAll": true,
+ "label": "Env",
+ "multi": true,
+ "name": "env",
+ "options": [],
+ "query": "label_values(node_exporter_build_info,env)",
+ "refresh": 2,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "tagValuesQuery": "",
+ "tags": [],
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ },
+ {
+ "allValue": null,
+ "current": {
+ "text": "All",
+ "value": "$__all"
+ },
+ "datasource": "Default",
+ "definition": "label_values(node_exporter_build_info{env=~'$env'},name)",
+ "hide": 0,
+ "includeAll": true,
+ "label": "CPU Name",
+ "multi": true,
+ "name": "name",
+ "options": [],
+ "query": "label_values(node_exporter_build_info{env=~'$env'},name)",
+ "refresh": 2,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "tagValuesQuery": "",
+ "tags": [],
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ }
+ ]
+ },
+ "time": {
+ "from": "now-5m",
+ "to": "now"
+ },
+ "timepicker": {
+ "refresh_intervals": [
+ "5s",
+ "10s",
+ "30s",
+ "1m",
+ "5m",
+ "15m",
+ "30m",
+ "1h",
+ "2h",
+ "1d"
+ ],
+ "time_options": [
+ "5m",
+ "15m",
+ "1h",
+ "6h",
+ "12h",
+ "24h",
+ "2d",
+ "7d",
+ "30d"
+ ]
+ },
+ "timezone": "browser",
+ "title": "RepoAchiever Diagnostics",
+ "uid": "64nrElFmk",
+ "version": 2
+}
\ No newline at end of file
diff --git a/config/grafana/datasources/datasource.tmpl b/config/grafana/datasources/datasource.tmpl
new file mode 100644
index 0000000..064ca10
--- /dev/null
+++ b/config/grafana/datasources/datasource.tmpl
@@ -0,0 +1,19 @@
+apiVersion: 1
+
+deleteDatasources:
+ - name: Default
+ orgId: 1
+
+datasources:
+ - name: Default
+ type: prometheus
+ access: proxy
+ orgId: 1
+ url: http://${(prometheus.host)}:${(prometheus.port)}
+ isDefault: true
+ jsonData:
+ graphiteVersion: "1.1"
+ tlsAuth: false
+ tlsAuthWithCACert: false
+ version: 1
+ editable: false
diff --git a/config/prometheus/prometheus.tmpl b/config/prometheus/prometheus.tmpl
new file mode 100644
index 0000000..f2ded30
--- /dev/null
+++ b/config/prometheus/prometheus.tmpl
@@ -0,0 +1,18 @@
+global:
+ scrape_interval: 5s
+ evaluation_interval: 5s
+
+scrape_configs:
+ - job_name: 'prometheus'
+
+ scrape_interval: 15s
+
+ static_configs:
+ - targets: ['${(metrics.host)}:${(metrics.port)}']
+
+ - job_name: 'node-exporter'
+
+ scrape_interval: 15s
+
+ static_configs:
+ - targets: ['${(nodeexporter.host)}:${(nodeexporter.port)}']
diff --git a/docs/detailed-design-raw.md b/docs/detailed-design-raw.md
new file mode 100644
index 0000000..046c305
--- /dev/null
+++ b/docs/detailed-design-raw.md
@@ -0,0 +1,76 @@
+```plantuml
+!pragma teoz true
+
+title
+ Detailed design of "ResourceTracker"
+end title
+
+actor "Client" as client
+
+box "Control plain" #MOTIVATION
+participant "API Server" as apiserver
+
+box "Cloud environment" #Lavender
+queue "Kafka" as kafka
+participant "Kafka starter" as kafkastarter
+participant "Agent" as agent
+entity "Cloud provider" as cloudprovider
+end box
+
+end box
+
+note over [[[[[[kafka]]]]]]: Kafka is considered to be used in persisted mode
+
+opt "endpoints"
+opt "/v1/secrets/acquire [POST]"
+apiserver -> cloudprovider: validate provided credentials
+cloudprovider -> apiserver: validation result
+end
+opt "/v1/topic/logs [GET]"
+apiserver -> kafka: retrieve state for the given "logs" topic
+kafka -> apiserver: transform data stream according to the specified filters
+end
+opt "/v1/terraform/apply [POST]"
+apiserver -> cloudprovider: deploy resource tracking infrastructure
+apiserver -> kafkastarter: request kafka cluster startup
+kafkastarter -> kafka: start kafka cluster
+end
+opt "/v1/terraform/destroy [POST]"
+apiserver -> cloudprovider: destroy resource tracking infrastructure
+end
+end
+
+opt "agent execution flow"
+agent -> cloudprovider: execute remote operations
+agent <-- cloudprovider: remote operation result
+agent --> kafka: push latest resource state to "logs" topic
+end
+
+opt "requests"
+note over client: Uses properties specified in a client\nconfiguration file located in\n a common directory
+opt "credentials validation"
+client -> apiserver: /v1/secrets/acquire [POST]
+end
+opt "script validation"
+client -> apiserver: /v1/script/acquire [POST]
+end
+opt "health check"
+client -> apiserver: /v1/health [GET]
+end
+opt "readiness check"
+client -> apiserver: /v1/readiness [GET]
+end
+opt "version validation"
+client -> apiserver: /v1/info/version [GET]
+end
+opt "infrustructure deployment"
+client -> apiserver: /v1/terraform/apply [POST]
+end
+opt "infrustructure clean up"
+client -> apiserver: /v1/terraform/destroy [POST]
+end
+opt "state retrieval"
+client -> apiserver: /v1/topic/logs [GET]
+end
+end
+```
\ No newline at end of file
diff --git a/docs/detailed-design.png b/docs/detailed-design.png
new file mode 100644
index 0000000..2c30540
Binary files /dev/null and b/docs/detailed-design.png differ
diff --git a/docs/high-level-design-raw.md b/docs/high-level-design-raw.md
new file mode 100644
index 0000000..0f4c954
--- /dev/null
+++ b/docs/high-level-design-raw.md
@@ -0,0 +1,23 @@
+```plantuml
+title
+
+High-level design of "RepoArchiver"
+
+end title
+
+actor "Client"
+
+component "Control plane" {
+node "API Server"
+node "Prometheus"
+
+cloud " Decentralized environment" {
+node "Cluster"
+entity "Worker"
+}
+
+[Client] <--> [API Server]: " Send requests"
+[API Server] <-> [Cluster]: " Schedule content processing"
+[Cluster] <--> [Worker]: " Propagate individual requests "
+[API Server] --> [Prometheus]: " Export diagnostics"
+```
\ No newline at end of file
diff --git a/docs/high-level-design.png b/docs/high-level-design.png
new file mode 100644
index 0000000..d589fff
Binary files /dev/null and b/docs/high-level-design.png differ
diff --git a/docs/workspace-design.md b/docs/workspace-design.md
new file mode 100644
index 0000000..1c72563
--- /dev/null
+++ b/docs/workspace-design.md
@@ -0,0 +1,18 @@
+```plantuml
+@startwbs
+title
+
+Workspace design of "RepoArchiver API Server"
+
+end title
+
+* Workspace unit
+** Content
+*** Certain repository
+**** Repository version
+** Metadata
+*** PRs
+*** Issues
+*** Releases
+@endwbs
+```
\ No newline at end of file
diff --git a/docs/workspace-design.png b/docs/workspace-design.png
new file mode 100644
index 0000000..a53a228
Binary files /dev/null and b/docs/workspace-design.png differ
diff --git a/exporter/pom.xml b/exporter/pom.xml
new file mode 100644
index 0000000..77859cb
--- /dev/null
+++ b/exporter/pom.xml
@@ -0,0 +1,21 @@
+
+
+ 4.0.0
+
+ com.repoachiever
+ base
+ 1.0-SNAPSHOT
+
+
+ org.example
+ exporter
+
+
+ 21
+ 21
+ UTF-8
+
+
+
\ No newline at end of file
diff --git a/exporter/src/main/java/org/example/Main.java b/exporter/src/main/java/org/example/Main.java
new file mode 100644
index 0000000..407f157
--- /dev/null
+++ b/exporter/src/main/java/org/example/Main.java
@@ -0,0 +1,7 @@
+package org.example;
+
+public class Main {
+ public static void main(String[] args) {
+ System.out.println("Hello world!");
+ }
+}
\ No newline at end of file
diff --git a/gui/.DS_Store b/gui/.DS_Store
new file mode 100644
index 0000000..1cc341e
Binary files /dev/null and b/gui/.DS_Store differ
diff --git a/gui/gui.iml b/gui/gui.iml
new file mode 100644
index 0000000..056f882
--- /dev/null
+++ b/gui/gui.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gui/pom.xml b/gui/pom.xml
new file mode 100644
index 0000000..025ae62
--- /dev/null
+++ b/gui/pom.xml
@@ -0,0 +1,206 @@
+
+
+ 4.0.0
+ gui
+ 1.0-SNAPSHOT
+ gui
+ GUI for ResourceTracker
+
+
+ com.repoachiever
+ base
+ 1.0-SNAPSHOT
+
+
+
+
+ com.repoachiever.GUI
+
+
+
+
+
+
+ Shell-Command-Executor-Lib
+ Shell-Command-Executor-Lib
+ system
+ ${basedir}/../lib/Shell-Command-Executor-Lib-0.5.0-SNAPSHOT.jar
+
+
+ ink.bluecloud
+ elementfx
+ system
+ ${basedir}/../lib/ElementFX-1.3-SNAPSHOT.jar
+
+
+
+
+ org.openjfx
+ javafx-controls
+
+
+ com.brunomnsilva
+ smartgraph
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework
+ spring-context
+
+
+
+
+ io.netty
+ netty-resolver-dns-native-macos
+ osx-aarch_64
+
+
+
+
+ org.openapitools
+ jackson-databind-nullable
+
+
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+
+
+ jakarta.validation
+ jakarta.validation-api
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+
+
+ jakarta.ws.rs
+ jakarta.ws.rs-api
+
+
+ org.projectlombok
+ lombok
+
+
+ org.yaml
+ snakeyaml
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-yaml
+
+
+ com.opencsv
+ opencsv
+
+
+ commons-io
+ commons-io
+
+
+ io.github.kostaskougios
+ cloning
+
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+
+ gui
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ org.openjfx
+ javafx-maven-plugin
+
+
+ org.openapitools
+ openapi-generator-maven-plugin
+
+
+
+ generate
+
+
+ ${project.basedir}/../api-server/src/main/openapi/openapi.yml
+ java
+ webclient
+ ${default.package}.api
+ ${default.package}.model
+ true
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+
+ @lombok.Data @lombok.AllArgsConstructor(staticName = "of")
+ src/main/java
+ true
+ false
+ true
+ true
+ false
+ true
+ java8
+ true
+ true
+
+
+
+
+
+
+ pl.project13.maven
+ git-commit-id-plugin
+
+
+ com.coderplus.maven.plugins
+ copy-rename-maven-plugin
+
+
+
+ ${basedir}/target/gui.jar
+ ${main.basedir}/../bin/gui/gui.jar
+
+
+
+
+
+
+
diff --git a/gui/src/.DS_Store b/gui/src/.DS_Store
new file mode 100644
index 0000000..e77e13b
Binary files /dev/null and b/gui/src/.DS_Store differ
diff --git a/gui/src/main/.DS_Store b/gui/src/main/.DS_Store
new file mode 100644
index 0000000..879ce8e
Binary files /dev/null and b/gui/src/main/.DS_Store differ
diff --git a/gui/src/main/java/com/repoachiever/App.java b/gui/src/main/java/com/repoachiever/App.java
new file mode 100644
index 0000000..668ba82
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/App.java
@@ -0,0 +1,81 @@
+package com.repoachiever;
+
+import com.repoachiever.service.client.observer.ResourceObserver;
+import com.repoachiever.service.element.font.FontLoader;
+import com.repoachiever.service.element.observer.ElementObserver;
+import com.repoachiever.service.element.stage.MainStage;
+import com.repoachiever.service.event.state.LocalState;
+import com.repoachiever.service.scheduler.SchedulerHelper;
+import javafx.application.Application;
+import javafx.application.HostServices;
+import javafx.application.Platform;
+import javafx.stage.Stage;
+import lombok.SneakyThrows;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.support.GenericApplicationContext;
+
+/** Represents entrypoint for the application. */
+public class App extends Application {
+ private ConfigurableApplicationContext applicationContext;
+
+ @Autowired private LocalState localState;
+
+ @Autowired private ElementObserver elementObserver;
+
+ @Autowired private ResourceObserver resourceObserver;
+
+ @Autowired private FontLoader fontLoader;
+
+ @Autowired private MainStage mainStage;
+
+ /**
+ * @see Application
+ */
+ public void launch() {
+ Application.launch();
+ }
+
+ /**
+ * @see Application
+ */
+ @Override
+ public void init() {
+ System.setProperty("apple.laf.useScreenMenuBar", "true");
+ System.setProperty("apple.awt.UIElement", "true");
+
+ ApplicationContextInitializer initializer =
+ applicationContext -> {
+ applicationContext.registerBean(Application.class, () -> App.this);
+ applicationContext.registerBean(Parameters.class, this::getParameters);
+ applicationContext.registerBean(HostServices.class, this::getHostServices);
+ };
+
+ applicationContext =
+ new SpringApplicationBuilder()
+ .sources(GUI.class)
+ .initializers(initializer)
+ .run(getParameters().getRaw().toArray(new String[0]));
+ }
+
+ /**
+ * @see Application
+ */
+ @Override
+ public void stop() {
+ applicationContext.close();
+ SchedulerHelper.close();
+ Platform.exit();
+ }
+
+ /**
+ * @see Application
+ */
+ @Override
+ @SneakyThrows
+ public void start(Stage stage) {
+ mainStage.getContent().show();
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/GUI.java b/gui/src/main/java/com/repoachiever/GUI.java
new file mode 100644
index 0000000..1bd752d
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/GUI.java
@@ -0,0 +1,11 @@
+package com.repoachiever;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class GUI {
+ public static void main(String[] args) {
+ App app = new App();
+ app.launch();
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/converter/CredentialsConverter.java b/gui/src/main/java/com/repoachiever/converter/CredentialsConverter.java
new file mode 100644
index 0000000..e6abb88
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/converter/CredentialsConverter.java
@@ -0,0 +1,11 @@
+package com.repoachiever.converter;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class CredentialsConverter {
+ @SuppressWarnings("unchecked")
+ public static T convert(Object input, Class stub) {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.convertValue(input, stub);
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/dto/CommandExecutorOutputDto.java b/gui/src/main/java/com/repoachiever/dto/CommandExecutorOutputDto.java
new file mode 100644
index 0000000..0051755
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/dto/CommandExecutorOutputDto.java
@@ -0,0 +1,13 @@
+package com.repoachiever.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/** Represents gathered output of the executed command. */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class CommandExecutorOutputDto {
+ private String normalOutput;
+
+ private String errorOutput;
+}
diff --git a/gui/src/main/java/com/repoachiever/dto/HealthCheckInternalCommandResultDto.java b/gui/src/main/java/com/repoachiever/dto/HealthCheckInternalCommandResultDto.java
new file mode 100644
index 0000000..122fda8
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/dto/HealthCheckInternalCommandResultDto.java
@@ -0,0 +1,13 @@
+package com.repoachiever.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/** Represents readiness check command result. */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class HealthCheckInternalCommandResultDto {
+ private Boolean status;
+
+ private String error;
+}
diff --git a/gui/src/main/java/com/repoachiever/dto/ReadinessCheckInternalCommandResultDto.java b/gui/src/main/java/com/repoachiever/dto/ReadinessCheckInternalCommandResultDto.java
new file mode 100644
index 0000000..159ed89
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/dto/ReadinessCheckInternalCommandResultDto.java
@@ -0,0 +1,13 @@
+package com.repoachiever.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/** Represents readiness check command result. */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class ReadinessCheckInternalCommandResultDto {
+ private Boolean status;
+
+ private String error;
+}
diff --git a/gui/src/main/java/com/repoachiever/dto/StartExternalCommandResultDto.java b/gui/src/main/java/com/repoachiever/dto/StartExternalCommandResultDto.java
new file mode 100644
index 0000000..8030ae6
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/dto/StartExternalCommandResultDto.java
@@ -0,0 +1,13 @@
+package com.repoachiever.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/** Represents start deployment command result. */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class StartExternalCommandResultDto {
+ private Boolean status;
+
+ private String error;
+}
diff --git a/gui/src/main/java/com/repoachiever/dto/StateExternalCommandResultDto.java b/gui/src/main/java/com/repoachiever/dto/StateExternalCommandResultDto.java
new file mode 100644
index 0000000..927de4d
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/dto/StateExternalCommandResultDto.java
@@ -0,0 +1,16 @@
+package com.repoachiever.dto;
+
+import com.repoachiever.model.TopicLogsResult;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/** Represents state command result. */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class StateExternalCommandResultDto {
+ private TopicLogsResult topicLogsResult;
+
+ private Boolean status;
+
+ private String error;
+}
diff --git a/gui/src/main/java/com/repoachiever/dto/StopExternalCommandResultDto.java b/gui/src/main/java/com/repoachiever/dto/StopExternalCommandResultDto.java
new file mode 100644
index 0000000..881b7fe
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/dto/StopExternalCommandResultDto.java
@@ -0,0 +1,13 @@
+package com.repoachiever.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/** Represents stop deployment command result. */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class StopExternalCommandResultDto {
+ private Boolean status;
+
+ private String error;
+}
diff --git a/gui/src/main/java/com/repoachiever/dto/ValidationScriptApplicationDto.java b/gui/src/main/java/com/repoachiever/dto/ValidationScriptApplicationDto.java
new file mode 100644
index 0000000..1749e85
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/dto/ValidationScriptApplicationDto.java
@@ -0,0 +1,12 @@
+package com.repoachiever.dto;
+
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/** Represents script validation application used for script acquiring process. */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class ValidationScriptApplicationDto {
+ private List fileContent;
+}
diff --git a/gui/src/main/java/com/repoachiever/dto/ValidationSecretsApplicationDto.java b/gui/src/main/java/com/repoachiever/dto/ValidationSecretsApplicationDto.java
new file mode 100644
index 0000000..288b23c
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/dto/ValidationSecretsApplicationDto.java
@@ -0,0 +1,14 @@
+package com.repoachiever.dto;
+
+import com.repoachiever.model.Provider;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/** Represents secrets validation application used for secrets acquiring process. */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class ValidationSecretsApplicationDto {
+ private Provider provider;
+
+ private String filePath;
+}
diff --git a/gui/src/main/java/com/repoachiever/dto/VersionExternalCommandResultDto.java b/gui/src/main/java/com/repoachiever/dto/VersionExternalCommandResultDto.java
new file mode 100644
index 0000000..622e8db
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/dto/VersionExternalCommandResultDto.java
@@ -0,0 +1,15 @@
+package com.repoachiever.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/** Represents version retrieval command result. */
+@Getter
+@AllArgsConstructor(staticName = "of")
+public class VersionExternalCommandResultDto {
+ private String data;
+
+ private Boolean status;
+
+ private String error;
+}
diff --git a/gui/src/main/java/com/repoachiever/entity/ConfigEntity.java b/gui/src/main/java/com/repoachiever/entity/ConfigEntity.java
new file mode 100644
index 0000000..c88e21d
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/entity/ConfigEntity.java
@@ -0,0 +1,66 @@
+package com.repoachiever.entity;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import java.util.List;
+import lombok.Getter;
+
+/** Represents configuration model used for ResourceTracker deployment operation. */
+@Getter
+public class ConfigEntity {
+ /** Represents request to be executed in remote environment. */
+ @Getter
+ public static class Request {
+ @NotBlank String name;
+
+ @Pattern(regexp = "^(((./)?)|((~/.)?)|((/?))?)([a-zA-Z/]*)((\\.([a-z]+))?)$")
+ String file;
+
+ @Pattern(
+ regexp =
+ "(((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)|(([\\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))|([\\?])|([\\*]))[\\s](((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?)|(([\\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9]))|([\\?])|([\\*]))[\\s](((([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?,)*([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?)|(([\\*]|[0-9]|[0-1][0-9]|[2][0-3])/([0-9]|[0-1][0-9]|[2][0-3]))|([\\?])|([\\*]))[\\s](((([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?,)*([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?(C)?)|(([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])/([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(C)?)|(L(-[0-9])?)|(L(-[1-2][0-9])?)|(L(-[3][0-1])?)|(LW)|([1-9]W)|([1-3][0-9]W)|([\\?])|([\\*]))[\\s](((([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?,)*([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?)|(([1-9]|0[1-9]|1[0-2])/([1-9]|0[1-9]|1[0-2]))|(((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?,)*(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)|((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)/(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))|([\\?])|([\\*]))[\\s]((([1-7](-([1-7]))?,)*([1-7])(-([1-7]))?)|([1-7]/([1-7]))|(((MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?,)*(MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?(C)?)|((MON|TUE|WED|THU|FRI|SAT|SUN)/(MON|TUE|WED|THU|FRI|SAT|SUN)(C)?)|(([1-7]|(MON|TUE|WED|THU|FRI|SAT|SUN))?(L|LW)?)|(([1-7]|MON|TUE|WED|THU|FRI|SAT|SUN)#([1-7])?)|([\\?])|([\\*]))([\\s]?(([\\*])?|(19[7-9][0-9])|(20[0-9][0-9]))?|"
+ + " (((19[7-9][0-9])|(20[0-9][0-9]))/((19[7-9][0-9])|(20[0-9][0-9])))?|"
+ + " ((((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?,)*((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?)?)")
+ String frequency;
+ }
+
+ List requests;
+
+ /**
+ * Represents remove cloud infrastructure configuration properties used for further deployment
+ * related operations.
+ */
+ @Getter
+ public static class Cloud {
+ @JsonFormat(shape = JsonFormat.Shape.OBJECT)
+ public enum Provider {
+ @JsonProperty("aws")
+ AWS,
+ }
+
+ @NotBlank Provider provider;
+
+ @Getter
+ public static class AWSCredentials {
+ @Pattern(regexp = "^(((./)?)|((~/.)?)|((/?))?)([a-zA-Z/]*)((\\.([a-z]+))?)$")
+ String file;
+
+ @NotBlank String region;
+ }
+
+ @NotBlank Object credentials;
+ }
+
+ Cloud cloud;
+
+ /** Represents API Server configuration used for further connection establishment. */
+ @Getter
+ public static class APIServer {
+ @NotBlank String host;
+ }
+
+ @JsonProperty("api-server")
+ APIServer apiServer;
+}
diff --git a/gui/src/main/java/com/repoachiever/entity/PropertiesEntity.java b/gui/src/main/java/com/repoachiever/entity/PropertiesEntity.java
new file mode 100644
index 0000000..6e35d20
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/entity/PropertiesEntity.java
@@ -0,0 +1,211 @@
+package com.repoachiever.entity;
+
+import lombok.Getter;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+import org.springframework.core.io.ClassPathResource;
+
+/** Represents application properties used for application configuration. */
+@Getter
+@Configuration
+public class PropertiesEntity {
+ private static final String GIT_CONFIG_PROPERTIES_FILE = "git.properties";
+
+ @Value(value = "${window.main.name}")
+ private String windowMainName;
+
+ @Value(value = "${window.main.scale.min.width}")
+ private Double windowMainScaleMinWidth;
+
+ @Value(value = "${window.main.scale.min.height}")
+ private Double windowMainScaleMinHeight;
+
+ @Value(value = "${window.main.scale.max.width}")
+ private Double windowMainScaleMaxWidth;
+
+ @Value(value = "${window.main.scale.max.height}")
+ private Double windowMainScaleMaxHeight;
+
+ @Value(value = "${window.settings.name}")
+ private String windowSettingsName;
+
+ @Value(value = "${window.settings.scale.width}")
+ private Double windowSettingsScaleWidth;
+
+ @Value(value = "${window.settings.scale.height}")
+ private Double windowSettingsScaleHeight;
+
+ @Value(value = "${process.background.period}")
+ private Integer processBackgroundPeriod;
+
+ @Value(value = "${process.healthcheck.period}")
+ private Integer processHealthcheckPeriod;
+
+ @Value(value = "${process.readiness.period}")
+ private Integer processReadinessPeriod;
+
+ @Value(value = "${process.window.width.period}")
+ private Integer processWindowWidthPeriod;
+
+ @Value(value = "${process.window.height.period}")
+ private Integer processWindowHeightPeriod;
+
+ @Value(value = "${spinner.initial.delay}")
+ private Integer spinnerInitialDelay;
+
+ @Value(value = "${button.basic.size.width}")
+ private Double basicButtonSizeWidth;
+
+ @Value(value = "${button.basic.size.height}")
+ private Double basicButtonSizeHeight;
+
+ @Value(value = "${scene.general.background.color.r}")
+ private Integer generalBackgroundColorR;
+
+ @Value(value = "${scene.general.background.color.g}")
+ private Integer generalBackgroundColorG;
+
+ @Value(value = "${scene.general.background.color.b}")
+ private Integer generalBackgroundColorB;
+
+ @Value(value = "${scene.common.header.background.color.r}")
+ private Integer commonSceneHeaderBackgroundColorR;
+
+ @Value(value = "${scene.common.header.background.color.g}")
+ private Integer commonSceneHeaderBackgroundColorG;
+
+ @Value(value = "${scene.common.header.background.color.b}")
+ private Integer commonSceneHeaderBackgroundColorB;
+
+ @Value(value = "${scene.common.header.connection.background.color.r}")
+ private Integer commonSceneHeaderConnectionStatusBackgroundColorR;
+
+ @Value(value = "${scene.common.header.connection.background.color.g}")
+ private Integer commonSceneHeaderConnectionStatusBackgroundColorG;
+
+ @Value(value = "${scene.common.header.connection.background.color.b}")
+ private Integer commonSceneHeaderConnectionStatusBackgroundColorB;
+
+ @Value(value = "${scene.common.menu.background.color.r}")
+ private Integer commonSceneMenuBackgroundColorR;
+
+ @Value(value = "${scene.common.menu.background.color.g}")
+ private Integer commonSceneMenuBackgroundColorG;
+
+ @Value(value = "${scene.common.menu.background.color.b}")
+ private Integer commonSceneMenuBackgroundColorB;
+
+ @Value(value = "${scene.common.content.background.color.r}")
+ private Integer commonSceneContentBackgroundColorR;
+
+ @Value(value = "${scene.common.content.background.color.g}")
+ private Integer commonSceneContentBackgroundColorG;
+
+ @Value(value = "${scene.common.content.background.color.b}")
+ private Integer commonSceneContentBackgroundColorB;
+
+ @Value(value = "${scene.common.content.vertical-gap}")
+ private Double commonSceneContentVerticalGap;
+
+ @Value(value = "${scene.common.content.bar.horizontal-gap}")
+ private Double sceneCommonContentBarHorizontalGap;
+
+ @Value(value = "${scene.common.footer.background.color.r}")
+ private Integer commonSceneFooterBackgroundColorR;
+
+ @Value(value = "${scene.common.footer.background.color.g}")
+ private Integer commonSceneFooterBackgroundColorG;
+
+ @Value(value = "${scene.common.footer.background.color.b}")
+ private Integer commonSceneFooterBackgroundColorB;
+
+ @Value(value = "${image.status.scale}")
+ private Double statusImageScale;
+
+ @Value(value = "${font.default.name}")
+ private String fontDefaultName;
+
+ @Value(value = "${image.icon.name}")
+ private String imageIconName;
+
+ @Value(value = "${image.arrow.name}")
+ private String imageArrowName;
+
+ @Value(value = "${image.edit.name}")
+ private String imageEditName;
+
+ @Value(value = "${image.refresh.name}")
+ private String imageRefreshName;
+
+ @Value(value = "${image.start.name}")
+ private String imageStartName;
+
+ @Value(value = "${image.stop.name}")
+ private String imageStopName;
+
+ @Value(value = "${image.bar.width}")
+ private Integer imageBarWidth;
+
+ @Value(value = "${image.bar.height}")
+ private Integer imageBarHeight;
+
+ @Value(value = "${list-view.stub.name}")
+ private String listViewStubName;
+
+ @Value(value = "${alert.api-server-unavailable.message}")
+ private String alertApiServerUnavailableMessage;
+
+ @Value(value = "${alert.deployment-finished.message}")
+ private String alertDeploymentFinishedMessage;
+
+ @Value(value = "${alert.destruction-finished.message}")
+ private String alertDestructionFinishedMessage;
+
+ @Value(value = "${alert.version-mismatch.message}")
+ private String alertVersionMismatchMessage;
+
+ @Value(value = "${alert.editor-close-reminder.message}")
+ private String alertEditorCloseReminderMessage;
+
+ @Value(value = "${graph.css.location}")
+ private String graphCssFileLocation;
+
+ @Value(value = "${graph.properties.location}")
+ private String graphPropertiesLocation;
+
+ @Value(value = "${config.root}")
+ private String configRootPath;
+
+ @Value(value = "${swap.root}")
+ private String swapRootPath;
+
+ @Value(value = "${config.user.file}")
+ private String configUserFilePath;
+
+ @Value(value = "${api-server.directory}")
+ private String apiServerDirectory;
+
+ @Value(value = "${git.commit.id.abbrev}")
+ private String gitCommitId;
+
+ @Bean
+ private static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
+ PropertySourcesPlaceholderConfigurer propsConfig = new PropertySourcesPlaceholderConfigurer();
+ propsConfig.setLocation(new ClassPathResource(GIT_CONFIG_PROPERTIES_FILE));
+ propsConfig.setIgnoreResourceNotFound(true);
+ propsConfig.setIgnoreUnresolvablePlaceholders(true);
+ return propsConfig;
+ }
+
+ /**
+ * Removes the last symbol in git commit id of the repository.
+ *
+ * @return chopped repository git commit id.
+ */
+ public String getGitCommitId() {
+ return StringUtils.chop(gitCommitId);
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/exception/ApiServerException.java b/gui/src/main/java/com/repoachiever/exception/ApiServerException.java
new file mode 100644
index 0000000..d89b655
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/exception/ApiServerException.java
@@ -0,0 +1,18 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+public class ApiServerException extends IOException {
+ public ApiServerException() {
+ this("");
+ }
+
+ public ApiServerException(Object... message) {
+ super(
+ new Formatter()
+ .format("API Server exception: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/exception/ApiServerNotAvailableException.java b/gui/src/main/java/com/repoachiever/exception/ApiServerNotAvailableException.java
new file mode 100644
index 0000000..f8d332a
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/exception/ApiServerNotAvailableException.java
@@ -0,0 +1,10 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Formatter;
+
+public class ApiServerNotAvailableException extends IOException {
+ public ApiServerNotAvailableException(Object... message) {
+ super(new Formatter().format("API Server is not available", message).toString());
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/exception/ApplicationFontFileNotFoundException.java b/gui/src/main/java/com/repoachiever/exception/ApplicationFontFileNotFoundException.java
new file mode 100644
index 0000000..77e759c
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/exception/ApplicationFontFileNotFoundException.java
@@ -0,0 +1,16 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+public class ApplicationFontFileNotFoundException extends IOException {
+ public ApplicationFontFileNotFoundException(Object... message) {
+ super(
+ new Formatter()
+ .format(
+ "Application font file at the given location is not available: %s",
+ Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/exception/ApplicationImageFileNotFoundException.java b/gui/src/main/java/com/repoachiever/exception/ApplicationImageFileNotFoundException.java
new file mode 100644
index 0000000..ec1ed6c
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/exception/ApplicationImageFileNotFoundException.java
@@ -0,0 +1,20 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+public class ApplicationImageFileNotFoundException extends IOException {
+ public ApplicationImageFileNotFoundException() {
+ this("");
+ }
+
+ public ApplicationImageFileNotFoundException(Object... message) {
+ super(
+ new Formatter()
+ .format(
+ "Application image file at the given location is not available: %s",
+ Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/exception/CloudCredentialsFileNotFoundException.java b/gui/src/main/java/com/repoachiever/exception/CloudCredentialsFileNotFoundException.java
new file mode 100644
index 0000000..43961c6
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/exception/CloudCredentialsFileNotFoundException.java
@@ -0,0 +1,19 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+public class CloudCredentialsFileNotFoundException extends IOException {
+ public CloudCredentialsFileNotFoundException() {
+ this("");
+ }
+
+ public CloudCredentialsFileNotFoundException(Object... message) {
+ super(
+ new Formatter()
+ .format(
+ "Given cloud credentials file is not found: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/exception/CloudCredentialsValidationException.java b/gui/src/main/java/com/repoachiever/exception/CloudCredentialsValidationException.java
new file mode 100644
index 0000000..713b34f
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/exception/CloudCredentialsValidationException.java
@@ -0,0 +1,18 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+public class CloudCredentialsValidationException extends IOException {
+ public CloudCredentialsValidationException() {
+ this("");
+ }
+
+ public CloudCredentialsValidationException(Object... message) {
+ super(
+ new Formatter()
+ .format("Given cloud credentials are not valid!: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/exception/CommandExecutorException.java b/gui/src/main/java/com/repoachiever/exception/CommandExecutorException.java
new file mode 100644
index 0000000..600bcb3
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/exception/CommandExecutorException.java
@@ -0,0 +1,10 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Formatter;
+
+public class CommandExecutorException extends IOException {
+ public CommandExecutorException(Object... message) {
+ super(new Formatter().format("Invalid command executor behaviour", message).toString());
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/exception/ScriptDataValidationException.java b/gui/src/main/java/com/repoachiever/exception/ScriptDataValidationException.java
new file mode 100644
index 0000000..17250fb
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/exception/ScriptDataValidationException.java
@@ -0,0 +1,19 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/** Represents exception, when given file is not valid. */
+public class ScriptDataValidationException extends IOException {
+ public ScriptDataValidationException() {
+ this("");
+ }
+
+ public ScriptDataValidationException(Object... message) {
+ super(
+ new Formatter()
+ .format("Given explicit script file is not valid: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/exception/SmartGraphCssFileNotFoundException.java b/gui/src/main/java/com/repoachiever/exception/SmartGraphCssFileNotFoundException.java
new file mode 100644
index 0000000..856d7e8
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/exception/SmartGraphCssFileNotFoundException.java
@@ -0,0 +1,13 @@
+package com.repoachiever.exception;
+
+import java.io.FileNotFoundException;
+import java.util.Formatter;
+
+public class SmartGraphCssFileNotFoundException extends FileNotFoundException {
+ public SmartGraphCssFileNotFoundException(Object... message) {
+ super(
+ new Formatter()
+ .format("SmartGraph CSS file at the given location is not available", message)
+ .toString());
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/exception/SmartGraphPropertiesFileNotFoundException.java b/gui/src/main/java/com/repoachiever/exception/SmartGraphPropertiesFileNotFoundException.java
new file mode 100644
index 0000000..5701c9f
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/exception/SmartGraphPropertiesFileNotFoundException.java
@@ -0,0 +1,13 @@
+package com.repoachiever.exception;
+
+import java.io.FileNotFoundException;
+import java.util.Formatter;
+
+public class SmartGraphPropertiesFileNotFoundException extends FileNotFoundException {
+ public SmartGraphPropertiesFileNotFoundException(Object... message) {
+ super(
+ new Formatter()
+ .format("SmartGraph properties file at the given location is not available", message)
+ .toString());
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/exception/SwapFileCreationFailedException.java b/gui/src/main/java/com/repoachiever/exception/SwapFileCreationFailedException.java
new file mode 100644
index 0000000..c14abf4
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/exception/SwapFileCreationFailedException.java
@@ -0,0 +1,19 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/** Represents exception, when swap file creation failed. */
+public class SwapFileCreationFailedException extends IOException {
+ public SwapFileCreationFailedException() {
+ this("");
+ }
+
+ public SwapFileCreationFailedException(Object... message) {
+ super(
+ new Formatter()
+ .format("Swap file creation failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/exception/SwapFileDeletionFailedException.java b/gui/src/main/java/com/repoachiever/exception/SwapFileDeletionFailedException.java
new file mode 100644
index 0000000..5f4a5d1
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/exception/SwapFileDeletionFailedException.java
@@ -0,0 +1,19 @@
+package com.repoachiever.exception;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Formatter;
+
+/** Represents exception, when swap file deletion failed. */
+public class SwapFileDeletionFailedException extends IOException {
+ public SwapFileDeletionFailedException() {
+ this("");
+ }
+
+ public SwapFileDeletionFailedException(Object... message) {
+ super(
+ new Formatter()
+ .format("Swap file deletion failed: %s", Arrays.stream(message).toArray())
+ .toString());
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/client/command/ApplyClientCommandService.java b/gui/src/main/java/com/repoachiever/service/client/command/ApplyClientCommandService.java
new file mode 100644
index 0000000..274c3c6
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/client/command/ApplyClientCommandService.java
@@ -0,0 +1,52 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.TerraformResourceApi;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.TerraformDeploymentApplication;
+import com.repoachiever.model.TerraformDeploymentApplicationResult;
+import com.repoachiever.service.client.common.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents apply client command service. */
+@Service
+public class ApplyClientCommandService
+ implements IClientCommand<
+ TerraformDeploymentApplicationResult, TerraformDeploymentApplication> {
+ @Autowired private ConfigService configService;
+
+ private TerraformResourceApi terraformResourceApi;
+
+ /**
+ * @see IClientCommand
+ */
+ @Override
+ @PostConstruct
+ public void configure() {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.terraformResourceApi = new TerraformResourceApi(apiClient);
+ }
+
+ /**
+ * @see IClientCommand
+ */
+ @Override
+ public TerraformDeploymentApplicationResult process(TerraformDeploymentApplication input)
+ throws ApiServerException {
+ try {
+ return terraformResourceApi.v1TerraformApplyPost(input).block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/client/command/DestroyClientCommandService.java b/gui/src/main/java/com/repoachiever/service/client/command/DestroyClientCommandService.java
new file mode 100644
index 0000000..fa4ffd1
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/client/command/DestroyClientCommandService.java
@@ -0,0 +1,47 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.TerraformResourceApi;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.TerraformDestructionApplication;
+import com.repoachiever.service.client.common.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents destroy client command service. */
+@Service
+public class DestroyClientCommandService
+ implements IClientCommand {
+ @Autowired private ConfigService configService;
+ private TerraformResourceApi terraformResourceApi;
+
+ /**
+ * @see IClientCommand
+ */
+ @Override
+ @PostConstruct
+ public void configure() {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.terraformResourceApi = new TerraformResourceApi(apiClient);
+ }
+
+ /**
+ * @see IClientCommand
+ */
+ public Void process(TerraformDestructionApplication input) throws ApiServerException {
+ try {
+ return terraformResourceApi.v1TerraformDestroyPost(input).block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/client/command/HealthCheckClientCommandService.java b/gui/src/main/java/com/repoachiever/service/client/command/HealthCheckClientCommandService.java
new file mode 100644
index 0000000..9a7162f
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/client/command/HealthCheckClientCommandService.java
@@ -0,0 +1,47 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.HealthResourceApi;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.HealthCheckResult;
+import com.repoachiever.service.client.common.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents health check client command service. */
+@Service
+public class HealthCheckClientCommandService implements IClientCommand {
+ @Autowired private ConfigService configService;
+
+ private HealthResourceApi healthResourceApi;
+
+ /**
+ * @see IClientCommand
+ */
+ @Override
+ @PostConstruct
+ public void configure() {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.healthResourceApi = new HealthResourceApi(apiClient);
+ }
+
+ /**
+ * @see IClientCommand
+ */
+ public HealthCheckResult process(Void input) throws ApiServerException {
+ try {
+ return healthResourceApi.v1HealthGet().block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/client/command/LogsClientCommandService.java b/gui/src/main/java/com/repoachiever/service/client/command/LogsClientCommandService.java
new file mode 100644
index 0000000..6ff0026
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/client/command/LogsClientCommandService.java
@@ -0,0 +1,46 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.TopicResourceApi;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.TopicLogsApplication;
+import com.repoachiever.model.TopicLogsResult;
+import com.repoachiever.service.client.common.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents logs topic client command service. */
+@Service
+public class LogsClientCommandService
+ implements IClientCommand {
+ private final TopicResourceApi topicResourceApi;
+
+ public LogsClientCommandService(@Autowired ConfigService configService) {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.topicResourceApi = new TopicResourceApi(apiClient);
+ }
+
+ /** */
+ @Override
+ public void configure() {}
+
+ /**
+ * @see IClientCommand
+ */
+ @Override
+ public TopicLogsResult process(TopicLogsApplication input) throws ApiServerException {
+ try {
+ return topicResourceApi.v1TopicLogsPost(input).block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/client/command/ReadinessCheckClientCommandService.java b/gui/src/main/java/com/repoachiever/service/client/command/ReadinessCheckClientCommandService.java
new file mode 100644
index 0000000..75a3b9f
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/client/command/ReadinessCheckClientCommandService.java
@@ -0,0 +1,49 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.HealthResourceApi;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.ReadinessCheckApplication;
+import com.repoachiever.model.ReadinessCheckResult;
+import com.repoachiever.service.client.common.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents readiness check client command service. */
+@Service
+public class ReadinessCheckClientCommandService
+ implements IClientCommand {
+ @Autowired private ConfigService configService;
+
+ private HealthResourceApi healthResourceApi;
+
+ /**
+ * @see IClientCommand
+ */
+ @Override
+ @PostConstruct
+ public void configure() {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.healthResourceApi = new HealthResourceApi(apiClient);
+ }
+
+ /**
+ * @see IClientCommand
+ */
+ public ReadinessCheckResult process(ReadinessCheckApplication input) throws ApiServerException {
+ try {
+ return healthResourceApi.v1ReadinessPost(input).block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/client/command/ScriptAcquireClientCommandService.java b/gui/src/main/java/com/repoachiever/service/client/command/ScriptAcquireClientCommandService.java
new file mode 100644
index 0000000..d2c089c
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/client/command/ScriptAcquireClientCommandService.java
@@ -0,0 +1,54 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.ValidationResourceApi;
+import com.repoachiever.dto.ValidationScriptApplicationDto;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.ValidationScriptApplication;
+import com.repoachiever.model.ValidationScriptApplicationResult;
+import com.repoachiever.service.client.common.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents script validation client command service. */
+@Service
+public class ScriptAcquireClientCommandService
+ implements IClientCommand {
+ @Autowired private ConfigService configService;
+
+ private ValidationResourceApi validationResourceApi;
+
+ /**
+ * @see IClientCommand
+ */
+ @Override
+ @PostConstruct
+ public void configure() {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.validationResourceApi = new ValidationResourceApi(apiClient);
+ }
+
+ /**
+ * @see IClientCommand
+ */
+ @Override
+ public ValidationScriptApplicationResult process(ValidationScriptApplicationDto input)
+ throws ApiServerException {
+ try {
+ return validationResourceApi
+ .v1ScriptAcquirePost(ValidationScriptApplication.of(input.getFileContent()))
+ .block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/client/command/SecretsAcquireClientCommandService.java b/gui/src/main/java/com/repoachiever/service/client/command/SecretsAcquireClientCommandService.java
new file mode 100644
index 0000000..f3d9d2a
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/client/command/SecretsAcquireClientCommandService.java
@@ -0,0 +1,74 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.ValidationResourceApi;
+import com.repoachiever.dto.ValidationSecretsApplicationDto;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.exception.CloudCredentialsFileNotFoundException;
+import com.repoachiever.model.Provider;
+import com.repoachiever.model.ValidationSecretsApplication;
+import com.repoachiever.model.ValidationSecretsApplicationResult;
+import com.repoachiever.service.client.common.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import jakarta.annotation.PostConstruct;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents secrets validation client command service. */
+@Service
+public class SecretsAcquireClientCommandService
+ implements IClientCommand {
+ @Autowired private ConfigService configService;
+
+ private ValidationResourceApi validationResourceApi;
+
+ /**
+ * @see IClientCommand
+ */
+ @Override
+ @PostConstruct
+ public void configure() {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.validationResourceApi = new ValidationResourceApi(apiClient);
+ }
+
+ /**
+ * @see IClientCommand
+ */
+ @Override
+ public ValidationSecretsApplicationResult process(ValidationSecretsApplicationDto input)
+ throws ApiServerException {
+ Path filePath = Paths.get(input.getFilePath());
+
+ if (Files.notExists(filePath)) {
+ throw new ApiServerException(new CloudCredentialsFileNotFoundException().getMessage());
+ }
+
+ String content;
+
+ try {
+ content = Files.readString(filePath);
+ } catch (IOException e) {
+ throw new ApiServerException(e.getMessage());
+ }
+
+ try {
+ return validationResourceApi
+ .v1SecretsAcquirePost(ValidationSecretsApplication.of(Provider.AWS, content))
+ .block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getHeaders()).getMessage());
+ }
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/client/command/VersionClientCommandService.java b/gui/src/main/java/com/repoachiever/service/client/command/VersionClientCommandService.java
new file mode 100644
index 0000000..b53a972
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/client/command/VersionClientCommandService.java
@@ -0,0 +1,47 @@
+package com.repoachiever.service.client.command;
+
+import com.repoachiever.ApiClient;
+import com.repoachiever.api.InfoResourceApi;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.ApplicationInfoResult;
+import com.repoachiever.service.client.common.IClientCommand;
+import com.repoachiever.service.config.ConfigService;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+
+/** Represents version information client command service. */
+@Service
+public class VersionClientCommandService implements IClientCommand {
+ @Autowired private ConfigService configService;
+
+ private InfoResourceApi infoResourceApi;
+
+ /**
+ * @see IClientCommand
+ */
+ @Override
+ @PostConstruct
+ public void configure() {
+ ApiClient apiClient =
+ new ApiClient().setBasePath(configService.getConfig().getApiServer().getHost());
+
+ this.infoResourceApi = new InfoResourceApi(apiClient);
+ }
+
+ /**
+ * @see IClientCommand
+ */
+ public ApplicationInfoResult process(Void input) throws ApiServerException {
+ try {
+ return infoResourceApi.v1InfoVersionGet().block();
+ } catch (WebClientResponseException e) {
+ throw new ApiServerException(e.getResponseBodyAsString());
+ } catch (WebClientRequestException e) {
+ throw new ApiServerException(new ApiServerNotAvailableException(e.getMessage()).getMessage());
+ }
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/client/common/IClientCommand.java b/gui/src/main/java/com/repoachiever/service/client/common/IClientCommand.java
new file mode 100644
index 0000000..c6645e6
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/client/common/IClientCommand.java
@@ -0,0 +1,22 @@
+package com.repoachiever.service.client.common;
+
+import com.repoachiever.exception.ApiServerException;
+
+/**
+ * Represents external resource command interface.
+ *
+ * @param type of the command response.
+ * @param type of the command request.
+ */
+public interface IClientCommand {
+ /** Provides configuration of the API Client. */
+ void configure();
+
+ /**
+ * Processes certain request for an external command.
+ *
+ * @param input input to be given as request body.
+ * @return command response.
+ */
+ T process(K input) throws ApiServerException;
+}
diff --git a/gui/src/main/java/com/repoachiever/service/client/observer/ResourceObserver.java b/gui/src/main/java/com/repoachiever/service/client/observer/ResourceObserver.java
new file mode 100644
index 0000000..91fa45f
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/client/observer/ResourceObserver.java
@@ -0,0 +1,52 @@
+package com.repoachiever.service.client.observer;
+
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.model.HealthCheckResult;
+import com.repoachiever.service.client.command.HealthCheckClientCommandService;
+import com.repoachiever.service.client.command.ReadinessCheckClientCommandService;
+import com.repoachiever.service.event.payload.ConnectionStatusEvent;
+import com.repoachiever.service.hand.executor.CommandExecutorService;
+import com.repoachiever.service.scheduler.SchedulerHelper;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Component;
+
+/** Provides resource observables to manage state of the application. */
+@Component
+public class ResourceObserver {
+ @Autowired private ApplicationEventPublisher applicationEventPublisher;
+
+ @Autowired private PropertiesEntity properties;
+
+ @Autowired private CommandExecutorService commandExecutorService;
+
+ @Autowired private HealthCheckClientCommandService healthCommandService;
+
+ @Autowired private ReadinessCheckClientCommandService readinessCommandService;
+
+ /** Sends healthcheck requests to API Server and updates connection status. */
+ @PostConstruct
+ private void handleHealthCommand() {
+ SchedulerHelper.scheduleTask(
+ () -> {
+ ConnectionStatusEvent connectionStatusEvent;
+
+ try {
+ HealthCheckResult result = healthCommandService.process(null);
+
+ connectionStatusEvent =
+ switch (result.getStatus()) {
+ case UP -> new ConnectionStatusEvent(true);
+ case DOWN -> new ConnectionStatusEvent(false);
+ };
+ } catch (ApiServerException e) {
+ connectionStatusEvent = new ConnectionStatusEvent(false);
+ }
+
+ applicationEventPublisher.publishEvent(connectionStatusEvent);
+ },
+ properties.getProcessHealthcheckPeriod());
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/command/common/ICommand.java b/gui/src/main/java/com/repoachiever/service/command/common/ICommand.java
new file mode 100644
index 0000000..1352379
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/command/common/ICommand.java
@@ -0,0 +1,9 @@
+package com.repoachiever.service.command.common;
+
+import com.repoachiever.exception.ApiServerException;
+
+/** Represents common command interface. */
+public interface ICommand {
+ /** Processes certain request for an external command. */
+ T process() throws ApiServerException;
+}
diff --git a/gui/src/main/java/com/repoachiever/service/command/external/start/StartExternalCommandService.java b/gui/src/main/java/com/repoachiever/service/command/external/start/StartExternalCommandService.java
new file mode 100644
index 0000000..e6096db
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/command/external/start/StartExternalCommandService.java
@@ -0,0 +1,25 @@
+package com.repoachiever.service.command.external.start;
+
+import com.repoachiever.dto.StartExternalCommandResultDto;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.command.external.start.provider.aws.AWSStartExternalCommandService;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents start external command service. */
+@Service
+public class StartExternalCommandService implements ICommand {
+ @Autowired private ConfigService configService;
+
+ @Autowired private AWSStartExternalCommandService awsStartExternalCommandService;
+
+ /**
+ * @see ICommand
+ */
+ public StartExternalCommandResultDto process() {
+ return switch (configService.getConfig().getCloud().getProvider()) {
+ case AWS -> awsStartExternalCommandService.process();
+ };
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/command/external/start/provider/aws/AWSStartExternalCommandService.java b/gui/src/main/java/com/repoachiever/service/command/external/start/provider/aws/AWSStartExternalCommandService.java
new file mode 100644
index 0000000..faadc82
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/command/external/start/provider/aws/AWSStartExternalCommandService.java
@@ -0,0 +1,110 @@
+package com.repoachiever.service.command.external.start.provider.aws;
+
+import com.repoachiever.converter.CredentialsConverter;
+import com.repoachiever.dto.StartExternalCommandResultDto;
+import com.repoachiever.dto.ValidationScriptApplicationDto;
+import com.repoachiever.dto.ValidationSecretsApplicationDto;
+import com.repoachiever.entity.ConfigEntity;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.CloudCredentialsValidationException;
+import com.repoachiever.exception.ScriptDataValidationException;
+import com.repoachiever.model.*;
+import com.repoachiever.service.client.command.ApplyClientCommandService;
+import com.repoachiever.service.client.command.ScriptAcquireClientCommandService;
+import com.repoachiever.service.client.command.SecretsAcquireClientCommandService;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.config.ConfigService;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents start external command service for AWS provider. */
+@Service
+public class AWSStartExternalCommandService implements ICommand {
+ @Autowired private ConfigService configService;
+
+ @Autowired private ApplyClientCommandService applyClientCommandService;
+
+ @Autowired private SecretsAcquireClientCommandService secretsAcquireClientCommandService;
+
+ @Autowired private ScriptAcquireClientCommandService scriptAcquireClientCommandService;
+
+ /**
+ * @see ICommand
+ */
+ @Override
+ public StartExternalCommandResultDto process() {
+ ConfigEntity.Cloud.AWSCredentials credentials =
+ CredentialsConverter.convert(
+ configService.getConfig().getCloud().getCredentials(),
+ ConfigEntity.Cloud.AWSCredentials.class);
+
+ ValidationSecretsApplicationDto validationSecretsApplicationDto =
+ ValidationSecretsApplicationDto.of(Provider.AWS, credentials.getFile());
+
+ ValidationSecretsApplicationResult validationSecretsApplicationResult;
+ try {
+ validationSecretsApplicationResult =
+ secretsAcquireClientCommandService.process(validationSecretsApplicationDto);
+ } catch (ApiServerException e) {
+ return StartExternalCommandResultDto.of(false, e.getMessage());
+ }
+
+ if (validationSecretsApplicationResult.getValid()) {
+ List requests = new ArrayList<>();
+
+ for (ConfigEntity.Request request : configService.getConfig().getRequests()) {
+ try {
+ requests.add(DeploymentRequest.of(
+ request.getName(),
+ Files.readString(Paths.get(request.getFile())),
+ request.getFrequency()));
+ } catch (IOException e) {
+ return StartExternalCommandResultDto.of(false, new ScriptDataValidationException(e.getMessage()).getMessage());
+ }
+ }
+
+ ValidationScriptApplicationDto validationScriptApplicationDto =
+ ValidationScriptApplicationDto.of(
+ requests.stream().map(DeploymentRequest::getScript).toList());
+
+ ValidationScriptApplicationResult validationScriptApplicationResult;
+ try {
+ validationScriptApplicationResult =
+ scriptAcquireClientCommandService.process(validationScriptApplicationDto);
+ } catch (ApiServerException e) {
+ return StartExternalCommandResultDto.of(false, e.getMessage());
+ }
+
+ if (validationScriptApplicationResult.getValid()) {
+ CredentialsFields credentialsFields =
+ CredentialsFields.of(
+ AWSSecrets.of(
+ validationSecretsApplicationResult.getSecrets().getAccessKey(),
+ validationSecretsApplicationResult.getSecrets().getSecretKey()),
+ credentials.getRegion());
+
+ TerraformDeploymentApplication terraformDeploymentApplication =
+ TerraformDeploymentApplication.of(requests, Provider.AWS, credentialsFields);
+
+ try {
+ applyClientCommandService.process(terraformDeploymentApplication);
+ } catch (ApiServerException e) {
+ return StartExternalCommandResultDto.of(false, e.getMessage());
+ }
+
+ return StartExternalCommandResultDto.of(true, null);
+ } else {
+ return StartExternalCommandResultDto.of(
+ false, new ScriptDataValidationException().getMessage());
+ }
+ } else {
+ return StartExternalCommandResultDto.of(
+ false, new CloudCredentialsValidationException().getMessage());
+ }
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/command/external/state/StateExternalCommandService.java b/gui/src/main/java/com/repoachiever/service/command/external/state/StateExternalCommandService.java
new file mode 100644
index 0000000..a7d0d5e
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/command/external/state/StateExternalCommandService.java
@@ -0,0 +1,25 @@
+package com.repoachiever.service.command.external.state;
+
+import com.repoachiever.dto.StateExternalCommandResultDto;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.command.external.state.provider.aws.AWSStateExternalCommandService;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents state external command service. */
+@Service
+public class StateExternalCommandService implements ICommand {
+ @Autowired private ConfigService configService;
+
+ @Autowired private AWSStateExternalCommandService awsStateExternalCommandService;
+
+ /**
+ * @see ICommand
+ */
+ public StateExternalCommandResultDto process() {
+ return switch (configService.getConfig().getCloud().getProvider()) {
+ case AWS -> awsStateExternalCommandService.process();
+ };
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/command/external/state/provider/aws/AWSStateExternalCommandService.java b/gui/src/main/java/com/repoachiever/service/command/external/state/provider/aws/AWSStateExternalCommandService.java
new file mode 100644
index 0000000..838a1c2
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/command/external/state/provider/aws/AWSStateExternalCommandService.java
@@ -0,0 +1,72 @@
+package com.repoachiever.service.command.external.state.provider.aws;
+
+import com.repoachiever.converter.CredentialsConverter;
+import com.repoachiever.dto.StateExternalCommandResultDto;
+import com.repoachiever.dto.ValidationSecretsApplicationDto;
+import com.repoachiever.entity.ConfigEntity;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.CloudCredentialsValidationException;
+import com.repoachiever.model.*;
+import com.repoachiever.service.client.command.LogsClientCommandService;
+import com.repoachiever.service.client.command.SecretsAcquireClientCommandService;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents start external command service for AWS provider. */
+@Service
+public class AWSStateExternalCommandService implements ICommand {
+ @Autowired private ConfigService configService;
+
+ @Autowired private SecretsAcquireClientCommandService secretsAcquireClientCommandService;
+
+ @Autowired private LogsClientCommandService logsClientCommandService;
+
+ /**
+ * @see ICommand
+ */
+ @Override
+ public StateExternalCommandResultDto process() {
+ ConfigEntity.Cloud.AWSCredentials credentials =
+ CredentialsConverter.convert(
+ configService.getConfig().getCloud().getCredentials(),
+ ConfigEntity.Cloud.AWSCredentials.class);
+
+ ValidationSecretsApplicationDto validationSecretsApplicationDto =
+ ValidationSecretsApplicationDto.of(Provider.AWS, credentials.getFile());
+
+ ValidationSecretsApplicationResult validationSecretsApplicationResult;
+ try {
+ validationSecretsApplicationResult =
+ secretsAcquireClientCommandService.process(validationSecretsApplicationDto);
+ } catch (ApiServerException e) {
+ return StateExternalCommandResultDto.of(null, false, e.getMessage());
+ }
+
+ if (validationSecretsApplicationResult.getValid()) {
+ CredentialsFields credentialsFields =
+ CredentialsFields.of(
+ AWSSecrets.of(
+ validationSecretsApplicationResult.getSecrets().getAccessKey(),
+ validationSecretsApplicationResult.getSecrets().getSecretKey()),
+ credentials.getRegion());
+
+ TopicLogsResult topicLogsResult;
+
+ TopicLogsApplication topicLogsApplication =
+ TopicLogsApplication.of(Provider.AWS, credentialsFields);
+
+ try {
+ topicLogsResult = logsClientCommandService.process(topicLogsApplication);
+ } catch (ApiServerException e) {
+ return StateExternalCommandResultDto.of(null, false, e.getMessage());
+ }
+
+ return StateExternalCommandResultDto.of(topicLogsResult, true, null);
+ } else {
+ return StateExternalCommandResultDto.of(
+ null, false, new CloudCredentialsValidationException().getMessage());
+ }
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/command/external/stop/StopExternalCommandService.java b/gui/src/main/java/com/repoachiever/service/command/external/stop/StopExternalCommandService.java
new file mode 100644
index 0000000..a6e0d83
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/command/external/stop/StopExternalCommandService.java
@@ -0,0 +1,25 @@
+package com.repoachiever.service.command.external.stop;
+
+import com.repoachiever.dto.StopExternalCommandResultDto;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.command.external.stop.provider.aws.AWSStopExternalCommandService;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents stop external command service. */
+@Service
+public class StopExternalCommandService implements ICommand {
+ @Autowired private ConfigService configService;
+
+ @Autowired private AWSStopExternalCommandService stopExternalCommandService;
+
+ /**
+ * @see ICommand
+ */
+ public StopExternalCommandResultDto process() {
+ return switch (configService.getConfig().getCloud().getProvider()) {
+ case AWS -> stopExternalCommandService.process();
+ };
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/command/external/stop/provider/aws/AWSStopExternalCommandService.java b/gui/src/main/java/com/repoachiever/service/command/external/stop/provider/aws/AWSStopExternalCommandService.java
new file mode 100644
index 0000000..4f58d3b
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/command/external/stop/provider/aws/AWSStopExternalCommandService.java
@@ -0,0 +1,75 @@
+package com.repoachiever.service.command.external.stop.provider.aws;
+
+import com.repoachiever.converter.CredentialsConverter;
+import com.repoachiever.dto.StopExternalCommandResultDto;
+import com.repoachiever.dto.ValidationSecretsApplicationDto;
+import com.repoachiever.entity.ConfigEntity;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.CloudCredentialsValidationException;
+import com.repoachiever.model.*;
+import com.repoachiever.service.client.command.DestroyClientCommandService;
+import com.repoachiever.service.client.command.SecretsAcquireClientCommandService;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** */
+@Service
+public class AWSStopExternalCommandService implements ICommand {
+ @Autowired private ConfigService configService;
+
+ @Autowired private DestroyClientCommandService destroyClientCommandService;
+
+ @Autowired private SecretsAcquireClientCommandService secretsAcquireClientCommandService;
+
+ /**
+ * @see ICommand
+ */
+ @Override
+ public StopExternalCommandResultDto process() {
+ ConfigEntity.Cloud.AWSCredentials credentials =
+ CredentialsConverter.convert(
+ configService.getConfig().getCloud().getCredentials(),
+ ConfigEntity.Cloud.AWSCredentials.class);
+
+ ValidationSecretsApplicationDto validationSecretsApplicationDto =
+ ValidationSecretsApplicationDto.of(Provider.AWS, credentials.getFile());
+
+ ValidationSecretsApplicationResult validationSecretsApplicationResult;
+ try {
+ validationSecretsApplicationResult =
+ secretsAcquireClientCommandService.process(validationSecretsApplicationDto);
+ } catch (ApiServerException e) {
+ return StopExternalCommandResultDto.of(false, e.getMessage());
+ }
+
+ if (validationSecretsApplicationResult.getValid()) {
+ CredentialsFields credentialsFields =
+ CredentialsFields.of(
+ AWSSecrets.of(
+ validationSecretsApplicationResult.getSecrets().getAccessKey(),
+ validationSecretsApplicationResult.getSecrets().getSecretKey()),
+ credentials.getRegion());
+
+ TerraformDestructionApplication terraformDestructionApplication =
+ TerraformDestructionApplication.of(
+ configService.getConfig().getRequests().stream()
+ .map(element -> DestructionRequest.of(element.getName()))
+ .toList(),
+ Provider.AWS,
+ credentialsFields);
+
+ try {
+ destroyClientCommandService.process(terraformDestructionApplication);
+ } catch (ApiServerException e) {
+ return StopExternalCommandResultDto.of(false, e.getMessage());
+ }
+
+ return StopExternalCommandResultDto.of(true, null);
+ } else {
+ return StopExternalCommandResultDto.of(
+ false, new CloudCredentialsValidationException().getMessage());
+ }
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/command/external/version/VersionExternalCommandService.java b/gui/src/main/java/com/repoachiever/service/command/external/version/VersionExternalCommandService.java
new file mode 100644
index 0000000..7eb4406
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/command/external/version/VersionExternalCommandService.java
@@ -0,0 +1,33 @@
+package com.repoachiever.service.command.external.version;
+
+import com.repoachiever.dto.VersionExternalCommandResultDto;
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.model.ApplicationInfoResult;
+import com.repoachiever.service.client.command.VersionClientCommandService;
+import com.repoachiever.service.command.common.ICommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents version external command service. */
+@Service
+public class VersionExternalCommandService implements ICommand {
+ @Autowired private PropertiesEntity properties;
+
+ @Autowired private VersionClientCommandService versionClientCommandService;
+
+ /**
+ * @see ICommand
+ */
+ public VersionExternalCommandResultDto process() {
+ ApplicationInfoResult applicationInfoResult;
+ try {
+ applicationInfoResult = versionClientCommandService.process(null);
+ } catch (ApiServerException e) {
+ return VersionExternalCommandResultDto.of(null, false, e.getMessage());
+ }
+
+ return VersionExternalCommandResultDto.of(
+ applicationInfoResult.getExternalApi().getHash(), true, null);
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/command/internal/health/HealthCheckInternalCommandService.java b/gui/src/main/java/com/repoachiever/service/command/internal/health/HealthCheckInternalCommandService.java
new file mode 100644
index 0000000..0347b14
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/command/internal/health/HealthCheckInternalCommandService.java
@@ -0,0 +1,41 @@
+package com.repoachiever.service.command.internal.health;
+
+import com.repoachiever.dto.HealthCheckInternalCommandResultDto;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.model.HealthCheckResult;
+import com.repoachiever.model.HealthCheckStatus;
+import com.repoachiever.service.client.command.HealthCheckClientCommandService;
+import com.repoachiever.service.command.common.ICommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class HealthCheckInternalCommandService
+ implements ICommand {
+ @Autowired private HealthCheckClientCommandService healthCheckClientCommandService;
+
+ /**
+ * @see ICommand
+ */
+ @Override
+ public HealthCheckInternalCommandResultDto process() {
+ HealthCheckResult healthCheckResult;
+ try {
+ healthCheckResult = healthCheckClientCommandService.process(null);
+ } catch (ApiServerException e) {
+ return HealthCheckInternalCommandResultDto.of(false, e.getMessage());
+ }
+
+ if (healthCheckResult.getStatus() == HealthCheckStatus.DOWN) {
+ return HealthCheckInternalCommandResultDto.of(
+ false,
+ new ApiServerException(
+ new ApiServerNotAvailableException(healthCheckResult.getChecks().toString())
+ .getMessage())
+ .getMessage());
+ }
+
+ return HealthCheckInternalCommandResultDto.of(true, null);
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/command/internal/readiness/ReadinessCheckInternalCommandService.java b/gui/src/main/java/com/repoachiever/service/command/internal/readiness/ReadinessCheckInternalCommandService.java
new file mode 100644
index 0000000..053e8dd
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/command/internal/readiness/ReadinessCheckInternalCommandService.java
@@ -0,0 +1,28 @@
+package com.repoachiever.service.command.internal.readiness;
+
+import com.repoachiever.dto.ReadinessCheckInternalCommandResultDto;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.command.internal.readiness.provider.aws.AWSReadinessCheckInternalCommandService;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents readiness check internal command service. */
+@Service
+public class ReadinessCheckInternalCommandService
+ implements ICommand {
+ @Autowired private ConfigService configService;
+
+ @Autowired
+ private AWSReadinessCheckInternalCommandService awsReadinessCheckInternalCommandService;
+
+ /**
+ * @see ICommand
+ */
+ @Override
+ public ReadinessCheckInternalCommandResultDto process() {
+ return switch (configService.getConfig().getCloud().getProvider()) {
+ case AWS -> awsReadinessCheckInternalCommandService.process();
+ };
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/command/internal/readiness/provider/aws/AWSReadinessCheckInternalCommandService.java b/gui/src/main/java/com/repoachiever/service/command/internal/readiness/provider/aws/AWSReadinessCheckInternalCommandService.java
new file mode 100644
index 0000000..081da5c
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/command/internal/readiness/provider/aws/AWSReadinessCheckInternalCommandService.java
@@ -0,0 +1,82 @@
+package com.repoachiever.service.command.internal.readiness.provider.aws;
+
+import com.repoachiever.converter.CredentialsConverter;
+import com.repoachiever.dto.ReadinessCheckInternalCommandResultDto;
+import com.repoachiever.dto.ValidationSecretsApplicationDto;
+import com.repoachiever.entity.ConfigEntity;
+import com.repoachiever.exception.ApiServerException;
+import com.repoachiever.exception.ApiServerNotAvailableException;
+import com.repoachiever.exception.CloudCredentialsValidationException;
+import com.repoachiever.model.*;
+import com.repoachiever.service.client.command.ReadinessCheckClientCommandService;
+import com.repoachiever.service.client.command.SecretsAcquireClientCommandService;
+import com.repoachiever.service.command.common.ICommand;
+import com.repoachiever.service.config.ConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AWSReadinessCheckInternalCommandService
+ implements ICommand {
+ @Autowired private ConfigService configService;
+
+ @Autowired private SecretsAcquireClientCommandService secretsAcquireClientCommandService;
+
+ @Autowired private ReadinessCheckClientCommandService readinessCheckClientCommandService;
+
+ /**
+ * @see ICommand
+ */
+ @Override
+ public ReadinessCheckInternalCommandResultDto process() {
+ ConfigEntity.Cloud.AWSCredentials credentials =
+ CredentialsConverter.convert(
+ configService.getConfig().getCloud().getCredentials(),
+ ConfigEntity.Cloud.AWSCredentials.class);
+
+ ValidationSecretsApplicationDto validationSecretsApplicationDto =
+ ValidationSecretsApplicationDto.of(Provider.AWS, credentials.getFile());
+
+ ValidationSecretsApplicationResult validationSecretsApplicationResult;
+ try {
+ validationSecretsApplicationResult =
+ secretsAcquireClientCommandService.process(validationSecretsApplicationDto);
+ } catch (ApiServerException e) {
+ return ReadinessCheckInternalCommandResultDto.of(false, e.getMessage());
+ }
+
+ if (validationSecretsApplicationResult.getValid()) {
+ CredentialsFields credentialsFields =
+ CredentialsFields.of(
+ AWSSecrets.of(
+ validationSecretsApplicationResult.getSecrets().getAccessKey(),
+ validationSecretsApplicationResult.getSecrets().getSecretKey()),
+ credentials.getRegion());
+
+ ReadinessCheckApplication readinessCheckApplication =
+ ReadinessCheckApplication.of(Provider.AWS, credentialsFields);
+
+ ReadinessCheckResult readinessCheckResult;
+ try {
+ readinessCheckResult =
+ readinessCheckClientCommandService.process(readinessCheckApplication);
+ } catch (ApiServerException e) {
+ return ReadinessCheckInternalCommandResultDto.of(false, e.getMessage());
+ }
+
+ if (readinessCheckResult.getStatus() == ReadinessCheckStatus.DOWN) {
+ return ReadinessCheckInternalCommandResultDto.of(
+ false,
+ new ApiServerException(
+ new ApiServerNotAvailableException(readinessCheckResult.getData().toString())
+ .getMessage())
+ .getMessage());
+ }
+
+ return ReadinessCheckInternalCommandResultDto.of(true, null);
+ } else {
+ return ReadinessCheckInternalCommandResultDto.of(
+ false, new CloudCredentialsValidationException().getMessage());
+ }
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/config/ConfigService.java b/gui/src/main/java/com/repoachiever/service/config/ConfigService.java
new file mode 100644
index 0000000..cb66093
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/config/ConfigService.java
@@ -0,0 +1,78 @@
+package com.repoachiever.service.config;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.repoachiever.entity.ConfigEntity;
+import com.repoachiever.entity.PropertiesEntity;
+import jakarta.annotation.PostConstruct;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Paths;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/** Represents service used for configuration processing. */
+@Service
+public class ConfigService {
+ private static final Logger logger = LogManager.getLogger(ConfigService.class);
+
+ @Autowired private PropertiesEntity properties;
+
+ private ConfigEntity parsedConfigFile;
+
+ /** Processes configuration file */
+ @PostConstruct
+ public void configure() {
+ InputStream configFile;
+
+ try {
+ configFile =
+ new FileInputStream(
+ Paths.get(
+ System.getProperty("user.home"),
+ properties.getConfigRootPath(),
+ properties.getConfigUserFilePath())
+ .toString());
+ } catch (FileNotFoundException e) {
+ logger.fatal(e.getMessage());
+ return;
+ }
+
+ ObjectMapper mapper =
+ new ObjectMapper(new YAMLFactory())
+ .configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, true)
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
+ .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ ObjectReader reader = mapper.reader().forType(new TypeReference() {});
+
+ try {
+ parsedConfigFile = reader.readValues(configFile).readAll().getFirst();
+ } catch (IOException e) {
+ logger.fatal(e.getMessage());
+ } finally {
+ try {
+ configFile.close();
+ } catch (IOException e) {
+ logger.fatal(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Retrieves parsed configuration file entity.
+ *
+ * @return retrieved parsed configuration file entity.
+ */
+ public ConfigEntity getConfig() {
+ return parsedConfigFile;
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/element/alert/ErrorAlert.java b/gui/src/main/java/com/repoachiever/service/element/alert/ErrorAlert.java
new file mode 100644
index 0000000..f6a4d89
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/element/alert/ErrorAlert.java
@@ -0,0 +1,43 @@
+package com.repoachiever.service.element.alert;
+
+import com.repoachiever.service.element.storage.ElementStorage;
+import com.repoachiever.service.element.text.common.IElement;
+import ink.bluecloud.css.CssResources;
+import ink.bluecloud.css.ElementButton;
+import ink.bluecloud.css.ElementButtonKt;
+import java.util.UUID;
+import javafx.application.Platform;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Button;
+import javafx.scene.control.ButtonType;
+import org.springframework.stereotype.Service;
+
+/** Represents error related alert. */
+@Service
+public class ErrorAlert implements IElement {
+ UUID id = UUID.randomUUID();
+
+ public ErrorAlert() {
+ Platform.runLater(
+ () -> {
+ Alert alert = new Alert(Alert.AlertType.ERROR);
+ alert.setTitle("Error occurred");
+
+ ElementButtonKt.theme(
+ (Button) alert.getDialogPane().lookupButton(ButtonType.OK), ElementButton.redButton);
+ alert.getDialogPane().getStylesheets().add(CssResources.globalCssFile);
+ alert.getDialogPane().getStylesheets().add(CssResources.buttonCssFile);
+ alert.getDialogPane().getStylesheets().add(CssResources.textFieldCssFile);
+
+ ElementStorage.setElement(id, alert);
+ });
+ }
+
+ /**
+ * @see IElement
+ */
+ @Override
+ public Alert getContent() {
+ return ElementStorage.getElement(id);
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/element/alert/InformationAlert.java b/gui/src/main/java/com/repoachiever/service/element/alert/InformationAlert.java
new file mode 100644
index 0000000..31ca501
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/element/alert/InformationAlert.java
@@ -0,0 +1,43 @@
+package com.repoachiever.service.element.alert;
+
+import com.repoachiever.service.element.storage.ElementStorage;
+import com.repoachiever.service.element.text.common.IElement;
+import ink.bluecloud.css.CssResources;
+import ink.bluecloud.css.ElementButton;
+import ink.bluecloud.css.ElementButtonKt;
+import java.util.UUID;
+import javafx.application.Platform;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Button;
+import javafx.scene.control.ButtonType;
+import org.springframework.stereotype.Service;
+
+/** Represents error related alert. */
+@Service
+public class InformationAlert implements IElement {
+ UUID id = UUID.randomUUID();
+
+ public InformationAlert() {
+ Platform.runLater(
+ () -> {
+ Alert alert = new Alert(Alert.AlertType.INFORMATION);
+ alert.setTitle("Action information");
+
+ ElementButtonKt.theme(
+ (Button) alert.getDialogPane().lookupButton(ButtonType.OK), ElementButton.redButton);
+ alert.getDialogPane().getStylesheets().add(CssResources.globalCssFile);
+ alert.getDialogPane().getStylesheets().add(CssResources.buttonCssFile);
+ alert.getDialogPane().getStylesheets().add(CssResources.textFieldCssFile);
+
+ ElementStorage.setElement(id, alert);
+ });
+ }
+
+ /**
+ * @see IElement
+ */
+ @Override
+ public Alert getContent() {
+ return ElementStorage.getElement(id);
+ }
+}
diff --git a/gui/src/main/java/com/repoachiever/service/element/button/BasicButton.java b/gui/src/main/java/com/repoachiever/service/element/button/BasicButton.java
new file mode 100644
index 0000000..ddebf80
--- /dev/null
+++ b/gui/src/main/java/com/repoachiever/service/element/button/BasicButton.java
@@ -0,0 +1,75 @@
+package com.repoachiever.service.element.button;
+
+import com.repoachiever.entity.PropertiesEntity;
+import com.repoachiever.service.element.common.ElementHelper;
+import com.repoachiever.service.element.storage.ElementStorage;
+import com.repoachiever.service.element.text.common.IElement;
+import com.repoachiever.service.element.text.common.IElementResizable;
+import com.repoachiever.service.event.state.LocalState;
+import ink.bluecloud.css.CssResources;
+import ink.bluecloud.css.ElementButton;
+import ink.bluecloud.css.ElementButtonKt;
+import java.util.UUID;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.control.Button;
+
+/** Represents basic button. */
+public class BasicButton implements IElementResizable, IElement