Skip to content

Commit

Permalink
Implement missing CLI options and polish interface
Browse files Browse the repository at this point in the history
  • Loading branch information
baloo42 committed May 8, 2024
1 parent 7674c76 commit 9e6889b
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 46 deletions.
2 changes: 1 addition & 1 deletion crd-generator/cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
<version>2.1.1</version>
<configuration>
<flags>-Xmx1G</flags>
<programFile>java-gen</programFile>
<programFile>crd-gen</programFile>
<attachProgramFile>true</attachProgramFile>
</configuration>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,98 +21,187 @@
import io.fabric8.crdv2.generator.CustomResourceInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.simple.SimpleLogger;
import picocli.CommandLine;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

@CommandLine.Command(name = "crd-gen", mixinStandardHelpOptions = true, helpCommand = true, versionProvider = KubernetesClientVersionProvider.class)
/**
* CRD-Generator Command Line Interface.
*/
@CommandLine.Command(name = "crd-gen", mixinStandardHelpOptions = true, helpCommand = true, versionProvider = KubernetesClientVersionProvider.class, sortOptions = false, description = "\nFabric8 CRD-Generator:\nGenerate Custom Resource Definitions (CRD) for Kubernetes from Java classes.\n")
public class CRDGeneratorCLI implements Runnable {

private static final Logger log = LoggerFactory.getLogger(CustomResourceCollector.class);
private static final CRDGenerationInfo EMPTY_INFO = new CRDGenerationInfo();

private Logger log;

@CommandLine.Spec
CommandLine.Model.CommandSpec spec;

@CommandLine.Option(names = { "-c",
"--classes" }, description = "Directories or JAR files to be used to scan for Custom Resource classes")
List<File> classesToIndex;
@CommandLine.Parameters(arity = "1..*", converter = SourceTypeConverter.class, description = "A directory or JAR file to scan for Custom Resource classes, or a full qualified Custom Resource class name.")
List<Source> source;

@CommandLine.Option(names = { "-cr",
"--custom-resource" }, description = "Custom Resource classes, which should be considered to generate the CRDs. If set, scanning is disabled.")
List<String> customResourceClasses;

@CommandLine.Option(names = { "-cp", "--classpath" }, description = "The classpath which be used during the CRD generation")
List<String> classpaths = new ArrayList<>();
@CommandLine.Option(names = {
"--classpath" }, description = "Additional classpath elements, e.g. a dependency packaged as JAR file or a directory of class files.")
List<String> classpathElements = new ArrayList<>();

@CommandLine.Option(names = { "-o", "--output-dir" }, description = "The output directory for the generated CRDs")
@CommandLine.Option(names = { "-o",
"--output-dir" }, description = "The output directory for the generated CRDs.", showDefaultValue = CommandLine.Help.Visibility.ALWAYS)
File outputDirectory = new File(".");

@CommandLine.Option(names = { "-f",
"--force-index" }, description = "If true, a Jandex index will be created even if the directory or JAR file contains an existing index.", defaultValue = "false")
@CommandLine.Option(names = {
"--force-index" }, description = "Create Jandex index even if the directory or JAR file contains an existing index.", defaultValue = "false")
Boolean forceIndex;

@CommandLine.Option(names = { "-p", "--parallel" }, description = "Enable parallel generation", defaultValue = "true")
Boolean parallel;
@CommandLine.Option(names = { "--no-parallel" }, description = "Disable parallel generation.", defaultValue = "false")
Boolean parallelDisabled;

@CommandLine.Option(names = {
"--implicit-preserve-unknown-fields" }, description = "If enabled, all objects are implicitly marked with x-kubernetes-preserve-unknown-fields: true.", defaultValue = "false")
"--implicit-preserve-unknown-fields" }, description = "Mark all objects with x-kubernetes-preserve-unknown-fields: true.", defaultValue = "false")
Boolean implicitPreserveUnknownFields;

