Repository navigation
[DRAFT] Demo operator CRD with generated ServerConfiguration #11806
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
|
|
||
| <!-- | ||
| ~ Copyright 2016 Red Hat, Inc. and/or its affiliates | ||
| ~ and other contributors as indicated by the @author tags. | ||
| ~ | ||
| ~ 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. | ||
| --> | ||
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
| <parent> | ||
| <artifactId>keycloak-docs-parent</artifactId> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know it's just a demo but wanted to point out that we're inheriting from docs module for some reason. Is that intended?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not intended, will be removed if/when we bring this forward |
||
| <groupId>org.keycloak</groupId> | ||
| <version>999-SNAPSHOT</version> | ||
| <relativePath>../../operator/pom.xml</relativePath> | ||
| </parent> | ||
|
|
||
| <modelVersion>4.0.0</modelVersion> | ||
|
|
||
| <name>Keycloak Operator Maven Plugin</name> | ||
| <artifactId>keycloak-operator-maven-plugin</artifactId> | ||
|
pedroigor marked this conversation as resolved.
Resolved
|
||
| <description>Keycloak Operator Maven Plugin</description> | ||
| <packaging>maven-plugin</packaging> | ||
|
|
||
| <dependencies> | ||
| <dependency> | ||
| <groupId>org.apache.maven</groupId> | ||
| <artifactId>maven-plugin-api</artifactId> | ||
| <version>3.6.3</version> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.apache.maven.plugin-tools</groupId> | ||
| <artifactId>maven-plugin-annotations</artifactId> | ||
| <version>3.6.0</version> | ||
| <scope>provided</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.apache.maven</groupId> | ||
| <artifactId>maven-project</artifactId> | ||
| <version>2.2.1</version> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.keycloak</groupId> | ||
| <artifactId>keycloak-quarkus-server</artifactId> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>com.github.javaparser</groupId> | ||
| <artifactId>javaparser-core</artifactId> | ||
|
pedroigor marked this conversation as resolved.
Resolved
|
||
| <version>3.24.2</version> | ||
| </dependency> | ||
| </dependencies> | ||
|
|
||
| </project> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| package org.keycloak.operator.codegen; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||
| import com.fasterxml.jackson.annotation.JsonPropertyDescription; | ||
| import com.github.javaparser.ast.CompilationUnit; | ||
| import com.github.javaparser.ast.Modifier; | ||
| import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; | ||
| import com.github.javaparser.ast.body.FieldDeclaration; | ||
| import com.github.javaparser.ast.expr.StringLiteralExpr; | ||
| import com.github.javaparser.utils.StringEscapeUtils; | ||
| import org.apache.maven.plugin.AbstractMojo; | ||
| import org.apache.maven.plugin.MojoFailureException; | ||
| import org.apache.maven.plugin.logging.Log; | ||
| import org.apache.maven.plugins.annotations.LifecyclePhase; | ||
| import org.apache.maven.plugins.annotations.Mojo; | ||
| import org.apache.maven.plugins.annotations.Parameter; | ||
| import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; | ||
|
|
||
| import java.io.File; | ||
| import java.io.FileWriter; | ||
| import java.io.IOException; | ||
| import java.io.PrintWriter; | ||
| import java.nio.file.Path; | ||
| import java.nio.file.Paths; | ||
| import java.util.HashMap; | ||
| import java.util.HashSet; | ||
| import java.util.Locale; | ||
| import java.util.Set; | ||
| import java.util.Map; | ||
|
|
||
| import static io.smallrye.config.common.utils.StringUtil.replaceNonAlphanumericByUnderscores; | ||
|
|
||
| @Mojo(name = "keycloak-operator-codegen", defaultPhase = LifecyclePhase.GENERATE_SOURCES) | ||
| public class CodegenMojo extends AbstractMojo { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was wondering if we should not have have it the other way around. Some POJO with few custom annotations on top of field as the source of truth. This would get translated into the mappers. Might be a bit cleaner than a maven plugin for basically generating classes on the fly? In any case, might be worth exploring what Store.X team will be doing. As mentioned, I think it is related to this, so might make sense to align on it.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This would require a pretty invasive change on how the CLI and configuration work as of today, I would like to hear from @pedroigor and @DGuhr But, also, let's first open a GH "discussion" about the desired outcome and tackle the technical implementation as a separate subject.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My PoC actually had a POJO inside that could be used/extended as source of truth (the pojo is actually applicable to all sorts of "PortConfiguration", so the actual configurationkey POJO would extend it and add e.g. name, description, and so on), and I am pretty paranoid due to bad experiences when it comes to code generators, so +1 from my side. Nevertheless I totally agree this is more invasive, needs a bit of boilerplate work and should be discussed first. For the problem space / the discussion part, a primer:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @DGuhr +1 to that it'd also be beneficial in general – decoupling our configuration logic from Quarkus. |
||
|
|
||
| @Parameter(property = "operator.codegen.target", defaultValue = "${basedir}/target/generated-sources/java") | ||
| private File target; | ||
|
|
||
| private Map<String, Set<Property>> configurations = new HashMap<>(); | ||
|
|
||
| private void addKeyValue(String k, Property v) { | ||
| if (configurations.containsKey(k)) { | ||
| Set<Property> tmpValue = configurations.get(k); | ||
| tmpValue.add(v); | ||
| configurations.put(k, tmpValue); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a nitpick (for when this is not a demo anymore :)). I believe you don’t need to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yup |
||
| } else { | ||
| Set<Property> configs = new HashSet<>(); | ||
| configs.add(v); | ||
| configurations.put(k, configs); | ||
| } | ||
| } | ||
|
|
||
| private void writeToFile(File file, String str) throws IOException { | ||
| try (FileWriter fileWriter = new FileWriter(file); | ||
| PrintWriter printWriter = new PrintWriter(fileWriter)) { | ||
| printWriter.println(str); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void execute() throws MojoFailureException { | ||
| try { | ||
| Log log = getLog(); | ||
|
|
||
| // TODO: if custom image should we remove build time configurations? | ||
| PropertyMappers.getMappers().forEach(c -> { | ||
| addKeyValue( | ||
| c.getCategory().getHeading(), | ||
| new Property( | ||
| c.getCliFormat().replaceFirst("--", ""), | ||
| c.getType().getCanonicalName(), | ||
| c.getDescription()) | ||
| ); | ||
| }); | ||
|
|
||
| Map<String, CompilationUnit> cus = new HashMap<>(); | ||
|
|
||
| CompilationUnit cu = new CompilationUnit(); | ||
| cu.setPackageDeclaration("org.keycloak.codegen.configuration"); | ||
|
|
||
| ClassOrInterfaceDeclaration serverConfig = cu.addClass("ServerConfig"); | ||
| cus.put("ServerConfig", cu); | ||
|
|
||
| Path destination = Paths.get(target.getAbsolutePath(), "org", "keycloak", "codegen", "configuration"); | ||
| destination.toFile().mkdirs(); | ||
|
|
||
| configurations.forEach((k, v) -> { | ||
| String className = k.replace("/", "_"); | ||
| serverConfig.addField(className, className.toLowerCase(Locale.ROOT)); | ||
|
|
||
| CompilationUnit innerCu = new CompilationUnit(); | ||
| innerCu.setPackageDeclaration("org.keycloak.codegen.configuration"); | ||
| ClassOrInterfaceDeclaration clazz = innerCu.addClass(className); | ||
| cus.put(className, innerCu); | ||
|
|
||
| v.forEach(p -> { | ||
| String fieldName = replaceNonAlphanumericByUnderscores(p.getName()); | ||
|
|
||
| if (!fieldName.equals("null")) { // edge case in Proxy configuration | ||
| FieldDeclaration field = cl 10BC8 azz.addField(p.getType(), fieldName, Modifier.Keyword.PUBLIC); | ||
| field.addSingleMemberAnnotation( | ||
| JsonProperty.class, | ||
| new StringLiteralExpr(p.getName())); | ||
| if (p.getDescription() != null) { | ||
| field.addSingleMemberAnnotation( | ||
| JsonPropertyDescription.class, | ||
| new StringLiteralExpr(StringEscapeUtils.escapeJava(p.getDescription()))); | ||
| } | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| cus.forEach((k, v) -> { | ||
| try { | ||
| writeToFile(destination.resolve(k + ".java").toFile(), v.toString()); | ||
| } catch (IOException e) { | ||
| e.printStackTrace(); | ||
| } | ||
| }); | ||
| } catch (Exception e) { | ||
| e.printStackTrace(); | ||
| throw new MojoFailureException("Failed to generate code", e); | ||
| } | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,40 @@ | ||||
| package org.keycloak.operator.codegen; | ||||
|
|
||||
| import java.util.Objects; | ||||
|
|
||||
| public class Property { | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if this type should be added to the configuration API itself. We are using a similar structure when generating docs. See
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was wondering if I should try to re-use the docs generation, but I have no problems in including
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that would be awesome but I don't think it is a blocker. |
||||
| private final String name; | ||||
| private final String type; | ||||
| private final String description; | ||||
|
|
||||
| public Property(String name, String type, String description) { | ||||
| this.name = name; | ||||
| this.type = type; | ||||
| this.description = description; | ||||
| } | ||||
|
|
||||
| public String getName() { | ||||
| return name; | ||||
| } | ||||
|
|
||||
| public String getType() { | ||||
| return type; | ||||
| } | ||||
|
|
||||
| public String getDescription() { | ||||
| return description; | ||||
| } | ||||
|
|
||||
| @Override | ||||
| public boolean equals(Object o) { | ||||
| if (this == o) return true; | ||||
| if (o == null || getClass() != o.getClass()) return false; | ||||
| Property property = (Property) o; | ||||
| return Objects.equals(name, property.name); | ||||
| } | ||||
|
|
||||
| @Override | ||||
| public int hashCode() { | ||||
| return Objects.hash(name); | ||||
| } | ||||
| } | ||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package org.keycloak.quarkus.runtime.cli; | ||
|
|
||
| import picocli.CommandLine; | ||
|
|
||
| import java.util.function.Function; | ||
|
|
||
| public class PicocliConverterAdapter { | ||
|
|
||
| public static class ConverterAdapter<T> implements CommandLine.ITypeConverter<T> { | ||
| private final Function<String, T> conversionFn; | ||
|
|
||
| public ConverterAdapter(Function<String, T> fn) { | ||
| this.conversionFn = fn; | ||
| } | ||
|
|
||
| @Override | ||
| public T convert(String s) throws Exception { | ||
| try { | ||
| return this.conversionFn.apply(s); | ||
| } catch (Exception e) { | ||
| throw new CommandLine.TypeConversionException(e.getMessage()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public static <T> CommandLine.ITypeConverter<T> build(Function<String, T> fn) { | ||
| return new ConverterAdapter<T>(fn); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.