ArgoCD Application Sync in Java: Automating Kubernetes Deployments

ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes. This guide shows how to programmatically manage ArgoCD applications using Java.


Core Concepts

What is ArgoCD Application Sync?

  • Synchronizing Kubernetes manifests from Git repositories to clusters
  • Automated deployment and lifecycle management
  • GitOps approach: Git as the single source of truth
  • Programmatic control over ArgoCD applications

Key Operations:

  • Create/Update Applications: Define what to deploy
  • Sync Applications: Deploy latest changes
  • Get Status: Monitor deployment health
  • Rollback: Revert to previous versions
  • Delete: Remove applications

Dependencies and Setup

Maven Dependencies
<properties>
<kubernetes-client.version>6.7.2</kubernetes-client.version>
<argo-cd.version>2.7.0</argo-cd.version>
<spring-boot.version>3.1.0</spring-boot.version>
<jackson.version>2.15.2</jackson.version>
</properties>
<dependencies>
<!-- ArgoCD Client -->
<dependency>
<groupId>io.argoproj</groupId>
<artifactId>argo-cd-client</artifactId>
<version>${argo-cd.version}</version>
</dependency>
<!-- Kubernetes Client -->
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java</artifactId>
<version>${kubernetes-client.version}</version>
<classifier>17</classifier>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
ArgoCD Configuration
# application.yml
argocd:
server:
url: ${ARGOCD_SERVER:https://argocd.example.com}
token: ${ARGOCD_TOKEN:}
username: ${ARGOCD_USERNAME:admin}
password: ${ARGOCD_PASSWORD:}
insecure: ${ARGOCD_INSECURE:false}
timeout: 30000
defaults:
namespace: argocd
project: default
sync-policy:
automated:
prune: true
selfHeal: true

Core Implementation

1. Configuration Classes
@Configuration
@ConfigurationProperties(prefix = "argocd")
@Data
public class ArgoCDConfig {
private Server server = new Server();
private Defaults defaults = new Defaults();
@Data
public static class Server {
private String url;
private String token;
private String username;
private String password;
private boolean insecure;
private int timeout = 30000;
}
@Data
public static class Defaults {
private String namespace = "argocd";
private String project = "default";
private SyncPolicy syncPolicy = new SyncPolicy();
}
@Data
public static class SyncPolicy {
private Automated automated = new Automated();
private Retry retry;
private List<String> syncOptions;
}
@Data
public static class Automated {
private boolean prune = true;
private boolean selfHeal = true;
}
@Data
public static class Retry {
private int limit = 5;
private String backoff;
}
}
2. ArgoCD Client Service
@Component
@Slf4j
public class ArgoCDClient {
private final ArgoCDConfig config;
private final WebClient webClient;
private final ObjectMapper objectMapper;
public ArgoCDClient(ArgoCDConfig config, ObjectMapper objectMapper) {
this.config = config;
this.objectMapper = objectMapper;
this.webClient = buildWebClient();
}
private WebClient buildWebClient() {
WebClient.Builder builder = WebClient.builder()
.baseUrl(config.getServer().getUrl())
.defaultHeader("Content-Type", "application/json")
.defaultHeader("Accept", "application/json");
// Add authentication
if (config.getServer().getToken() != null) {
builder.defaultHeader("Authorization", 
"Bearer " + config.getServer().getToken());
} else if (config.getServer().getUsername() != null) {
// For basic auth (less common with tokens)
String credentials = config.getServer().getUsername() + ":" + 
config.getServer().getPassword();
String base64Credentials = Base64.getEncoder().encodeToString(credentials.getBytes());
builder.defaultHeader("Authorization", "Basic " + base64Credentials);
}
return builder.build();
}
public <T> T executeGet(String path, Class<T> responseType) {
try {
return webClient.get()
.uri(path)
.retrieve()
.bodyToMono(responseType)
.block();
} catch (WebClientResponseException e) {
throw new ArgoCDException("GET request failed: " + path, e);
}
}
public <T> T executePost(String path, Object body, Class<T> responseType) {
try {
return webClient.post()
.uri(path)
.bodyValue(body)
.retrieve()
.bodyToMono(responseType)
.block();
} catch (WebClientResponseException e) {
throw new ArgoCDException("POST request failed: " + path, e);
}
}
public <T> T executePut(String path, Object body, Class<T> responseType) {
try {
return webClient.put()
.uri(path)
.bodyValue(body)
.retrieve()
.bodyToMono(responseType)
.block();
} catch (WebClientResponseException e) {
throw new ArgoCDException("PUT request failed: " + path, e);
}
}
public void executeDelete(String path) {
try {
webClient.delete()
.uri(path)
.retrieve()
.toBodilessEntity()
.block();
} catch (WebClientResponseException e) {
throw new ArgoCDException("DELETE request failed: " + path, e);
}
}
}
3. Application Models
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ArgoCDApplication {
private V1ObjectMeta metadata;
private ApplicationSpec spec;
private ApplicationStatus status;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class ApplicationSpec {
private ApplicationDestination destination;
private ApplicationSource source;
private SyncPolicy syncPolicy;
private String project;
private List<Info> info;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class ApplicationDestination {
private String server;
private String namespace;
private String name;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class ApplicationSource {
private String repoURL;
private String path;
private String targetRevision;
private String chart;
private HelmSource helm;
private KustomizeSource kustomize;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class SyncPolicy {
private Automated automated;
private SyncPolicyRetry retry;
private List<String> syncOptions;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class Automated {
private boolean prune;
private boolean selfHeal;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class SyncPolicyRetry {
private Integer limit;
private RetryBackoff backoff;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class RetryBackoff {
private String duration;
private String factor;
private String maxDuration;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class Info {
private String name;
private String value;
}
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ApplicationStatus {
private List<ResourceStatus> resources;
private List<Condition> conditions;
private HealthStatus health;
private SyncStatus sync;
private String observedAt;
private String sourceType;
private String summary;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class ResourceStatus {
private String group;
private String version;
private String kind;
private String namespace;
private String name;
private String status;
private HealthStatus health;
private Boolean hook;
private Boolean requiresPruning;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class HealthStatus {
private String status;
private String message;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class SyncStatus {
private String status;
private String revision;
private List<ResourceStatus> resources;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class Condition {
private String type;
private String message;
private String lastTransitionTime;
}
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SyncOperation {
private String revision;
private Boolean prune;
private Boolean dryRun;
private Boolean apply;
private List<SyncStrategy> strategies;
private List<SyncResource> resources;
private List<String> syncOptions;
private SyncOperationRetry retry;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class SyncStrategy {
private String apply;
private SyncStrategyHook hook;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class SyncStrategyHook {
private String force;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class SyncResource {
private String group;
private String kind;
private String name;
private String namespace;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class SyncOperationRetry {
private Integer limit;
private SyncOperationRetryBackoff backoff;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class SyncOperationRetryBackoff {
private String duration;
private Integer factor;
private String maxDuration;
}
}
4. ArgoCD Application Service
@Service
@Slf4j
public class ArgoCDApplicationService {
private final ArgoCDClient argoCDClient;
private final ArgoCDConfig config;
private final ObjectMapper objectMapper;
private static final String APPLICATIONS_PATH = "/api/v1/applications";
private static final String SYNC_PATH = APPLICATIONS_PATH + "/{name}/sync";
private static final String ROLLBACK_PATH = APPLICATIONS_PATH + "/{name}/rollback";
public ArgoCDApplicationService(ArgoCDClient argoCDClient, ArgoCDConfig config, 
ObjectMapper objectMapper) {
this.argoCDClient = argoCDClient;
this.config = config;
this.objectMapper = objectMapper;
}
// Create Application
public ArgoCDApplication createApplication(CreateApplicationRequest request) {
log.info("Creating ArgoCD application: {}", request.getName());
ArgoCDApplication application = buildApplication(request);
return argoCDClient.executePost(APPLICATIONS_PATH, application, ArgoCDApplication.class);
}
// Get Application
public ArgoCDApplication getApplication(String name) {
log.debug("Getting ArgoCD application: {}", name);
String path = APPLICATIONS_PATH + "/" + name;
return argoCDClient.executeGet(path, ArgoCDApplication.class);
}
// List Applications
public List<ArgoCDApplication> listApplications() {
log.debug("Listing ArgoCD applications");
ApplicationList response = argoCDClient.executeGet(APPLICATIONS_PATH, ApplicationList.class);
return response.getItems() != null ? response.getItems() : new ArrayList<>();
}
// Sync Application
public SyncResponse syncApplication(String applicationName, SyncOperation syncOperation) {
log.info("Syncing ArgoCD application: {}", applicationName);
String path = SYNC_PATH.replace("{name}", applicationName);
return argoCDClient.executePost(path, syncOperation, SyncResponse.class);
}
// Sync Application with default options
public SyncResponse syncApplication(String applicationName) {
SyncOperation syncOperation = SyncOperation.builder()
.prune(true)
.dryRun(false)
.apply(true)
.syncOptions(List.of("CreateNamespace=true"))
.build();
return syncApplication(applicationName, syncOperation);
}
// Wait for sync completion
public boolean waitForSync(String applicationName, Duration timeout) {
log.info("Waiting for application sync: {}", applicationName);
long endTime = System.currentTimeMillis() + timeout.toMillis();
while (System.currentTimeMillis() < endTime) {
try {
ArgoCDApplication app = getApplication(applicationName);
ApplicationStatus status = app.getStatus();
if (status != null && status.getSync() != null && 
"Synced".equals(status.getSync().getStatus()) &&
status.getHealth() != null && 
"Healthy".equals(status.getHealth().getStatus())) {
log.info("Application {} synced successfully", applicationName);
return true;
}
// Log sync progress
if (status != null && status.getSync() != null) {
log.debug("Sync status: {}, Health: {}", 
status.getSync().getStatus(),
status.getHealth() != null ? status.getHealth().getStatus() : "Unknown");
}
Thread.sleep(5000); // Wait 5 seconds before next check
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ArgoCDException("Wait for sync interrupted", e);
} catch (Exception e) {
log.warn("Error checking sync status: {}", e.getMessage());
}
}
log.error("Application {} sync timeout after {}", applicationName, timeout);
return false;
}
// Update Application
public ArgoCDApplication updateApplication(String name, UpdateApplicationRequest request) {
log.info("Updating ArgoCD application: {}", name);
String path = APPLICATIONS_PATH + "/" + name;
// Get current application first
ArgoCDApplication currentApp = getApplication(name);
// Update spec with new values
ArgoCDApplication.ApplicationSpec updatedSpec = updateApplicationSpec(
currentApp.getSpec(), request);
currentApp.setSpec(updatedSpec);
return argoCDClient.executePut(path, currentApp, ArgoCDApplication.class);
}
// Delete Application
public void deleteApplication(String name, boolean cascade) {
log.info("Deleting ArgoCD application: {}", name);
String path = APPLICATIONS_PATH + "/" + name;
Map<String, Object> queryParams = new HashMap<>();
if (cascade) {
queryParams.put("cascade", true);
}
// For query parameters, we'd need to modify the client
argoCDClient.executeDelete(path);
}
// Get Application Resource Tree
public ApplicationTree getApplicationTree(String name) {
log.debug("Getting resource tree for application: {}", name);
String path = APPLICATIONS_PATH + "/" + name + "/resource-tree";
return argoCDClient.executeGet(path, ApplicationTree.class);
}
// Rollback Application
public SyncResponse rollbackApplication(String name, String revision) {
log.info("Rolling back application {} to revision: {}", name, revision);
String path = ROLLBACK_PATH.replace("{name}", name);
Map<String, Object> rollbackRequest = new HashMap<>();
rollbackRequest.put("id", revision);
rollbackRequest.put("prune", true);
return argoCDClient.executePost(path, rollbackRequest, SyncResponse.class);
}
private ArgoCDApplication buildApplication(CreateApplicationRequest request) {
V1ObjectMeta metadata = new V1ObjectMeta();
metadata.setName(request.getName());
metadata.setNamespace(config.getDefaults().getNamespace());
ArgoCDApplication.ApplicationDestination destination = 
ArgoCDApplication.ApplicationDestination.builder()
.server(request.getDestinationServer())
.namespace(request.getDestinationNamespace())
.build();
ArgoCDApplication.ApplicationSource source = 
ArgoCDApplication.ApplicationSource.builder()
.repoURL(request.getRepoUrl())
.path(request.getPath())
.targetRevision(request.getTargetRevision())
.build();
ArgoCDApplication.SyncPolicy syncPolicy = buildSyncPolicy(request);
ArgoCDApplication.ApplicationSpec spec = 
ArgoCDApplication.ApplicationSpec.builder()
.destination(destination)
.source(source)
.syncPolicy(syncPolicy)
.project(config.getDefaults().getProject())
.build();
return ArgoCDApplication.builder()
.metadata(metadata)
.spec(spec)
.build();
}
private ArgoCDApplication.SyncPolicy buildSyncPolicy(CreateApplicationRequest request) {
ArgoCDConfig.Automated configAutomated = config.getDefaults().getSyncPolicy().getAutomated();
ArgoCDApplication.Automated automated = ArgoCDApplication.Automated.builder()
.prune(request.isPrune() != null ? request.isPrune() : configAutomated.isPrune())
.selfHeal(request.isSelfHeal() != null ? request.isSelfHeal() : configAutomated.isSelfHeal())
.build();
return ArgoCDApplication.SyncPolicy.builder()
.automated(automated)
.syncOptions(List.of("CreateNamespace=true"))
.build();
}
private ArgoCDApplication.ApplicationSpec updateApplicationSpec(
ArgoCDApplication.ApplicationSpec currentSpec, UpdateApplicationRequest request) {
ArgoCDApplication.ApplicationSource updatedSource = currentSpec.getSource();
if (request.getTargetRevision() != null) {
updatedSource.setTargetRevision(request.getTargetRevision());
}
if (request.getPath() != null) {
updatedSource.setPath(request.getPath());
}
return ArgoCDApplication.ApplicationSpec.builder()
.destination(currentSpec.getDestination())
.source(updatedSource)
.syncPolicy(currentSpec.getSyncPolicy())
.project(currentSpec.getProject())
.info(currentSpec.getInfo())
.build();
}
}
// Supporting DTOs
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class CreateApplicationRequest {
@NotBlank
private String name;
@NotBlank
private String repoUrl;
@NotBlank
private String path;
private String targetRevision;
@NotBlank
private String destinationServer;
private String destinationNamespace;
private Boolean prune;
private Boolean selfHeal;
private Map<String, String> labels;
private Map<String, String> annotations;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class UpdateApplicationRequest {
private String targetRevision;
private String path;
private Boolean prune;
private Boolean selfHeal;
}
@Data
class ApplicationList {
private List<ArgoCDApplication> items;
}
@Data
class SyncResponse {
private List<OperationState> operationState;
@Data
public static class OperationState {
private String phase;
private String message;
private String startedAt;
private String finishedAt;
private SyncOperationResult syncResult;
}
@Data
public static class SyncOperationResult {
private List<ResourceResult> resources;
private String revision;
}
@Data
public static class ResourceResult {
private String group;
private String version;
private String kind;
private String namespace;
private String name;
private String status;
private String message;
private String hookType;
private String hookPhase;
private String syncPhase;
}
}
@Data
class ApplicationTree {
private List<ResourceNode> nodes;
@Data
public static class ResourceNode {
private String uid;
private String parentRefs;
private String resourceVersion;
private String name;
private String namespace;
private String kind;
private String group;
private HealthStatus health;
private List<InfoItem> info;
private List<ResourceRef> networkingInfo;
}
@Data
public static class HealthStatus {
private String status;
private String message;
}
@Data
public static class InfoItem {
private String name;
private String value;
}
@Data
public static class ResourceRef {
private String targetRef;
private String external;
}
}
// Custom Exceptions
public class ArgoCDException extends RuntimeException {
public ArgoCDException(String message) {
super(message);
}
public ArgoCDException(String message, Throwable cause) {
super(message, cause);
}
}
5. Application Synchronization Manager
@Service
@Slf4j
public class ApplicationSyncManager {
private final ArgoCDApplicationService applicationService;
private final SlackAlertService slackAlertService; // From previous example
public ApplicationSyncManager(ArgoCDApplicationService applicationService,
SlackAlertService slackAlertService) {
this.applicationService = applicationService;
this.slackAlertService = slackAlertService;
}
public SyncResult syncApplicationWithRetry(String applicationName, int maxRetries) {
log.info("Starting sync with retry for application: {}", applicationName);
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
log.info("Sync attempt {}/{} for {}", attempt, maxRetries, applicationName);
// Trigger sync
SyncResponse syncResponse = applicationService.syncApplication(applicationName);
// Wait for sync completion
boolean syncSuccess = applicationService.waitForSync(applicationName, 
Duration.ofMinutes(10));
if (syncSuccess) {
log.info("Application {} synced successfully on attempt {}", 
applicationName, attempt);
sendSyncSuccessAlert(applicationName, attempt);
return SyncResult.success(applicationName, attempt);
} else {
log.warn("Application {} sync failed on attempt {}", applicationName, attempt);
if (attempt < maxRetries) {
// Wait before retry
Thread.sleep(Duration.ofSeconds(30).toMillis());
}
}
} catch (Exception e) {
log.error("Sync attempt {} failed for {}: {}", attempt, applicationName, e.getMessage());
if (attempt == maxRetries) {
sendSyncFailureAlert(applicationName, e.getMessage());
return SyncResult.failure(applicationName, e.getMessage());
}
try {
Thread.sleep(Duration.ofSeconds(30).toMillis());
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new ArgoCDException("Sync interrupted", ie);
}
}
}
return SyncResult.failure(applicationName, "Max retries exceeded");
}
public DeploymentResult deployApplication(DeploymentRequest request) {
log.info("Starting deployment for application: {}", request.getApplicationName());
try {
// Check if application exists
ArgoCDApplication existingApp = null;
try {
existingApp = applicationService.getApplication(request.getApplicationName());
} catch (ArgoCDException e) {
log.debug("Application does not exist, will create: {}", request.getApplicationName());
}
if (existingApp == null) {
// Create new application
CreateApplicationRequest createRequest = CreateApplicationRequest.builder()
.name(request.getApplicationName())
.repoUrl(request.getRepoUrl())
.path(request.getPath())
.targetRevision(request.getTargetRevision())
.destinationServer(request.getCluster())
.destinationNamespace(request.getNamespace())
.prune(true)
.selfHeal(true)
.build();
applicationService.createApplication(createRequest);
log.info("Created new application: {}", request.getApplicationName());
} else {
// Update existing application
UpdateApplicationRequest updateRequest = UpdateApplicationRequest.builder()
.targetRevision(request.getTargetRevision())
.path(request.getPath())
.build();
applicationService.updateApplication(request.getApplicationName(), updateRequest);
log.info("Updated existing application: {}", request.getApplicationName());
}
// Sync the application
SyncResult syncResult = syncApplicationWithRetry(request.getApplicationName(), 3);
return DeploymentResult.builder()
.applicationName(request.getApplicationName())
.success(syncResult.isSuccess())
.message(syncResult.getMessage())
.timestamp(Instant.now())
.build();
} catch (Exception e) {
log.error("Deployment failed for {}: {}", request.getApplicationName(), e.getMessage());
sendDeploymentFailureAlert(request.getApplicationName(), e.getMessage());
return DeploymentResult.builder()
.applicationName(request.getApplicationName())
.success(false)
.message("Deployment failed: " + e.getMessage())
.timestamp(Instant.now())
.build();
}
}
public RollbackResult rollbackApplication(String applicationName, String targetRevision) {
log.info("Rolling back application {} to revision: {}", applicationName, targetRevision);
try {
SyncResponse rollbackResponse = applicationService.rollbackApplication(
applicationName, targetRevision);
// Wait for rollback to complete
boolean rollbackSuccess = applicationService.waitForSync(applicationName, 
Duration.ofMinutes(5));
RollbackResult result = RollbackResult.builder()
.applicationName(applicationName)
.targetRevision(targetRevision)
.success(rollbackSuccess)
.timestamp(Instant.now())
.build();
if (rollbackSuccess) {
sendRollbackSuccessAlert(applicationName, targetRevision);
} else {
sendRollbackFailureAlert(applicationName, targetRevision);
}
return result;
} catch (Exception e) {
log.error("Rollback failed for {}: {}", applicationName, e.getMessage());
sendRollbackFailureAlert(applicationName, targetRevision);
return RollbackResult.builder()
.applicationName(applicationName)
.targetRevision(targetRevision)
.success(false)
.message("Rollback failed: " + e.getMessage())
.timestamp(Instant.now())
.build();
}
}
public ApplicationHealthStatus getApplicationHealth(String applicationName) {
try {
ArgoCDApplication application = applicationService.getApplication(applicationName);
ApplicationStatus status = application.getStatus();
if (status == null) {
return ApplicationHealthStatus.unknown(applicationName, "No status available");
}
String syncStatus = status.getSync() != null ? status.getSync().getStatus() : "Unknown";
String healthStatus = status.getHealth() != null ? status.getHealth().getStatus() : "Unknown";
return ApplicationHealthStatus.builder()
.applicationName(applicationName)
.syncStatus(syncStatus)
.healthStatus(healthStatus)
.syncMessage(status.getSync() != null ? status.getSync().getRevision() : "")
.healthMessage(status.getHealth() != null ? status.getHealth().getMessage() : "")
.observedAt(status.getObservedAt())
.build();
} catch (Exception e) {
log.error("Failed to get health status for {}: {}", applicationName, e.getMessage());
return ApplicationHealthStatus.unknown(applicationName, e.getMessage());
}
}
private void sendSyncSuccessAlert(String applicationName, int attempt) {
slackAlertService.sendSimpleMessage(
String.format(":white_check_mark: Application `%s` synced successfully (attempt %d)", 
applicationName, attempt));
}
private void sendSyncFailureAlert(String applicationName, String error) {
slackAlertService.sendRichAlert(AlertRequest.builder()
.level(AlertRequest.AlertLevel.ERROR)
.title("Sync Failed")
.message(String.format("Application `%s` sync failed", applicationName))
.details(Map.of("error", error))
.build());
}
private void sendDeploymentFailureAlert(String applicationName, String error) {
slackAlertService.sendRichAlert(AlertRequest.builder()
.level(AlertRequest.AlertLevel.ERROR)
.title("Deployment Failed")
.message(String.format("Deployment of `%s` failed", applicationName))
.details(Map.of("error", error))
.build());
}
private void sendRollbackSuccessAlert(String applicationName, String revision) {
slackAlertService.sendSimpleMessage(
String.format(":rewind: Application `%s` rolled back to revision `%s`", 
applicationName, revision));
}
private void sendRollbackFailureAlert(String applicationName, String revision) {
slackAlertService.sendRichAlert(AlertRequest.builder()
.level(AlertRequest.AlertLevel.ERROR)
.title("Rollback Failed")
.message(String.format("Rollback of `%s` to `%s` failed", applicationName, revision))
.build());
}
}
// Result DTOs
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class SyncResult {
private String applicationName;
private boolean success;
private String message;
private int attempts;
private Instant timestamp;
public static SyncResult success(String applicationName, int attempts) {
return SyncResult.builder()
.applicationName(applicationName)
.success(true)
.message("Sync completed successfully")
.attempts(attempts)
.timestamp(Instant.now())
.build();
}
public static SyncResult failure(String applicationName, String error) {
return SyncResult.builder()
.applicationName(applicationName)
.success(false)
.message(error)
.timestamp(Instant.now())
.build();
}
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class DeploymentResult {
private String applicationName;
private boolean success;
private String message;
private Instant timestamp;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class RollbackResult {
private String applicationName;
private String targetRevision;
private boolean success;
private String message;
private Instant timestamp;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class ApplicationHealthStatus {
private String applicationName;
private String syncStatus;
private String healthStatus;
private String syncMessage;
private String healthMessage;
private String observedAt;
private Instant checkedAt;
public static ApplicationHealthStatus unknown(String applicationName, String message) {
return ApplicationHealthStatus.builder()
.applicationName(applicationName)
.syncStatus("Unknown")
.healthStatus("Unknown")
.healthMessage(message)
.checkedAt(Instant.now())
.build();
}
public boolean isHealthy() {
return "Synced".equals(syncStatus) && "Healthy".equals(healthStatus);
}
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class DeploymentRequest {
private String applicationName;
private String repoUrl;
private String path;
private String targetRevision;
private String cluster;
private String namespace;
private Map<String, String> parameters;
}
6. REST Controller
@RestController
@RequestMapping("/api/argocd")
@Slf4j
public class ArgoCDController {
private final ApplicationSyncManager syncManager;
private final ArgoCDApplicationService applicationService;
public ArgoCDController(ApplicationSyncManager syncManager, 
ArgoCDApplicationService applicationService) {
this.syncManager = syncManager;
this.applicationService = applicationService;
}
@PostMapping("/applications")
public ResponseEntity<DeploymentResult> createApplication(@Valid @RequestBody DeploymentRequest request) {
log.info("Received deployment request for: {}", request.getApplicationName());
DeploymentResult result = syncManager.deployApplication(request);
if (result.isSuccess()) {
return ResponseEntity.ok(result);
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
}
@PostMapping("/applications/{name}/sync")
public ResponseEntity<SyncResult> syncApplication(@PathVariable String name) {
log.info("Received sync request for application: {}", name);
SyncResult result = syncManager.syncApplicationWithRetry(name, 3);
if (result.isSuccess()) {
return ResponseEntity.ok(result);
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
}
@PostMapping("/applications/{name}/rollback")
public ResponseEntity<RollbackResult> rollbackApplication(
@PathVariable String name, 
@RequestParam String revision) {
log.info("Received rollback request for application: {} to revision: {}", name, revision);
RollbackResult result = syncManager.rollbackApplication(name, revision);
if (result.isSuccess()) {
return ResponseEntity.ok(result);
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
}
}
@GetMapping("/applications/{name}/health")
public ResponseEntity<ApplicationHealthStatus> getApplicationHealth(@PathVariable String name) {
ApplicationHealthStatus health = syncManager.getApplicationHealth(name);
return ResponseEntity.ok(health);
}
@GetMapping("/applications")
public ResponseEntity<List<ArgoCDApplication>> listApplications() {
List<ArgoCDApplication> applications = applicationService.listApplications();
return ResponseEntity.ok(applications);
}
@GetMapping("/applications/{name}")
public ResponseEntity<ArgoCDApplication> getApplication(@PathVariable String name) {
ArgoCDApplication application = applicationService.getApplication(name);
return ResponseEntity.ok(application);
}
@DeleteMapping("/applications/{name}")
public ResponseEntity<Map<String, String>> deleteApplication(
@PathVariable String name,
@RequestParam(defaultValue = "true") boolean cascade) {
applicationService.deleteApplication(name, cascade);
return ResponseEntity.ok(Map.of(
"status", "success",
"message", "Application " + name + " deleted"
));
}
}
7. Scheduled Sync Monitor
@Component
@Slf4j
public class ScheduledSyncMonitor {
private final ApplicationSyncManager syncManager;
private final ArgoCDApplicationService applicationService;
private final SlackAlertService slackAlertService;
private final Set<String> monitoredApplications;
public ScheduledSyncMonitor(ApplicationSyncManager syncManager,
ArgoCDApplicationService applicationService,
SlackAlertService slackAlertService) {
this.syncManager = syncManager;
this.applicationService = applicationService;
this.slackAlertService = slackAlertService;
this.monitoredApplications = ConcurrentHashMap.newKeySet();
}
@Scheduled(fixedRate = 300000) // 5 minutes
public void monitorApplicationsHealth() {
if (monitoredApplications.isEmpty()) {
return;
}
log.debug("Monitoring health of {} applications", monitoredApplications.size());
for (String appName : monitoredApplications) {
try {
ApplicationHealthStatus health = syncManager.getApplicationHealth(appName);
if (!health.isHealthy()) {
log.warn("Application {} is unhealthy: Sync={}, Health={}", 
appName, health.getSyncStatus(), health.getHealthStatus());
sendHealthAlert(appName, health);
}
} catch (Exception e) {
log.error("Failed to monitor application {}: {}", appName, e.getMessage());
}
}
}
@Scheduled(cron = "0 0 6 * * ?") // Daily at 6 AM
public void performDailySync() {
log.info("Starting daily sync of monitored applications");
for (String appName : monitoredApplications) {
try {
SyncResult result = syncManager.syncApplicationWithRetry(appName, 2);
if (!result.isSuccess()) {
log.error("Daily sync failed for {}: {}", appName, result.getMessage());
}
} catch (Exception e) {
log.error("Daily sync failed for {}: {}", appName, e.getMessage());
}
}
}
public void addMonitoredApplication(String applicationName) {
monitoredApplications.add(applicationName);
log.info("Added application to monitoring: {}", applicationName);
}
public void removeMonitoredApplication(String applicationName) {
monitoredApplications.remove(applicationName);
log.info("Removed application from monitoring: {}", applicationName);
}
public Set<String> getMonitoredApplications() {
return Collections.unmodifiableSet(monitoredApplications);
}
private void sendHealthAlert(String appName, ApplicationHealthStatus health) {
slackAlertService.sendRichAlert(AlertRequest.builder()
.level(AlertRequest.AlertLevel.WARNING)
.title("Application Health Alert")
.message(String.format("Application `%s` is unhealthy", appName))
.details(Map.of(
"syncStatus", health.getSyncStatus(),
"healthStatus", health.getHealthStatus(),
"healthMessage", health.getHealthMessage(),
"observedAt", health.getObservedAt()
))
.build());
}
}

Configuration

# application.yml
spring:
application:
name: argocd-sync-service
argocd:
server:
url: ${ARGOCD_SERVER:https://argocd.example.com}
token: ${ARGOCD_TOKEN:}
insecure: false
timeout: 30000
defaults:
namespace: argocd
project: default
sync-policy:
automated:
prune: true
selfHeal: true
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
logging:
level:
com.example.argocd: DEBUG

Best Practices

  1. Error Handling: Implement robust error handling and retry mechanisms
  2. Security: Use secure token authentication and avoid storing credentials
  3. Monitoring: Track sync operations and application health
  4. Idempotency: Ensure operations can be safely retried
  5. Resource Cleanup: Properly manage resources and connections
  6. Alerting: Integrate with alerting systems for critical failures
@Component
public class ArgoCDHealthCheck implements HealthIndicator {
private final ArgoCDApplicationService applicationService;
public ArgoCDHealthCheck(ArgoCDApplicationService applicationService) {
this.applicationService = applicationService;
}
@Override
public Health health() {
try {
// Test connection by listing applications
applicationService.listApplications();
return Health.up().withDetail("server", "reachable").build();
} catch (Exception e) {
return Health.down()
.withDetail("server", "unreachable")
.withDetail("error", e.getMessage())
.build();
}
}
}

Conclusion

ArgoCD Application Sync in Java provides:

  • Programmatic control over Kubernetes deployments
  • GitOps automation with full lifecycle management
  • Robust error handling with retry mechanisms
  • Comprehensive monitoring and alerting
  • RESTful API for integration with other systems

This implementation enables teams to automate their deployment pipelines, ensure consistent application states, and quickly respond to deployment issues through programmatic control of ArgoCD.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper