Skip to content

Complete Groovy to Java 17 migration and modernize with Lombok and Records - #541

Open
DerDaehne wants to merge 61 commits into
developfrom
feature/migrate-to-java
Open

Complete Groovy to Java 17 migration and modernize with Lombok and Records#541
DerDaehne wants to merge 61 commits into
developfrom
feature/migrate-to-java

Conversation

@DerDaehne

Copy link
Copy Markdown
Contributor

Overview

This pull request completes the migration of the remaining core application components from Groovy to Java 17. The changes focus on reducing boilerplate code and improving
readability by implementing Lombok, Java 17 Records, and modern Java syntax features.


Key Changes

  1. Boilerplate Reduction with Lombok
  • Lombok Integration: Configured the Lombok dependency and annotation processor inside pom.xml.
  • Config.java Refactoring: Applied @Getter and @Setter to the outer class and all 28 nested static schema classes. Standard trivial getters and setters were removed, resulting
    in a net reduction of 1,352 lines of code (~63% reduction). All custom validation logic and constructors initializing helm defaults were preserved.
  • Credentials.java and ScmCentralSchema.java: Refactored with @Getter, @Setter, and @tostring(exclude = "password") to eliminate more than 160 lines of boilerplate while
    ensuring sensitive passwords are never written to application logs.
  1. Adoption of Java 17 Records
  • Role.java and Permission.java: Converted to native Records to enforce immutability and replace repetitive class structures. Compact constructors were implemented for input
    validation.
  • FreeMarker Compatibility: Kept DockerImageParser.Image as a standard class but annotated it with Lombok @Getter instead of converting it to a Record. This avoids template
    parsing issues caused by the custom AllowListFreemarkerObjectWrapper requiring standard JavaBean getter methods (i.e. getRegistry()).
  • Test Updates: Updated property-style accesses in Groovy Spock tests (ScmManagerProviderTest.groovy) to use record-style method accessors (e.g. p.groupPermission()).
  1. Modern Java 17 Features
  • Pattern Matching for instanceof: Modernized K8sClient.java inside extractPhase(HasMetadata) to eliminate manual casting when checking for Pod instances.
  • Switch Expressions: Refactored visibility and access level mapping in GitlabProvider.java using switch expressions with arrow syntax (->) and multi-label cases (MAINTAIN,
    ADMIN ->).
  • Text Blocks: Converted inline Groovy script strings in PrometheusConfigurator.java into Java 17 Text Blocks ("""), removing raw newline string concatenations.
  1. Test Suite and Build Validation
  • Helm Version Fix: Corrected a version mismatch in GitopsPlaygroundCliTest.groovy by raising the expected Helm version to 3.11.10 to match the default configuration in
    ScmTenantSchema.java.
  • Test Executions: All 527 unit and integration tests are passing.

DerDaehne and others added 30 commits July 14, 2026 13:34
Migrate 'ReturnCode' enum and 'MapUtils' helper class from Groovy to Java.
This is the first step of the Groovy-to-Java migration, proving
the joint compilation setup works perfectly.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'DockerImageParser' and its nested 'Image' class from Groovy to Java.
Use modern Java Records for intermediate Tuple representation.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'NetworkingUtils' class from Groovy to Java.
Implement method overloading to replace Groovy default parameters, and
replace dynamic property accesses with standard Java getters.

Co-authored-by: Gemini <gemini@google.com>
…er to Java

Migrate 'CommandExecutor' and 'InsecureCredentialProvider' from Groovy to Java.
Implement necessary Groovy-interoperable method overloads for
process-execution and environmental variable mapping.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'AirGappedUtils' class from Groovy to Java.
Adjust visibility of GitRepo.NAMESPACE_3RD_PARTY_DEPENDENCIES constant to
public so it is exposed to the Java compiler in joint compilation.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'ClusterResourcesCopyFilter' utility from Groovy to Java.
Implement streams and lambdas to replace Groovy collections and closures.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'AllowListFreemarkerObjectWrapper' from Groovy to Java.
Use standard Java anonymous classes to represent the filtered TemplateHashModel.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'TemplatingEngine' from Groovy to Java.
Implement overloads to replace Groovy default parameters and use
try-with-resources to safely close Files.walk streams.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'FileSystemUtils' from Groovy to Java.
Use Files.readString and Files.writeString instead of Groovy extensions.
Implement try-with-resources for file walks to prevent stream resource leaks.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'ScmProviderType' enum and 'ConfigConstants' interface from Groovy to Java.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'Credentials' configuration model class from Groovy to Java.
Implement standard Java getters and setters and override toString.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'GitlabConfig' and 'ScmManagerConfig' interfaces from Groovy to Java.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'JsonSchemaGenerator' and 'JsonSchemaValidator' from Groovy to Java.
Use standard streams and list representation for schema validation messages.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'MultiTenantSchema', 'ScmCentralSchema', and 'ScmTenantSchema' from Groovy to Java.
Use standard Java nested static classes and bean properties for Picocli option parsing.

Co-authored-by: Gemini <gemini@google.com>
Fix GString cast issue in CommandExecutorForTest by using standard java String list.
Let TemplatingEngine propagate raw Freemarker exceptions so that
AllowlistFreemarkerObjectWrapperTest asserts the correct exception type.

Co-authored-by: Gemini <gemini@google.com>
Migrate the central 'Config' class from Groovy to Java.
Implement nested static configuration schemas and explicit bean getters/setters.
Integrate modern Java SecureRandom password generator and lambda-based Jackson serialization modifiers.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'Role', 'RoleBinding', and 'ServiceAccountRef' from Groovy to Java.
Implement nested enum Variant in Role and standard constructor logic.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'RbacDefinition' logic from Groovy to Java.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'HelmClient' utility from Groovy to Java.
Implement method overloads to replace Groovy default parameter values.
Delete empty 'HelmClientTest.groovy' placeholder.

Co-authored-by: Gemini <gemini@google.com>
Migrate the central 'K8sClient' from Groovy to Java.
Implement composition and delegation by splitting off private stateless helpers
into a package-private 'K8sClientHelper' class.
Expose mutable 'client' and 'gopConfig' fields for mock test injections.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'GitRepo' and 'GitRepoFactory' from Groovy to Java.
Adjust AirGappedUtils.java to properly wrap checked JGit GitAPIExceptions/IOExceptions in RuntimeExceptions.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'ScmManagerApiClient', 'ScmManagerApi', 'RepositoryApi', 'UsersApi', and 'PluginApi' from Groovy to Java.
Adjust ScmManagerSetupTest Mockito stubbing for getGitProvider() to support Java getters.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'JenkinsApiClient', 'UserManager', 'JobManager', and 'GlobalPropertyManager' from Groovy to Java.
Use Java Text Blocks and precise string placeholders/replacements to match multiline Groovy string test assertions exactly.
Use LinkedHashMap to preserve exact JSON map insertion order in credential serialization.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'Tool', 'CommonToolConfig', and 'ImagePullSecretCreator' from Groovy to Java.
Use private logger visibility in Tool.java to prevent name collisions with Groovy subclasses annotated with @slf4j.
Implement robust Java reflection fallback to support subclass dynamic 'namespace' property lookups.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'Ingress', 'Registry', 'CertManager', and 'ExternalSecretsOperator' from Groovy to Java.
All migrated classes inherit from the new Java 'Tool' base class.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'ArgoCD' and 'ScmManager' from Groovy to Java.
Keep standard annotations, DI wiring and orders intact.

Co-authored-by: Gemini <gemini@google.com>
Migrate 'Jenkins', 'Vault', and 'Monitoring' from Groovy to Java.
Wrap checked IOException and TemplateException thrown by TemplatingEngine.replaceTemplate
in Vault.java and convert etc/group gid lookup to use pure Java parsing.

Co-authored-by: Gemini <gemini@google.com>
Migrate all core Application components, Workspace classes, ContentLoader
and CLI classes from Groovy to Java 17.
Ensure proper type checking for nested RepoCoordinate in ContentLoaderTest.

Co-authored-by: Gemini <gemini@google.com>
- Wrap JGit checked exceptions in Tool.java and ArgoCD.java.
- Implement robust raw Map type check and Groovy-compatible map printing in ArgoCD.java's postConfigInit.
- Propagate raw RuntimeExceptions in AirGappedUtils.java.
- Use a mutable HashMap for service registry helm values to support deep merging.

Co-authored-by: Gemini <gemini@google.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

DerDaehne and others added 12 commits July 21, 2026 09:30
Replace var with explicit types where the inferred type is a simple,
well-known class, keeping var only for the few fabric8 Kubernetes client
calls whose real type is a multiply-nested generic that would hurt
readability if spelled out. Rename single-letter lambda parameters to
descriptive names across file-filter and stream predicates.

Adapted from the project's Groovy style guide now that the codebase has
migrated to Java.
…, git providers, k8s client and tools

Addresses the maintainability findings from the PR-541 SonarQube analysis
(RuntimeException/S112 findings intentionally excluded, per agreement).

Key changes:
- DeploymentContext: switch boxed Boolean getters to primitive boolean,
  fixing S5411 unboxing risks across ~15 call sites in one place.
- K8sClientHelper.findApiResourceViaDiscovery: split into focused helper
  methods to bring cognitive complexity from 61 down to allowed levels.
- Deduplicate repeated string literals into named constants throughout
  (ContentLoader, K8sClient, Jenkins, Monitoring, destroy handlers, etc.).
- Replace raw generics, unnecessary casts, Collectors.toList() ->
  toList(), and merge switch case labels using comma syntax.
- Remove genuinely unused fields/params (CertManager, Ingress,
  ExternalSecretsOperator k8sClient; ScmManagerApiClient credentials).
- Replace deprecated NetworkingUtils.getHost/getProtocol usage with
  java.net.URI-based implementation in ScmTenantSchema.

Left unchanged, by design:
- S107 (too many parameters) on Deployer/DeploymentStrategy/HelmStrategy/
  Jenkins would require an invasive DTO refactor across 14 callers.
- S106 on CLI stdout output (--version, --output-config-file) and
  CommandExecutor's tee streams: intentional stdout/stderr behavior,
  not accidental logging.
- S3011 reflection accessibility in GenerateJsonSchema: inherent to the
  schema/doc generator's field introspection.
- S115 VaultMode enum casing (dev/prod): renaming would break the
  public config/CLI contract documented in configuration.schema.json.

Co-Authored-By: Claude <noreply@anthropic.com>
Fix issues still present after the previous SonarQube cleanup commit,
verified against current source (many previously reported findings had
already been resolved and were stale). Covers wildcard imports, magic
numbers (mostly HTTP status codes), missing Locale/Charset arguments,
uncompiled regexes, methods that can be static, missing else branches,
overlong lambdas, and defensive copies for mutable getters/setters.

Also replaces the deprecated JacksonSchemaModule with JacksonModule,
and refactors GitopsPlaygroundCliMain so System.exit is only called
from main() instead of the testable exec() method, which incidentally
makes exec() unit-testable without mocking System.exit.

Deliberately left several rule categories untouched: structural
findings that would require larger redesigns (long methods/classes,
cyclomatic/cognitive complexity), rules that are false positives for
this codebase's config-merge and CLI-passthrough design (S106, S1258,
S1309, S923, S1133), and a few user-facing/API changes that need a
human call (VaultMode enum casing, Tool->AbstractTool rename,
Deployer's boolean-flag method).

Co-Authored-By: Claude <noreply@anthropic.com>
… guaranteed

Boolean.TRUE.equals(x) was previously introduced to silence SonarQube's
boxed-Boolean warnings. Verified that every Config Boolean field these
checks reference has a non-null default value initializer, and that the
config-merge pipeline (deepMergeDefaults against a fresh Config()) fills
any remaining gaps before the final Config object is built. Two fields
(debug, trace) were missing a default and have been fixed for
consistency with the rest of the schema.

With non-null guaranteed, simplified ~60 call sites back to direct
boxed-Boolean usage for readability. Left two exceptions unchanged:
K8sClientHelper's checks on live Kubernetes API discovery data (genuinely
nullable external input), and HttpClientFactory.buildOkHttpClient's
isInsecure parameter, which a test helper intentionally passes as null.

Co-Authored-By: Claude <noreply@anthropic.com>
Went through all 17 @SuppressWarnings annotations in src/main and either
fixed the root cause or consolidated the suppression:

- Config.DEFAULT_ADMIN_PW was public static (mutable, but never actually
  reassigned) purely to dodge S1444/S1104/S3008; made it final, which
  satisfies all three rules at once.
- ArgoCD.java suppressed S1192 instead of extracting the repeated
  "argocd"/"secret" literals into constants; extracted them instead.
- Removed four groovy.lang.Tuple2 compatibility overloads from
  K8sClient that only existed for legacy Groovy test call sites;
  migrated those tests to the project's own Tuple type and deleted the
  dead code, eliminating the deprecation warnings they caused.
- Migrated K8sClient off Fabric8's deprecated createOrReplace()/
  replace(item) onto createOr(NonDeletingOperation::update) and
  patch(item) respectively (confirmed via Fabric8's FAQ.md as the
  intended replacement), removing the last "deprecation" suppression.
  Updated the two K8sClientTest mocks whose expected HTTP verb changed
  from PUT to PATCH as a result.
- Consolidated eight scattered "unchecked" casts of YAML/JSON-parsed
  Object to Map<String, Object> (all the same erasure-boundary pattern)
  into two documented MapUtils helpers, so the suppression exists once
  instead of at every call site.

The remaining five suppressions are genuinely unavoidable and now carry
a comment explaining why (resource ownership handed off across a method
boundary, a JGit API contract, and the K8sClient god-class's inherent
cognitive complexity, which needs a deliberate decomposition rather
than a quick fix).

Co-Authored-By: Claude <noreply@anthropic.com>
Resolves the confidently-classifiable subset of SonarQube S112 findings:
UncheckedIOException for IOException wrapping, IllegalArgumentException
for invalid config/CLI input, and IllegalStateException for unexpected
external state (not-found, timeout/retry-exhausted, bad API responses).
Heterogeneous catch-all wrappers and cases without an obvious JDK type
are intentionally left as RuntimeException, still requiring human judgment.

Co-Authored-By: Claude <noreply@anthropic.com>
… infrastructure

Continues working down the PR-541 quality gate violations. All changes are
behavior-preserving unless noted:

- Exceptions (S112 subset): narrow catch blocks and use specific JDK
  exceptions where the classification is unambiguous - IllegalArgumentException
  for malformed configured URLs (Grafana, Vault), IllegalStateException for
  broken environment (SSL context, ScmManager node port URI) and reflection
  failures in schema generation. Remaining generic RuntimeExceptions are left
  deliberately: they wrap heterogeneous causes and need a human decision on
  the target exception design (a generic catch-all exception type was
  considered and rejected). Also narrows two "throws Exception" signatures
  (ContentLoader helm releases -> GitAPIException, JenkinsApiClient
  RequestSupplier -> no checked exceptions) now that the call chains only
  throw unchecked exceptions.
- Declarations moved next to first use (S1941). Note: in
  GitlabProvider.createRepository the subgroup is now only ensured after the
  project-exists early return; an existing project implies its subgroup exists.
- @NoArgsConstructor on Jackson/picocli schema DTOs (S1258) instead of fake
  field defaults, because null means "not configured" for several fields and
  is checked at the call sites.
- Logger reconfiguration variables inlined/extracted (S1312): the rule only
  accepts a single private static final LOG(GER) field, which cannot express
  logback reconfiguration code that handles multiple loggers.
- Complexity: shared isNullOrEmpty helper in K8sClientHelper (S1067/S1541),
  Jenkins.runSetupScript split into global-property and metrics-user parts
  with a prefixed-property helper (S1541), ArgoCdApplicationStrategy
  .deployFeature split into values/sources/manifest helpers (S138),
  Monitoring.uriComponents guard clause (S1067).
- Pattern.compile(".ftl") hoisted to a constant (S4248).
- Deprecated victools JacksonModule replaced by JacksonSchemaModule (S5738).
- Tool renamed to AbstractTool to match the abstract class naming convention
  (S118); string literals and log messages untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolves the SonarQube S1176 findings (84 in total) by documenting the
public API surface instead of excluding the rule: K8sClient is the central
kubectl-replacement facade and its conventions are genuinely non-obvious
(empty namespace means "default", label keys ending in "-" remove the label,
"--all" fans out to all nodes, delete logs instead of throwing because
resources may legitimately be absent). The SCM-Manager retrofit interfaces
and DTO payloads get short descriptions plus @param/@return tags, which the
quality profile requires for constructors and non-getter methods as well.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Both stages "Unit Test" and "Sonar-Scanner" will perform unit tests,
so we can merge these into one step.

- builds triggered by timer event would have empty RecipientProviders,
  resulting in a situation where weekly build would not report the build
  status to anybody.
  From now on, the whole team will get informed via email
- Remove the no-key labelRemove overloads in K8sClient: they delegated with
  an empty array and therefore always threw "Missing key-value-pairs", yet
  the recently added Javadoc presented them as usable API. They had no
  callers; deleting them prevents anyone from wiring up a guaranteed crash.
- Consolidate the string null-or-empty checks on Micronaut's
  io.micronaut.core.util.StringUtils (already on the classpath) and JDK
  Objects.requireNonNullElse: drops the freshly added private copies in
  K8sClientHelper and Monitoring plus the pre-existing duplicate in
  GitHandler, so the predicate cannot drift between files.
- Introduce the ValuesFilePaths record in ArgoCdApplicationStrategy and
  derive the gop/user values paths in one place. The extracted helpers
  previously took 3-4 same-typed String path parameters that could be
  transposed at the call site without any compiler error.
- Deduplicate the root-logger lookup in GitopsPlaygroundCli behind a
  rootLogger(LoggerContext) method. Method return values are not flagged by
  Sonar rule S1312, so this restores the visible object identity of the
  three detach/re-attach call sites without reopening the finding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three version pins were controllable by Renovate but not actually
tracked:
- scm-manager helm chart version had no renovate annotation and its
  file wasn't in the custom manager's fileMatch
- kube-prometheus-stack's renovate comment was split across two
  lines, which the custom manager's regex can't match
- Dockerfile's HELM_VERSION arg was only used in curl download URLs,
  invisible to Renovate's default dockerfile manager

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@mdroll mdroll Jul 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really necessary for the first pitch:
Sorting methods and attributes order by access modifier (public, protected, private, none)

Ignoring Generic Catching/Throwing Exceptions
Smaller Sonar Lint recommendations e.g. primitive boolean expressions,
Guard Log Statements for log.info() for now because it makes the code messy

@Getter private final List<AbstractTool> tools;

@Inject
public DeploymentOrchestrator(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not only on this place but on all other places where the amount of constructor parameter exceeds the limit of 5 let's try to invent a encapsulated object e.g. Factory or something else.

// Ownership of clusterResourcesRepository is handed off to the returned RepositoryWorkspace,
// which closes it in RepositoryWorkspace#close(). Sonar can't trace that across the boundary.
@SuppressWarnings("java:S2095")
private RepositoryWorkspace createSingleInstanceWorkspace(DeploymentContext context) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context attribute is newer used


// Ownership of both GitRepo instances is handed off to the returned RepositoryWorkspace,
// which closes them in RepositoryWorkspace#close(). Sonar can't trace that across the boundary.
@SuppressWarnings("java:S2095")

@mdroll mdroll Jul 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Supressing warnings without comment


// Ownership of clusterResourcesRepository is handed off to the returned RepositoryWorkspace,
// which closes it in RepositoryWorkspace#close(). Sonar can't trace that across the boundary.
@SuppressWarnings("java:S2095")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Supressing warnings without comment

private final GitRepoFactory gitRepoFactory;
private final GitHandler gitHandler;

@Getter @Setter private RepositoryWorkspace workspace;

@mdroll mdroll Jul 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For better readability and maintainability put Annotations down under each other

@Getter
@Setter
@whatever
@whenever
private boolean repositoriesClones;

@mdroll mdroll left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need a bunch of rework here and there. Not explicitly looked on general sonar hints.
Still we need to test also the others profiles. And also test on openshift / OKD.


String version = createVersionOutput();
if (cliParams.getApplication().getVersionInfoRequested()) {
System.out.println(version);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no log.info()?

runHook(app, "preConfigInit", AbstractTool::preConfigInit, config);

if (config.getApplication().getOutputConfigFile()) {
System.out.println(config.toYaml(false));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why no log.info()?

@Slf4j
public class ApplicationConfigurator {

private final FileSystemUtils fileSystemUtils;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Newer used

}

private void storeGopInformationInSecret(DeploymentContext context) {
String namespace = DEFAULT_GOP_NAMESPACE;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is suited for to be in a own method

}

public void createLocalDirectories() {
Path.of(clusterResourcesRootDir()).toFile().mkdirs();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Next pitch switch to more modern API-Way:

Stream.of(
clusterResourcesRootDir(),
clusterResourcesAppsDir(),
clusterResourcesArgoCdDir(),
clusterResourcesApplicationsDir(),
clusterResourcesProjectsDir()
).forEach(this::createDirectorySafely);

* @param name name of the secret
* @return the base64-encoded {@code namespaces} value of the secret
*/
public String getArgoCDNamespacesSecret(String name) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never used

* @param usernameKey data key holding the username
* @return the decoded credentials
*/
public Credentials getCredentialsFromSecret(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never used

* @param name name of the ConfigMap
* @param filePath path of the file whose content becomes the ConfigMap data
*/
public void createConfigMapFromFile(String name, String filePath) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never used

}

private List<HasMetadata> loadYamlItems(InputStream stream, String sourceDescription) {
try {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this?


/** Kubernetes client using Fabric8 Kubernetes Client. */
@Singleton
@SuppressWarnings("java:S3776")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Supressing here
Lombok may can be added here so a few setter/getter methods are not needed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This @SuppressWarnings("java:S3776") is applied to the entire class, which hides complexity warnings for all methods. Consider narrowing it to only the specific methods that truly need it (e.g. applyYaml), or reduce complexity there instead. A class-level suppression makes it easy to miss new problems added later.

() -> {
// type is NonNamespaceOperation<Secret, SecretList, Resource<Secret>>; kept as `var`
// deliberately, spelling it out would hurt readability more than it helps.
var secretsClient = client.secrets().inNamespace(resolveNamespace(namespace));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This delete-then-create pattern is non-atomic: if the delete succeeds but the subsequent create fails, the secret is left absent. The createImagePullSecret method uses createOr(NonDeletingOperation::update) which is idempotent in a single API call. Consider using the same pattern here:
secretsClient.resource(secret).createOr(NonDeletingOperation::update);

log.debug("Namespace {} already exists.", namespace);
return true;
}
} catch (Exception e) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catching Exception broadly here can silently swallow real connectivity or authorization errors (e.g. 403 Forbidden would be treated as "namespace does not exist", causing createNamespace to attempt creation and fail with a confusing error). Consider catching only KubernetesClientException and rethrowing for non-404 status codes.

String auth =
Base64.getEncoder()
.encodeToString((user + ":" + password).getBytes(StandardCharsets.UTF_8));
String dockerConfig =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dockerConfig JSON string is assembled by hand via string concatenation. A username or password containing " would break the JSON silently. Use a proper serializer instead:
Map<String, Object> dockerConfigMap = Map.of( "auths", Map.of(host, Map.of("username", user, "password", password, "auth", auth))); String dockerConfig = Serialization.asJson(dockerConfigMap);

* @param selectors label key-value pairs the resources must match
*/
public void delete(String resource, String namespace, Tuple<?, ?>... selectors) {
if (selectors == null || selectors.length == 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two convenience overloads delete(resource) and delete(resource, namespace) both delegate to this varargs method with an empty array, which immediately throws IllegalArgumentException("Missing selectors"). This makes those overloads permanently broken. Either remove the empty-array guard for "delete all" semantics, or remove/document the no-selector overloads.

@FelixWende99 FelixWende99 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review of the Groovy-to-Java migration across the 20 biggest classes. Co-Authored by Copilot

patchType = PatchType.JSON;
break;
default:
patchType = PatchType.STRATEGIC_MERGE;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Undocumented fallthrough to STRATEGIC_MERGE — An unrecognised type string silently falls through to STRATEGIC_MERGE. Consider throwing IllegalArgumentException so misconfigurations surface immediately rather than silently using the wrong patch type.

Comment on lines +263 to +266
Map<String, Object> match = findApiResourceViaDiscovery(client, normalized, resourceType);

if (match.isEmpty()) {
throw new K8sClient.KubernetesApiResourceNotFoundException(resourceType);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent not-found contractgetCustomResourceClient throws KubernetesApiResourceNotFoundException when the match is empty, but findApiResourceViaDiscovery returns an empty map. Callers must know which convention applies. Consider making both throw, or both return Optional<Map<...>>.

log.debug("Actual config: {}", config.toYaml(true));
runHook(app, "postConfigInit", AbstractTool::postConfigInit, config);

context.close();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app bean used after its originating ApplicationContext is closedapp is fetched from the first context (line 83), which is then closed on line 97. app is reused on line 122 from the new context but the same object reference from the old closed context is passed to runHook. Fetch app from the second context after register() for consistency, or document why cross-context reuse is safe here.

private void addScmConfig(Config newConfig) {
log.debug("Adding additional config for SCM");

if (hasText(newConfig.getScm().getScmManager().getUrl())) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential NPE when scmManager is nullnewConfig.getScm().getScmManager().getUrl() is called unconditionally. In GitLab-only mode, GitHandler.validate() explicitly sets scmManager to null. Add a null guard:

if (newConfig.getScm().getScmManager() != null && hasText(newConfig.getScm().getScmManager().getUrl())) {

"Successfully set features.argocd.resourceInclusionsCluster via Kubernetes ENV to: {}",
internalClusterUrl);
} catch (MalformedURLException e) {
throw new UncheckedIOException(errorMessage, e);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MalformedURLException wrapped as UncheckedIOExceptionMalformedURLException is not an I/O error. Wrapping it as UncheckedIOException misrepresents the failure to callers and catch blocks. Use IllegalArgumentException (consistent with how line 328 handles the same exception type in isUrlSetAndValid).


@JsonIgnore
public String getHost() {
return URI.create(url).getHost();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URI.create(url) throws IllegalArgumentException on empty or invalid URLgetHost() and getProtocol() call URI.create(url) unconditionally. If url is empty (its default value is ""), URI.create("").getHost() returns null rather than throwing, but callers expecting a non-null host may behave unexpectedly. A blank-url guard or Optional return type would make the contract explicit.

}
}

public void listDirectories(String parentDir) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

listDirectories method has no return value and unclear purpose — This method (lines 196–202) calls getAllFilesFromDirectoryWithEnding(parentDir, "") which matches all files (not just directories), then logs each path at DEBUG. The method name says "list directories" but lists all files. Either fix the predicate to filter for directories only, or rename/document the method's actual behaviour.

Comment on lines +194 to +201
try {
TemplateModel statics =
new DefaultObjectWrapperBuilder(freemarker.template.Configuration.VERSION_2_3_32)
.build()
.getStaticModels();
values.put("statics", statics);
} catch (Exception e) {
throw new RuntimeException("Failed to expose freemarker statics model", e);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Freemarker getStaticModels() failure wrapped as RuntimeException — The catch block on line 200 wraps a Exception as RuntimeException. This is the same pattern flagged elsewhere in the codebase. DefaultObjectWrapperBuilder.build() does not declare checked exceptions; if it throws, it's a programming error (misconfiguration), not an expected runtime failure. Let it propagate unchecked or use a more specific message.

: new HashMap<>();

Map<String, Object> mergedMap = MapUtils.deepMerge(values, templatedMap);
tempValuesPath = new FileSystemUtils().writeTempFile(mergedMap);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new FileSystemUtils() bypasses DIprepareHelmValues() calls new FileSystemUtils() directly instead of using a constructor-injected instance. This makes the class harder to test and breaks Micronaut's singleton scope for FileSystemUtils. Add FileSystemUtils fileSystemUtils as a constructor parameter and use it here.

Comment on lines +193 to +215
while (System.currentTimeMillis() - startTime < timeoutMillis) {
try {
retrofit2.Call<Void> call = scmManager.getApiClient().generalApi().checkScmmAvailable();
retrofit2.Response<Void> response = call.execute();

if (response.isSuccessful()) {
log.debug("SCM-Manager is available.");
return;
}
} catch (Exception e) {
log.debug("Waiting for SCM-Manager... Error: {}", e.getMessage());
}

try {
Thread.sleep(intervalMillis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

throw new IllegalStateException(
"Timeout: SCM-Manager did not respond with 200 OK within " + timeoutSeconds + " seconds");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

waitForScmmAvailable swallows InterruptedException in the timeout loop — Line 209 catches InterruptedException, restores the interrupt flag with Thread.currentThread().interrupt(), but then continues the loop rather than returning or throwing. This means an interrupted thread will busy-spin until the timeout expires instead of stopping promptly. Add a return or break after Thread.currentThread().interrupt().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants