ArgoCD Application Sync in Java: Complete Automation Guide

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


Setup and Dependencies

1. Maven Dependencies
<properties>
<kubernetes-client.version>6.7.2</kubernetes-client.version>
<argo-cd.version>2.7.0</argo-cd.version>
<jackson.version>2.15.2</jackson.version>
<okhttp.version>4.11.0</okhttp.version>
</properties>
<dependencies>
<!-- Argo CD Models -->
<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>jar</classifier>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- YAML Processing -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
2. Configuration Properties
@Configuration
@ConfigurationProperties(prefix = "argocd")
@Data
public class ArgoCDConfig {
private String serverUrl;
private String authToken;
private String username;
private String password;
private boolean insecure = false;
private long timeoutMs = 30000;
private String namespace = "argocd";
// Sync options
private boolean prune = true;
private boolean dryRun = false;
private String strategy = "apply";
private boolean force = false;
// Retry configuration
private int maxRetries = 3;
private long retryDelayMs = 5000;
}
# application.yml
argocd:
server-url: ${ARGOCD_SERVER:https://argocd.example.com}
auth-token: ${ARGOCD_TOKEN:}
username: ${ARGOCD_USERNAME:admin}
password: ${ARGOCD_PASSWORD:}
insecure: false
timeout-ms: 30000
namespace: argocd
prune: true
dry-run: false
strategy: apply
force: false
max-retries: 3
retry-delay-ms: 5000

Core ArgoCD Client

1. ArgoCD Client Factory
@Component
@Slf4j
public class ArgoCDClientFactory {
private final ArgoCDConfig config;
private OkHttpClient httpClient;
private ObjectMapper objectMapper;
public ArgoCDClientFactory(ArgoCDConfig config) {
this.config = config;
initializeHttpClient();
initializeObjectMapper();
}
private void initializeHttpClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(Duration.ofMillis(config.getTimeoutMs()))
.readTimeout(Duration.ofMillis(config.getTimeoutMs()))
.writeTimeout(Duration.ofMillis(config.getTimeoutMs()));
// Add authentication interceptor
builder.addInterceptor(new ArgoCDAuthInterceptor(config));
// Add logging interceptor
if (log.isDebugEnabled()) {
builder.addInterceptor(new HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY));
}
// Handle insecure SSL if needed
if (config.isInsecure()) {
builder.sslSocketFactory(InsecureSSLFactory.getSocketFactory(), 
InsecureSSLFactory.getTrustManager());
builder.hostnameVerifier((hostname, session) -> true);
}
this.httpClient = builder.build();
}
private void initializeObjectMapper() {
this.objectMapper = new ObjectMapper();
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.objectMapper.registerModule(new JavaTimeModule());
}
public ArgoCDRestClient createRestClient() {
return new ArgoCDRestClient(httpClient, objectMapper, config);
}
public ArgoCDKubernetesClient createKubernetesClient() throws IOException {
return new ArgoCDKubernetesClient(config);
}
@PreDestroy
public void close() {
if (httpClient != null) {
httpClient.dispatcher().executorService().shutdown();
httpClient.connectionPool().evictAll();
}
}
}
// Authentication Interceptor
@Slf4j
class ArgoCDAuthInterceptor implements Interceptor {
private final ArgoCDConfig config;
public ArgoCDAuthInterceptor(ArgoCDConfig config) {
this.config = config;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request.Builder builder = originalRequest.newBuilder();
// Prefer token authentication
if (config.getAuthToken() != null && !config.getAuthToken().trim().isEmpty()) {
builder.header("Authorization", "Bearer " + config.getAuthToken());
} 
// Fallback to basic auth
else if (config.getUsername() != null && config.getPassword() != null) {
String credentials = config.getUsername() + ":" + config.getPassword();
String basicAuth = "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes());
builder.header("Authorization", basicAuth);
}
builder.header("Content-Type", "application/json");
builder.header("Accept", "application/json");
return chain.proceed(builder.build());
}
}
2. REST Client Implementation
@Component
@Slf4j
public class ArgoCDRestClient {
private final OkHttpClient httpClient;
private final ObjectMapper objectMapper;
private final ArgoCDConfig config;
private final String baseUrl;
public ArgoCDRestClient(OkHttpClient httpClient, ObjectMapper objectMapper, ArgoCDConfig config) {
this.httpClient = httpClient;
this.objectMapper = objectMapper;
this.config = config;
this.baseUrl = config.getServerUrl() + "/api/v1";
}
// Application Management
public List<Application> getApplications() throws ArgoCDException {
return executeGet("/applications", new TypeReference<List<Application>>() {});
}
public Application getApplication(String name) throws ArgoCDException {
return executeGet("/applications/" + name, Application.class);
}
public Application createApplication(Application application) throws ArgoCDException {
String json = toJson(application);
return executePost("/applications", json, Application.class);
}
public Application updateApplication(String name, Application application) throws ArgoCDException {
String json = toJson(application);
return executePut("/applications/" + name, json, Application.class);
}
public void deleteApplication(String name) throws ArgoCDException {
executeDelete("/applications/" + name);
}
// Application Sync
public SyncOperationResult syncApplication(String applicationName, SyncOperation syncOperation) 
throws ArgoCDException {
String json = toJson(syncOperation);
return executePost("/applications/" + applicationName + "/sync", json, SyncOperationResult.class);
}
public Application watchApplication(String name, long timeoutSeconds) throws ArgoCDException {
String url = String.format("/applications/%s?watch=true&timeoutSeconds=%d", name, timeoutSeconds);
return executeGet(url, Application.class);
}
// Sync Status and Operations
public List<OperationState> getOperationHistory(String applicationName) throws ArgoCDException {
return executeGet("/applications/" + applicationName + "/operations", 
new TypeReference<List<OperationState>>() {});
}
public SyncOperationResult getSyncResult(String applicationName, String operationId) throws ArgoCDException {
return executeGet("/applications/" + applicationName + "/operations/" + operationId, 
SyncOperationResult.class);
}
// Resource Actions
public Object getResource(String applicationName, String namespace, 
String resourceName, String version, String kind) throws ArgoCDException {
String url = String.format("/applications/%s/resource?namespace=%s&name=%s&version=%s&kind=%s",
applicationName, namespace, resourceName, version, kind);
return executeGet(url, Object.class);
}
public Object patchResource(String applicationName, String namespace, 
String resourceName, String version, String kind, 
String patchType, Object patch) throws ArgoCDException {
String url = String.format("/applications/%s/resource?namespace=%s&name=%s&version=%s&kind=%s",
applicationName, namespace, resourceName, version, kind);
String jsonPatch = toJson(patch);
return executePost(url + "&patchType=" + patchType, jsonPatch, Object.class);
}
// Private HTTP methods
private <T> T executeGet(String path, Class<T> responseType) throws ArgoCDException {
return executeRequest(new Request.Builder().url(baseUrl + path).get(), responseType);
}
private <T> T executeGet(String path, TypeReference<T> typeReference) throws ArgoCDException {
return executeRequest(new Request.Builder().url(baseUrl + path).get(), typeReference);
}
private <T> T executePost(String path, String jsonBody, Class<T> responseType) throws ArgoCDException {
RequestBody body = RequestBody.create(jsonBody, MediaType.parse("application/json"));
return executeRequest(new Request.Builder().url(baseUrl + path).post(body), responseType);
}
private <T> T executePut(String path, String jsonBody, Class<T> responseType) throws ArgoCDException {
RequestBody body = RequestBody.create(jsonBody, MediaType.parse("application/json"));
return executeRequest(new Request.Builder().url(baseUrl + path).put(body), responseType);
}
private void executeDelete(String path) throws ArgoCDException {
executeRequest(new Request.Builder().url(baseUrl + path).delete(), Void.class);
}
private <T> T executeRequest(Request.Builder requestBuilder, Class<T> responseType) throws ArgoCDException {
try {
Response response = httpClient.newCall(requestBuilder.build()).execute();
return handleResponse(response, responseType);
} catch (IOException e) {
throw new ArgoCDException("HTTP request failed", e);
}
}
private <T> T executeRequest(Request.Builder requestBuilder, TypeReference<T> typeReference) throws ArgoCDException {
try {
Response response = httpClient.newCall(requestBuilder.build()).execute();
return handleResponse(response, typeReference);
} catch (IOException e) {
throw new ArgoCDException("HTTP request failed", e);
}
}
private <T> T handleResponse(Response response, Class<T> responseType) throws ArgoCDException {
try {
if (!response.isSuccessful()) {
throw new ArgoCDException("HTTP " + response.code() + ": " + response.message());
}
if (responseType == Void.class) {
return null;
}
String responseBody = response.body().string();
return objectMapper.readValue(responseBody, responseType);
} catch (IOException e) {
throw new ArgoCDException("Failed to parse response", e);
}
}
private <T> T handleResponse(Response response, TypeReference<T> typeReference) throws ArgoCDException {
try {
if (!response.isSuccessful()) {
throw new ArgoCDException("HTTP " + response.code() + ": " + response.message());
}
String responseBody = response.body().string();
return objectMapper.readValue(responseBody, typeReference);
} catch (IOException e) {
throw new ArgoCDException("Failed to parse response", e);
}
}
private String toJson(Object object) throws ArgoCDException {
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new ArgoCDException("Failed to serialize object to JSON", e);
}
}
}
public class ArgoCDException extends Exception {
public ArgoCDException(String message) {
super(message);
}
public ArgoCDException(String message, Throwable cause) {
super(message, cause);
}
}
3. Kubernetes Client Implementation
@Component
@Slf4j
public class ArgoCDKubernetesClient {
private final ApiClient apiClient;
private final CustomObjectsApi customObjectsApi;
private final String namespace;
public ArgoCDKubernetesClient(ArgoCDConfig config) throws IOException {
this.namespace = config.getNamespace();
// Load kubeconfig or use in-cluster config
if (config.getAuthToken() != null) {
this.apiClient = createApiClientFromToken(config);
} else {
this.apiClient = Config.defaultClient();
}
if (config.isInsecure()) {
apiClient.setVerifyingSsl(false);
}
this.customObjectsApi = new CustomObjectsApi(apiClient);
}
private ApiClient createApiClientFromToken(ArgoCDConfig config) {
ApiClient client = new ApiClient();
client.setBasePath(config.getServerUrl());
client.setApiKey("Bearer " + config.getAuthToken());
client.setConnectTimeout(config.getTimeoutMs());
client.setReadTimeout(config.getTimeoutMs());
client.setWriteTimeout(config.getTimeoutMs());
return client;
}
public Object getApplication(String name) throws ArgoCDException {
try {
return customObjectsApi.getNamespacedCustomObject(
"argoproj.io",
"v1alpha1",
namespace,
"applications",
name
);
} catch (ApiException e) {
throw new ArgoCDException("Failed to get application: " + name, e);
}
}
public Object createApplication(Object application) throws ArgoCDException {
try {
return customObjectsApi.createNamespacedCustomObject(
"argoproj.io",
"v1alpha1",
namespace,
"applications",
application,
null,
null,
null
);
} catch (ApiException e) {
throw new ArgoCDException("Failed to create application", e);
}
}
public Object patchApplication(String name, Object patch) throws ArgoCDException {
try {
return customObjectsApi.patchNamespacedCustomObject(
"argoproj.io",
"v1alpha1",
namespace,
"applications",
name,
patch,
null,
null,
null
);
} catch (ApiException e) {
throw new ArgoCDException("Failed to patch application: " + name, e);
}
}
public void deleteApplication(String name) throws ArgoCDException {
try {
customObjectsApi.deleteNamespacedCustomObject(
"argoproj.io",
"v1alpha1",
namespace,
"applications",
name,
null,
null,
null,
null,
null,
null
);
} catch (ApiException e) {
throw new ArgoCDException("Failed to delete application: " + name, e);
}
}
public List<Object> listApplications() throws ArgoCDException {
try {
Object response = customObjectsApi.listNamespacedCustomObject(
"argoproj.io",
"v1alpha1",
namespace,
"applications",
null,
null,
null,
null,
null,
null,
null,
null
);
return extractItems(response);
} catch (ApiException e) {
throw new ArgoCDException("Failed to list applications", e);
}
}
@SuppressWarnings("unchecked")
private List<Object> extractItems(Object response) {
if (response instanceof Map) {
Map<String, Object> responseMap = (Map<String, Object>) response;
Object items = responseMap.get("items");
if (items instanceof List) {
return (List<Object>) items;
}
}
return Collections.emptyList();
}
}

