feat: add feature branch deployments with per-branch isolation
Enable deploying feature branches into isolated environments on the same k3s cluster. Each branch gets its own namespace (cam-<slug>), PostgreSQL schema, and OpenSearch index prefix for data isolation while sharing the underlying infrastructure. - Make OpenSearch index prefix and DB schema configurable via env vars (defaults preserve existing behavior) - Restructure deploy/ into Kustomize base + overlays (main/feature) - Extend CI to build Docker images for all branches, not just main - Add deploy-feature job with namespace creation, secret copying, Traefik Ingress routing (<slug>-api/ui.cameleer.siegeln.net) - Add cleanup-branch job to remove namespace, PG schema, OS indices on branch deletion - Install required tools (git, jq, curl) in CI deploy containers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import org.opensearch.client.opensearch.core.search.Hit;
|
||||
import org.opensearch.client.opensearch.indices.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -30,25 +31,29 @@ import java.util.stream.Collectors;
|
||||
public class OpenSearchIndex implements SearchIndex {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(OpenSearchIndex.class);
|
||||
private static final String INDEX_PREFIX = "executions-";
|
||||
private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||
.withZone(ZoneOffset.UTC);
|
||||
|
||||
private final OpenSearchClient client;
|
||||
private final String indexPrefix;
|
||||
|
||||
public OpenSearchIndex(OpenSearchClient client) {
|
||||
public OpenSearchIndex(OpenSearchClient client,
|
||||
@Value("${opensearch.index-prefix:executions-}") String indexPrefix) {
|
||||
this.client = client;
|
||||
this.indexPrefix = indexPrefix;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
void ensureIndexTemplate() {
|
||||
String templateName = indexPrefix + "template";
|
||||
String indexPattern = indexPrefix + "*";
|
||||
try {
|
||||
boolean exists = client.indices().existsIndexTemplate(
|
||||
ExistsIndexTemplateRequest.of(b -> b.name("executions-template"))).value();
|
||||
ExistsIndexTemplateRequest.of(b -> b.name(templateName))).value();
|
||||
if (!exists) {
|
||||
client.indices().putIndexTemplate(PutIndexTemplateRequest.of(b -> b
|
||||
.name("executions-template")
|
||||
.indexPatterns(List.of("executions-*"))
|
||||
.name(templateName)
|
||||
.indexPatterns(List.of(indexPattern))
|
||||
.template(t -> t
|
||||
.settings(s -> s
|
||||
.numberOfShards("3")
|
||||
@@ -65,7 +70,7 @@ public class OpenSearchIndex implements SearchIndex {
|
||||
|
||||
@Override
|
||||
public void index(ExecutionDocument doc) {
|
||||
String indexName = INDEX_PREFIX + DAY_FMT.format(doc.startTime());
|
||||
String indexName = indexPrefix + DAY_FMT.format(doc.startTime());
|
||||
try {
|
||||
client.index(IndexRequest.of(b -> b
|
||||
.index(indexName)
|
||||
@@ -98,7 +103,7 @@ public class OpenSearchIndex implements SearchIndex {
|
||||
public long count(SearchRequest request) {
|
||||
try {
|
||||
var countReq = CountRequest.of(b -> b
|
||||
.index(INDEX_PREFIX + "*")
|
||||
.index(indexPrefix + "*")
|
||||
.query(buildQuery(request)));
|
||||
return client.count(countReq).count();
|
||||
} catch (IOException e) {
|
||||
@@ -111,7 +116,7 @@ public class OpenSearchIndex implements SearchIndex {
|
||||
public void delete(String executionId) {
|
||||
try {
|
||||
client.deleteByQuery(DeleteByQueryRequest.of(b -> b
|
||||
.index(List.of(INDEX_PREFIX + "*"))
|
||||
.index(List.of(indexPrefix + "*"))
|
||||
.query(Query.of(q -> q.term(t -> t
|
||||
.field("execution_id")
|
||||
.value(FieldValue.of(executionId)))))));
|
||||
@@ -123,7 +128,7 @@ public class OpenSearchIndex implements SearchIndex {
|
||||
private org.opensearch.client.opensearch.core.SearchRequest buildSearchRequest(
|
||||
SearchRequest request, int size) {
|
||||
return org.opensearch.client.opensearch.core.SearchRequest.of(b -> {
|
||||
b.index(INDEX_PREFIX + "*")
|
||||
b.index(indexPrefix + "*")
|
||||
.query(buildQuery(request))
|
||||
.trackTotalHits(th -> th.enabled(true))
|
||||
.size(size)
|
||||
|
||||
@@ -3,13 +3,15 @@ server:
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/cameleer3
|
||||
url: jdbc:postgresql://localhost:5432/cameleer3?currentSchema=${CAMELEER_DB_SCHEMA:public}
|
||||
username: cameleer
|
||||
password: ${CAMELEER_DB_PASSWORD:cameleer_dev}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
schemas: ${CAMELEER_DB_SCHEMA:public}
|
||||
default-schema: ${CAMELEER_DB_SCHEMA:public}
|
||||
mvc:
|
||||
async:
|
||||
request-timeout: -1
|
||||
@@ -34,6 +36,7 @@ ingestion:
|
||||
|
||||
opensearch:
|
||||
url: ${OPENSEARCH_URL:http://localhost:9200}
|
||||
index-prefix: ${CAMELEER_OPENSEARCH_INDEX_PREFIX:executions-}
|
||||
queue-size: ${CAMELEER_OPENSEARCH_QUEUE_SIZE:10000}
|
||||
debounce-ms: ${CAMELEER_OPENSEARCH_DEBOUNCE_MS:2000}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user