@CommandLine.Option(names = {
"--include-package" }, description = "Use only Custom Resource classes of one or more packages.")
List<String> includedPackages = new LinkedList<>();
@CommandLine.Option(names = { "--include-group" }, description = "Use only Custom Resources classes of one or more groups.")
List<String> includedGroups = new LinkedList<>();
@CommandLine.Option(names = {
"--include-version" }, description = "Use only Custom Resource classes of one or more versions.")
List<String> includedVersions = new LinkedList<>();
@CommandLine.Option(names = { "--exclude-package" }, description = "Exclude Custom Resource classes by package.")
List<String> excludedPackages = new LinkedList<>();
@CommandLine.Option(names = { "--exclude-group" }, description = "Exclude Custom Resource classes by group.")
List<String> excludedGroups = new LinkedList<>();
@CommandLine.Option(names = { "--exclude-version" }, description = "Exclude Custom Resource classes by version.")
List<String> excludedVersions = new LinkedList<>();

@CommandLine.Option(names = {
"-v" }, description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.")
List<Boolean> verbose = new LinkedList<>();

private CRDGenerationInfo crdGenerationInfo = EMPTY_INFO;

@Override
public void run() {
System.setProperty(SimpleLogger.DEFAULT_LOG_LEVEL_KEY, getLogLevel());
log = LoggerFactory.getLogger(CRDGeneratorCLI.class);

List<String> customResourceClasses = source.stream()
.filter(s -> s.customResourceClass != null)
.map(s -> s.customResourceClass)
.collect(Collectors.toList());

List<File> filesToIndex = source.stream()
.filter(s -> s.fileToIndex != null)
.map(s -> s.fileToIndex)
.collect(Collectors.toList());

List<String> allClasspathElements = new LinkedList<>(this.classpathElements);
// add all files which are considered to index automatically to classpath elements
filesToIndex.stream()
.map(File::toString)
.forEach(allClasspathElements::add);

CustomResourceCollector customResourceCollector = new CustomResourceCollector()
.withParentClassLoader(Thread.currentThread().getContextClassLoader())
.withClasspaths(classpaths)
.withFilesToIndex(classesToIndex)
.withClasspaths(allClasspathElements)
.withFilesToIndex(filesToIndex)
.withForceIndex(forceIndex)
// TODO: allow to exclude / include
/*
* .withIncludePackages(inclusions.getPackages())
* .withIncludeGroups(inclusions.getGroups())
* .withIncludeVersions(inclusions.getVersions())
* .withExcludePackages(exclusions.getPackages())
* .withExcludeGroups(exclusions.getGroups())
* .withExcludeVersions(exclusions.getVersions())
*/
.withCustomResourceClasses(customResourceClasses);
.withCustomResourceClasses(customResourceClasses)
.withIncludePackages(includedPackages)
.withIncludeGroups(includedGroups)
.withIncludeVersions(includedVersions)
.withExcludePackages(excludedPackages)
.withExcludeGroups(excludedGroups)
.withExcludeVersions(excludedVersions);

CustomResourceInfo[] customResourceInfos = customResourceCollector.findCustomResources();

log.info("Found {} CustomResources", customResourceInfos.length);

File sanitizedOutputDirectory;
try {
Files.createDirectories(outputDirectory.toPath());
sanitizedOutputDirectory = outputDirectory.getCanonicalFile();
} catch (IOException e) {
throw new RuntimeException(e);
}

try {
Files.createDirectories(sanitizedOutputDirectory.toPath());
} catch (IOException e) {
throw new RuntimeException("Could not create output directory: " + e.getMessage());
}

CRDGenerator crdGenerator = new CRDGenerator()
.customResources(customResourceInfos)
.withParallelGenerationEnabled(parallel)
.withParallelGenerationEnabled(!parallelDisabled)
.withImplicitPreserveUnknownFields(implicitPreserveUnknownFields)
.inOutputDir(outputDirectory);
.inOutputDir(sanitizedOutputDirectory);

crdGenerationInfo = crdGenerator.detailedGenerate();
crdGenerationInfo.getCRDDetailsPerNameAndVersion().forEach((crdName, versionToInfo) -> {
log.info("Generated CRD {}:", crdName);
System.out.printf("Generated CRD %s:%n", crdName);
versionToInfo.forEach(
(version, info) -> log.info(" {} -> {}", version, info.getFilePath()));
(version, info) -> System.out.printf(" %s -> %s%n", version, info.getFilePath()));
});
}

public CRDGenerationInfo getCrdGenerationInfo() {
return crdGenerationInfo;
}

private String getLogLevel() {
switch (verbose.size()) {
case 1:
return "info";
case 2:
return "debug";
case 3:
return "trace";
default:
return "warn";
}
}

public static void main(String[] args) {
int exitCode = new CommandLine(new CRDGeneratorCLI()).execute(args);
System.exit(exitCode);
}