Application Sync Service

1. Core Sync Service
@Service
@Slf4j
public class ArgoCDSyncService {
private final ArgoCDRestClient restClient;
private final ArgoCDConfig config;
private final ObjectMapper objectMapper;
public ArgoCDSyncService(ArgoCDRestClient restClient, ArgoCDConfig config, ObjectMapper objectMapper) {
this.restClient = restClient;
this.config = config;
this.objectMapper = objectMapper;
}
public SyncOperationResult syncApplication(String applicationName) throws ArgoCDException {
return syncApplication(applicationName, createDefaultSyncOperation());
}
public SyncOperationResult syncApplication(String applicationName, SyncOperation syncOperation) 
throws ArgoCDException {
log.info("Initiating sync for application: {}", applicationName);
SyncOperationResult result = restClient.syncApplication(applicationName, syncOperation);
if (result != null && result.getStarted() != null) {
log.info("Sync started for application: {} with operation ID: {}", 
applicationName, result.getStarted().getOperation().getSync().getRevision());
// Wait for sync completion if not dry run
if (!config.isDryRun() && syncOperation.getDryRun() == null || !syncOperation.getDryRun()) {
return waitForSyncCompletion(applicationName, result);
}
}
return result;
}
public SyncOperationResult syncApplicationWithRetry(String applicationName, int maxRetries) 
throws ArgoCDException {
return syncApplicationWithRetry(applicationName, createDefaultSyncOperation(), maxRetries);
}
public SyncOperationResult syncApplicationWithRetry(String applicationName, 
SyncOperation syncOperation, 
int maxRetries) throws ArgoCDException {
ArgoCDException lastException = null;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
log.info("Sync attempt {}/{} for application: {}", attempt, maxRetries, applicationName);
return syncApplication(applicationName, syncOperation);
} catch (ArgoCDException e) {
lastException = e;
log.warn("Sync attempt {}/{} failed for application: {} - {}", 
attempt, maxRetries, applicationName, e.getMessage());
if (attempt < maxRetries) {
try {
Thread.sleep(config.getRetryDelayMs());
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new ArgoCDException("Sync interrupted", ie);
}
}
}
}
throw new ArgoCDException("All sync attempts failed for application: " + applicationName, lastException);
}
public SyncOperationResult syncAndWait(String applicationName, long timeoutMinutes) 
throws ArgoCDException {
SyncOperationResult result = syncApplication(applicationName);
return waitForSyncCompletion(applicationName, result, timeoutMinutes);
}
public boolean isApplicationSynced(String applicationName) throws ArgoCDException {
Application application = restClient.getApplication(applicationName);
return application.getStatus() != null && 
"Synced".equals(application.getStatus().getSync().getStatus());
}
public ApplicationHealth getApplicationHealth(String applicationName) throws ArgoCDException {
Application application = restClient.getApplication(applicationName);
return application.getStatus() != null ? application.getStatus().getHealth() : null;
}
public List<ResourceStatus> getOutOfSyncResources(String applicationName) throws ArgoCDException {
Application application = restClient.getApplication(applicationName);
if (application.getStatus() == null || application.getStatus().getResources() == null) {
return Collections.emptyList();
}
return application.getStatus().getResources().stream()
.filter(resource -> resource.getStatus() != null && 
"OutOfSync".equals(resource.getStatus()))
.collect(Collectors.toList());
}
// Private methods
private SyncOperation createDefaultSyncOperation() {
SyncOperation syncOperation = new SyncOperation();
syncOperation.setPrune(config.isPrune());
syncOperation.setDryRun(config.isDryRun());
syncOperation.setStrategy(createSyncStrategy());
List<SyncOperationResource> resources = new ArrayList<>();
syncOperation.setResources(resources);
return syncOperation;
}
private SyncStrategy createSyncStrategy() {
SyncStrategy strategy = new SyncStrategy();
if ("hook".equals(config.getStrategy())) {
SyncStrategyHook hook = new SyncStrategyHook();
hook.setForce(config.isForce());
strategy.setHook(hook);
} else {
SyncStrategyApply apply = new SyncStrategyApply();
apply.setForce(config.isForce());
strategy.setApply(apply);
}
return strategy;
}
private SyncOperationResult waitForSyncCompletion(String applicationName, SyncOperationResult initialResult) 
throws ArgoCDException {
return waitForSyncCompletion(applicationName, initialResult, 10); // Default 10 minutes timeout
}
private SyncOperationResult waitForSyncCompletion(String applicationName, 
SyncOperationResult initialResult,
long timeoutMinutes) throws ArgoCDException {
long startTime = System.currentTimeMillis();
long timeoutMs = timeoutMinutes * 60 * 1000;
log.info("Waiting for sync completion for application: {} (timeout: {} minutes)", 
applicationName, timeoutMinutes);
while (System.currentTimeMillis() - startTime < timeoutMs) {
try {
Application application = restClient.getApplication(applicationName);
ApplicationStatus status = application.getStatus();
if (status != null && status.getOperationState() != null) {
OperationState operationState = status.getOperationState();
String phase = operationState.getPhase();
log.debug("Sync phase for application {}: {}", applicationName, phase);
if ("Succeeded".equals(phase)) {
log.info("Sync completed successfully for application: {}", applicationName);
SyncOperationResult result = new SyncOperationResult();
result.setCompleted(operationState);
return result;
} else if ("Failed".equals(phase) || "Error".equals(phase)) {
log.error("Sync failed for application: {} - {}", 
applicationName, operationState.getMessage());
throw new ArgoCDException("Sync failed: " + operationState.getMessage());
}
// Continue waiting for "Running" phase
}
// Wait before next check
Thread.sleep(5000); // 5 seconds
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ArgoCDException("Sync wait interrupted", e);
}
}
throw new ArgoCDException("Sync timeout after " + timeoutMinutes + " minutes for application: " + applicationName);
}
}
2. Application Management Service
@Service
@Slf4j
public class ArgoCDApplicationService {
private final ArgoCDRestClient restClient;
private final ArgoCDKubernetesClient k8sClient;
public ArgoCDApplicationService(ArgoCDRestClient restClient, ArgoCDKubernetesClient k8sClient) {
this.restClient = restClient;
this.k8sClient = k8sClient;
}
public Application createApplicationFromManifest(String name, String namespace, 
String repoUrl, String path, 
String targetRevision) throws ArgoCDException {
Application application = new Application();
application.setMetadata(createMetadata(name, namespace));
application.setSpec(createApplicationSpec(repoUrl, path, targetRevision, namespace));
return restClient.createApplication(application);
}
public Application createApplicationFromYaml(String yamlContent) throws ArgoCDException {
try {
ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
Application application = yamlMapper.readValue(yamlContent, Application.class);
return restClient.createApplication(application);
} catch (IOException e) {
throw new ArgoCDException("Failed to parse YAML application manifest", e);
}
}
public List<Application> getApplicationsByProject(String project) throws ArgoCDException {
List<Application> allApplications = restClient.getApplications();
return allApplications.stream()
.filter(app -> app.getSpec() != null && 
project.equals(app.getSpec().getProject()))
.collect(Collectors.toList());
}
public List<Application> getOutOfSyncApplications() throws ArgoCDException {
List<Application> allApplications = restClient.getApplications();
return allApplications.stream()
.filter(app -> app.getStatus() != null && 
"OutOfSync".equals(app.getStatus().getSync().getStatus()))
.collect(Collectors.toList());
}
public List<Application> getUnhealthyApplications() throws ArgoCDException {
List<Application> allApplications = restClient.getApplications();
return allApplications.stream()
.filter(app -> app.getStatus() != null && 
app.getStatus().getHealth() != null &&
!"Healthy".equals(app.getStatus().getHealth().getStatus()))
.collect(Collectors.toList());
}
public void refreshApplication(String applicationName) throws ArgoCDException {
// Refresh by doing a no-op sync
SyncOperation syncOperation = new SyncOperation();
syncOperation.setDryRun(true);
syncOperation.setPrune(false);
restClient.syncApplication(applicationName, syncOperation);
log.info("Application refreshed: {}", applicationName);
}
public Application updateApplicationTargetRevision(String applicationName, String newRevision) 
throws ArgoCDException {
Application application = restClient.getApplication(applicationName);
if (application.getSpec() != null && application.getSpec().getSource() != null) {
application.getSpec().getSource().setTargetRevision(newRevision);
return restClient.updateApplication(applicationName, application);
}
throw new ArgoCDException("Cannot update target revision for application: " + applicationName);
}
// Helper methods
private ObjectMeta createMetadata(String name, String namespace) {
ObjectMeta metadata = new ObjectMeta();
metadata.setName(name);
metadata.setNamespace(namespace);
return metadata;
}
private ApplicationSpec createApplicationSpec(String repoUrl, String path, 
String targetRevision, String destinationNamespace) {
ApplicationSpec spec = new ApplicationSpec();
// Source
ApplicationSource source = new ApplicationSource();
source.setRepoURL(repoUrl);
source.setPath(path);
source.setTargetRevision(targetRevision);
spec.setSource(source);
// Destination
ApplicationDestination destination = new ApplicationDestination();
destination.setServer("https://kubernetes.default.svc");
destination.setNamespace(destinationNamespace);
spec.setDestination(destination);
// Project
spec.setProject("default");
// Sync policy
SyncPolicy syncPolicy = new SyncPolicy();
SyncPolicyAutomated automated = new SyncPolicyAutomated();
automated.setPrune(true);
automated.setSelfHeal(true);
syncPolicy.setAutomated(automated);
spec.setSyncPolicy(syncPolicy);
return spec;
}
}

