diff --git a/crd-generator/cli/pom.xml b/crd-generator/cli/pom.xml index 0b86b0a1037..9dbea66be83 100644 --- a/crd-generator/cli/pom.xml +++ b/crd-generator/cli/pom.xml @@ -117,7 +117,7 @@ 2.1.1 -Xmx1G - java-gen + crd-gen true diff --git a/crd-generator/cli/src/main/java/io/fabric8/crd/generator/cli/CRDGeneratorCLI.java b/crd-generator/cli/src/main/java/io/fabric8/crd/generator/cli/CRDGeneratorCLI.java index 034b4ee99d4..28d79fa5615 100644 --- a/crd-generator/cli/src/main/java/io/fabric8/crd/generator/cli/CRDGeneratorCLI.java +++ b/crd-generator/cli/src/main/java/io/fabric8/crd/generator/cli/CRDGeneratorCLI.java @@ -21,89 +21,134 @@ 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 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; - @CommandLine.Option(names = { "-cr", - "--custom-resource" }, description = "Custom Resource classes, which should be considered to generate the CRDs. If set, scanning is disabled.") - List customResourceClasses; - - @CommandLine.Option(names = { "-cp", "--classpath" }, description = "The classpath which be used during the CRD generation") - List 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 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 includedPackages = new LinkedList<>(); + @CommandLine.Option(names = { "--include-group" }, description = "Use only Custom Resources classes of one or more groups.") + List includedGroups = new LinkedList<>(); + @CommandLine.Option(names = { + "--include-version" }, description = "Use only Custom Resource classes of one or more versions.") + List includedVersions = new LinkedList<>(); + @CommandLine.Option(names = { "--exclude-package" }, description = "Exclude Custom Resource classes by package.") + List excludedPackages = new LinkedList<>(); + @CommandLine.Option(names = { "--exclude-group" }, description = "Exclude Custom Resource classes by group.") + List excludedGroups = new LinkedList<>(); + @CommandLine.Option(names = { "--exclude-version" }, description = "Exclude Custom Resource classes by version.") + List excludedVersions = new LinkedList<>(); + + @CommandLine.Option(names = { + "-v" }, description = "Verbose mode. Helpful for troubleshooting. Multiple -v options increase the verbosity.") + List 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 customResourceClasses = source.stream() + .filter(s -> s.customResourceClass != null) + .map(s -> s.customResourceClass) + .collect(Collectors.toList()); + + List filesToIndex = source.stream() + .filter(s -> s.fileToIndex != null) + .map(s -> s.fileToIndex) + .collect(Collectors.toList()); + + List 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())); }); } @@ -111,8 +156,52 @@ 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 { + @Override + public Source convert(String value) { + return Source.from(value); + } + } } diff --git a/crd-generator/cli/src/main/resources/simplelogger.properties b/crd-generator/cli/src/main/resources/simplelogger.properties new file mode 100644 index 00000000000..a884d6065f3 --- /dev/null +++ b/crd-generator/cli/src/main/resources/simplelogger.properties @@ -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 diff --git a/crd-generator/cli/src/test/java/io/fabric8/crd/generator/cli/CRDGeneratorCLITest.java b/crd-generator/cli/src/test/java/io/fabric8/crd/generator/cli/CRDGeneratorCLITest.java index d5995f0c042..bce76b16ed5 100644 --- a/crd-generator/cli/src/test/java/io/fabric8/crd/generator/cli/CRDGeneratorCLITest.java +++ b/crd-generator/cli/src/test/java/io/fabric8/crd/generator/cli/CRDGeneratorCLITest.java @@ -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()); } @@ -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); @@ -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); @@ -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);