/**
* Wraps a positional argument.
*/
private static class Source {
File fileToIndex;
String customResourceClass;

static Source from(String value) {
Source source = new Source();
File f = new File(value);
if (f.exists()) {
try {
source.fileToIndex = f.getCanonicalFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
source.customResourceClass = value;
}
return source;
}
}

private static class SourceTypeConverter implements CommandLine.ITypeConverter<Source> {
@Override
public Source convert(String value) {
return Source.from(value);
}
}
}
43 changes: 43 additions & 0 deletions crd-generator/cli/src/main/resources/simplelogger.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#
# Copyright (C) 2015 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# SLF4J's SimpleLogger configuration file
# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err.
# Default logging detail level for all instances of SimpleLogger.
# Must be one of ("trace", "debug", "info", "warn", or "error").
# If not specified, defaults to "info".
org.slf4j.simpleLogger.defaultLogLevel=warn
# Logging detail level for a SimpleLogger instance named "xxxxx".
# Must be one of ("trace", "debug", "info", "warn", or "error").
# If not specified, the default logging detail level is used.
#org.slf4j.simpleLogger.log.xxxxx=
# Set to true if you want the current date and time to be included in output messages.
# Default is false, and will output the number of milliseconds elapsed since startup.
#org.slf4j.simpleLogger.showDateTime=false
# The date and time format to be used in the output messages.
# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat.
# If the format is not specified or is invalid, the default format is used.
# The default format is yyyy-MM-dd HH:mm:ss:SSS Z.
#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z
# Set to true if you want to output the current thread name.
# Defaults to true.
org.slf4j.simpleLogger.showThreadName=false
# Set to true if you want the Logger instance name to be included in output messages.
# Defaults to true.
org.slf4j.simpleLogger.showLogName=false
# Set to true if you want the last component of the name to be included in output messages.
# Defaults to false.
#org.slf4j.simpleLogger.showShortLogName=false
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@
public class CRDGeneratorCLITest {

@Test
public void givenNoInput_thenGenerateNoCRDs() {
public void givenNoInput_thenFailAndGenerateNoCRDs() {
CRDGeneratorCLI cliApp = new CRDGeneratorCLI();
CommandLine cmd = new CommandLine(cliApp);
int exitCode = cmd.execute();
assertEquals(0, exitCode);
assertEquals(2, exitCode);
assertEquals(0, cliApp.getCrdGenerationInfo().numberOfGeneratedCRDs());
}

Expand All @@ -41,8 +41,8 @@ public void givenSingleCRClassNameFromSameClasspath_thenGenerate(@TempDir Path t
CRDGeneratorCLI cliApp = new CRDGeneratorCLI();
CommandLine cmd = new CommandLine(cliApp);
String[] args = new String[] {
"-cr", "io.fabric8.crd.generator.cli.examples.basic.Basic",
"-o", tempDir.toString()
"-o", tempDir.toString(),
"io.fabric8.crd.generator.cli.examples.basic.Basic"
};
int exitCode = cmd.execute(args);
assertEquals(0, exitCode);
Expand All @@ -55,9 +55,9 @@ public void givenSingleCRClassNameFromExternalClasspath_thenGenerate(@TempDir Pa
CRDGeneratorCLI cliApp = new CRDGeneratorCLI();
CommandLine cmd = new CommandLine(cliApp);
String[] args = new String[] {
"-cp", "../api-v2/target/test-classes/",
"-cr", "io.fabric8.crdv2.example.basic.Basic",
"-o", tempDir.toString()
"--classpath", "../api-v2/target/test-classes/",
"-o", tempDir.toString(),
"io.fabric8.crdv2.example.basic.Basic"
};

int exitCode = cmd.execute(args);
Expand All @@ -71,9 +71,8 @@ public void givenClassesDirectory_thenScanAndGenerate(@TempDir Path tempDir) {
CRDGeneratorCLI cliApp = new CRDGeneratorCLI();
CommandLine cmd = new CommandLine(cliApp);
String[] args = new String[] {
"-c", "target/test-classes/",
//"-k", "basics.sample.fabric8.io",
"-o", tempDir.toString()
"-o", tempDir.toString(),
"target/test-classes/"
};

int exitCode = cmd.execute(args);
Expand Down

0 comments on commit 9e6889b

Please sign in to comment.