Domain Models

1. ArgoCD Application Models
// Simplified ArgoCD Application model
@Data
public class Application {
private String apiVersion = "argoproj.io/v1alpha1";
private String kind = "Application";
private ObjectMeta metadata;
private ApplicationSpec spec;
private ApplicationStatus status;
}
@Data
public class ObjectMeta {
private String name;
private String namespace;
private Map<String, String> labels;
private Map<String, String> annotations;
}
@Data
public class ApplicationSpec {
private ApplicationSource source;
private ApplicationDestination destination;
private String project;
private SyncPolicy syncPolicy;
}
@Data
public class ApplicationSource {
private String repoURL;
private String path;
private String targetRevision;
private String chart;
private String valueFiles;
}
@Data
public class ApplicationDestination {
private String server;
private String namespace;
}
@Data
public class SyncPolicy {
private SyncPolicyAutomated automated;
private SyncPolicyRetry retry;
}
@Data
public class SyncPolicyAutomated {
private boolean prune;
private boolean selfHeal;
}
@Data
public class ApplicationStatus {
private List<ResourceStatus> resources;
private SyncStatus sync;
private ApplicationHealth health;
private OperationState operationState;
private String history;
}
@Data
public class ResourceStatus {
private String name;
private String kind;
private String namespace;
private String status;
private String health;
}
@Data
public class SyncStatus {
private String status;
private String revision;
}
@Data
public class ApplicationHealth {
private String status;
private String message;
}
@Data
public class OperationState {
private String phase;
private String message;
private Operation operation;
private SyncOperationResult syncResult;
}
@Data
public class SyncOperation {
private String revision;
private Boolean prune;
private Boolean dryRun;
private SyncStrategy strategy;
private List<SyncOperationResource> resources;
private List<String> syncOptions;
}
@Data
public class SyncOperationResult {
private Operation started;
private OperationState completed;
}
@Data
public class Operation {
private SyncOperation sync;
}
// Additional models as needed...

REST Controllers

1. Sync Controller
@RestController
@RequestMapping("/api/argocd")
@Slf4j
public class ArgoCDSyncController {
private final ArgoCDSyncService syncService;
private final ArgoCDApplicationService appService;
public ArgoCDSyncController(ArgoCDSyncService syncService, ArgoCDApplicationService appService) {
this.syncService = syncService;
this.appService = appService;
}
@PostMapping("/applications/{name}/sync")
public ResponseEntity<SyncResponse> syncApplication(@PathVariable String name) {
try {
SyncOperationResult result = syncService.syncApplication(name);
return ResponseEntity.ok(new SyncResponse("Sync initiated", result));
} catch (ArgoCDException e) {
log.error("Failed to sync application: {}", name, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new SyncResponse("Sync failed: " + e.getMessage(), null));
}
}
@PostMapping("/applications/{name}/sync-and-wait")
public ResponseEntity<SyncResponse> syncAndWait(@PathVariable String name,
@RequestParam(defaultValue = "10") long timeoutMinutes) {
try {
SyncOperationResult result = syncService.syncAndWait(name, timeoutMinutes);
return ResponseEntity.ok(new SyncResponse("Sync completed", result));
} catch (ArgoCDException e) {
log.error("Failed to sync application: {}", name, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new SyncResponse("Sync failed: " + e.getMessage(), null));
}
}
@GetMapping("/applications/{name}/sync-status")
public ResponseEntity<SyncStatusResponse> getSyncStatus(@PathVariable String name) {
try {
boolean isSynced = syncService.isApplicationSynced(name);
ApplicationHealth health = syncService.getApplicationHealth(name);
List<ResourceStatus> outOfSyncResources = syncService.getOutOfSyncResources(name);
SyncStatusResponse response = new SyncStatusResponse(isSynced, health, outOfSyncResources);
return ResponseEntity.ok(response);
} catch (ArgoCDException e) {
log.error("Failed to get sync status for application: {}", name, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/applications/out-of-sync")
public ResponseEntity<List<Application>> getOutOfSyncApplications() {
try {
List<Application> applications = appService.getOutOfSyncApplications();
return ResponseEntity.ok(applications);
} catch (ArgoCDException e) {
log.error("Failed to get out-of-sync applications", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
// Response DTOs
@Data
@AllArgsConstructor
public static class SyncResponse {
private String message;
private SyncOperationResult result;
}
@Data
@AllArgsConstructor
public static class SyncStatusResponse {
private boolean synced;
private ApplicationHealth health;
private List<ResourceStatus> outOfSyncResources;
}
}
2. Application Management Controller
@RestController
@RequestMapping("/api/argocd/applications")
@Slf4j
public class ApplicationController {
private final ArgoCDApplicationService appService;
private final ArgoCDRestClient restClient;
public ApplicationController(ArgoCDApplicationService appService, ArgoCDRestClient restClient) {
this.appService = appService;
this.restClient = restClient;
}
@PostMapping
public ResponseEntity<Application> createApplication(@RequestBody CreateApplicationRequest request) {
try {
Application application = appService.createApplicationFromManifest(
request.getName(),
request.getNamespace(),
request.getRepoUrl(),
request.getPath(),
request.getTargetRevision()
);
return ResponseEntity.status(HttpStatus.CREATED).body(application);
} catch (ArgoCDException e) {
log.error("Failed to create application", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PostMapping("/yaml")
public ResponseEntity<Application> createApplicationFromYaml(@RequestBody String yamlContent) {
try {
Application application = appService.createApplicationFromYaml(yamlContent);
return ResponseEntity.status(HttpStatus.CREATED).body(application);
} catch (ArgoCDException e) {
log.error("Failed to create application from YAML", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping
public ResponseEntity<List<Application>> getApplications() {
try {
List<Application> applications = restClient.getApplications();
return ResponseEntity.ok(applications);
} catch (ArgoCDException e) {
log.error("Failed to get applications", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@GetMapping("/{name}")
public ResponseEntity<Application> getApplication(@PathVariable String name) {
try {
Application application = restClient.getApplication(name);
return ResponseEntity.ok(application);
} catch (ArgoCDException e) {
log.error("Failed to get application: {}", name, e);
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
}
@DeleteMapping("/{name}")
public ResponseEntity<Void> deleteApplication(@PathVariable String name) {
try {
restClient.deleteApplication(name);
return ResponseEntity.noContent().build();
} catch (ArgoCDException e) {
log.error("Failed to delete application: {}", name, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PutMapping("/{name}/revision")
public ResponseEntity<Application> updateTargetRevision(@PathVariable String name,
@RequestParam String revision) {
try {
Application application = appService.updateApplicationTargetRevision(name, revision);
return ResponseEntity.ok(application);
} catch (ArgoCDException e) {
log.error("Failed to update target revision for application: {}", name, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PostMapping("/{name}/refresh")
public ResponseEntity<Void> refreshApplication(@PathVariable String name) {
try {
appService.refreshApplication(name);
return ResponseEntity.ok().build();
} catch (ArgoCDException e) {
log.error("Failed to refresh application: {}", name, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
// Request DTO
@Data
public static class CreateApplicationRequest {
@NotBlank
private String name;
@NotBlank
private String namespace;
@NotBlank
private String repoUrl;
@NotBlank
private String path;
private String targetRevision = "HEAD";
}
}

Scheduled Sync Service

@Service
@Slf4j
public class ScheduledSyncService {
private final ArgoCDSyncService syncService;
private final ArgoCDApplicationService appService;
public ScheduledSyncService(ArgoCDSyncService syncService, ArgoCDApplicationService appService) {
this.syncService = syncService;
this.appService = appService;
}
@Scheduled(cron = "${argocd.sync.cron:0 0 * * * *}") // Every hour by default
public void syncOutOfSyncApplications() {
log.info("Starting scheduled sync of out-of-sync applications");
try {
List<Application> outOfSyncApps = appService.getOutOfSyncApplications();
log.info("Found {} out-of-sync applications", outOfSyncApps.size());
for (Application app : outOfSyncApps) {
try {
String appName = app.getMetadata().getName();
log.info("Syncing out-of-sync application: {}", appName);
syncService.syncApplication(appName);
} catch (ArgoCDException e) {
log.error("Failed to sync application: {}", app.getMetadata().getName(), e);
}
}
} catch (ArgoCDException e) {
log.error("Failed to get out-of-sync applications for scheduled sync", e);
}
}
@Scheduled(cron = "${argocd.health-check.cron:0 */5 * * * *}") // Every 5 minutes
public void checkApplicationHealth() {
log.debug("Checking application health");
try {
List<Application> unhealthyApps = appService.getUnhealthyApplications();
if (!unhealthyApps.isEmpty()) {
log.warn("Found {} unhealthy applications: {}", 
unhealthyApps.size(),
unhealthyApps.stream()
.map(app -> app.getMetadata().getName())
.collect(Collectors.joining(", ")));
// Could trigger alerts or auto-healing here
}
} catch (ArgoCDException e) {
log.error("Failed to check application health", e);
}
}
}

Configuration

@Configuration
@EnableScheduling
@EnableConfigurationProperties(ArgoCDConfig.class)
public class ArgoCDAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ArgoCDClientFactory argoCDClientFactory(ArgoCDConfig config) {
return new ArgoCDClientFactory(config);
}
@Bean
@ConditionalOnMissingBean
public ArgoCDRestClient argoCDRestClient(ArgoCDClientFactory factory) {
return factory.createRestClient();
}
@Bean
@ConditionalOnMissingBean
public ArgoCDKubernetesClient argoCDKubernetesClient(ArgoCDClientFactory factory) throws IOException {
return factory.createKubernetesClient();
}
@Bean
@ConditionalOnMissingBean
public ArgoCDSyncService argoCDSyncService(ArgoCDRestClient restClient, ArgoCDConfig config) {
return new ArgoCDSyncService(restClient, config, new ObjectMapper());
}
@Bean
@ConditionalOnMissingBean
public ArgoCDApplicationService argoCDApplicationService(ArgoCDRestClient restClient, 
ArgoCDKubernetesClient k8sClient) {
return new ArgoCDApplicationService(restClient, k8sClient);
}
}

Best Practices

  1. Error Handling: Always handle ArgoCD exceptions gracefully
  2. Retry Logic: Implement retry for transient failures
  3. Timeouts: Set appropriate timeouts for sync operations
  4. Security: Use secure authentication methods (tokens over basic auth)
  5. Monitoring: Track sync operations and application health
  6. Resource Cleanup: Close HTTP clients properly
  7. Idempotency: Design sync operations to be idempotent

This comprehensive ArgoCD Java integration provides a robust foundation for automating application deployments, managing sync operations, and monitoring application health in your GitOps workflows.

Leave a Reply

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


Macro Nepal Helper