├── HELP.md
├── README.md
├── bin
├── foo.yaml
├── regen_crds.sh
└── test.yaml
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
├── java
│ └── io
│ │ └── spring
│ │ ├── ControllersApplication.java
│ │ └── models
│ │ ├── V1Foo.java
│ │ ├── V1FooList.java
│ │ ├── V1FooSpec.java
│ │ └── V1FooStatus.java
└── resources
│ ├── application.properties
│ ├── configmap.yaml
│ └── deployment.yaml
└── test
└── java
└── io
└── spring
└── ControllersApplicationTests.java
/HELP.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | ### Reference Documentation
4 | For further reference, please consider the following sections:
5 |
6 | * [Official Gradle documentation](https://docs.gradle.org)
7 | * [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.0.4/gradle-plugin/reference/html/)
8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.0.4/gradle-plugin/reference/html/#build-image)
9 | * [Spring for GraphQL](https://docs.spring.io/spring-boot/docs/3.0.4/reference/html/web.html#web.graphql)
10 |
11 | ### Guides
12 | The following guides illustrate how to use some features concretely:
13 |
14 | * [Building a GraphQL service](https://spring.io/guides/gs/graphql-server/)
15 |
16 | ### Additional Links
17 | These additional references should also help you:
18 |
19 | * [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle)
20 |
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # You're Going to Get Through This
2 |
3 |
4 |
5 | ## Generate a new spring native project from start.spring.io
6 |
7 | We'll need a new project from the [Spring Initializr](https://start.spring.io). Make sure to select `GraalVM Native Image` and `Lombok`.
8 |
9 | It'll generate everything and put it in the `io.spring.controllers` package. To keep things simpler, we've moved everything up one package, to `io.spring`. Delete the `controllers` package for both `src/main` and `src/test`.
10 |
11 | ## Customize the build file
12 |
13 | The Spring Initializr will get us most of the way (it does _a lot _ of code generation) but we need to add an extra dependency -- the official Java client for Kubernetes.
14 |
15 | If you're using Apache Maven, add this:
16 |
17 | ```xml
18 |
19 | io.kubernetes
20 | client-java-spring-aot-integration
21 | 17.0.0
22 |
23 | ```
24 |
25 | If you're using Gradle, add this to your dependencies:
26 |
27 | ```groovy
28 | implementation 'io.kubernetes:client-java-spring-aot-integration:17.0.0'
29 | ```
30 |
31 | ## Stage
32 |
33 | Copy `bin` and `k8s` from the source code to the new project generated from the Spring Initializr
34 |
35 |
36 |
37 | ## Deploy the CRD to Kubernetes
38 |
39 | ```shell
40 | k apply -f bin/foo.yaml
41 | ```
42 |
43 | We should be able to do
44 |
45 |
46 | ```shell
47 | k get crds
48 | ```
49 |
50 | And see the newly minted CRD. Now would also be an apropos time to show the audience the soul-annihilating-ly tedious definition of the CRD itself (`foo.yaml`). This CRD is why the Kubernetes community can't have nice things.
51 |
52 | We should also show the `test.yaml`, but don't apply it yet. This way people get the distinction between the archetypal definition of the CRD and an instance of the CRD.
53 |
54 | ## Run the Code Generator for the Image
55 |
56 | We'll need a little script to help us code-generate the Java code for our CRDs. Copy the `regen_crds.sh` script from our backup directory to the new project, in a directory called `bin`:
57 |
58 | ```shell
59 | ./bin/regen_crd.sh
60 | ```
61 |
62 | ## And then a Miracle Happens
63 |
64 | There are several main concepts we need to understand before the code we're about to write makes sense.
65 |
66 |
67 | ### Controller
68 |
69 | Kubernetes is an edge-leveled reconciling controller. Basically, it will spin up a loop that evaluates some system state and if that system state should ever drift from the operator's desired state, the controller's job is to make that state so. So, llogically, a K8s CRD is two things: a CRD definition and a controller that reacts to the lifecycle of new instances of that CRD. We've already defined the CRD itself and looked at the generated code for the CRD instance itself. We're halfway there! We just need the controller itself. That'll be our first Spring `@Bean`.
70 |
71 | ```java
72 |
73 | @Bean
74 | Controller controller(SharedInformerFactory sharedInformerFactory,
75 | SharedIndexInformer fooNodeInformer,
76 | Reconciler reconciler) {
77 | var builder = ControllerBuilder //
78 | .defaultBuilder(sharedInformerFactory)//
79 | .watch((q) -> ControllerBuilder //
80 | .controllerWatchBuilder(V1Foo.class, q)
81 | .withResyncPeriod(Duration.ofHours(1)).build() //
82 | ) //
83 | .withWorkerCount(2);
84 | return builder
85 | .withReconciler(reconciler) //
86 | .withReadyFunc(fooNodeInformer::hasSynced) // optional: only start once the index is synced
87 | .withName("fooController") ///
88 | .build();
89 |
90 | }
91 | ```
92 |
93 | Things are broken! We don't have any of the three dependencies expressed here: `SharedInformerFactory`, `Reconciler`, and `SharedIndexInformer`.
94 |
95 | ```java
96 |
97 | @Bean
98 | SharedIndexInformer fooNodeInformer(
99 | SharedInformerFactory sharedInformerFactory,
100 | GenericKubernetesApi api) {
101 | return sharedInformerFactory.sharedIndexInformerFor(api, V1Foo.class, 0);
102 | }
103 |
104 | ```
105 |
106 | This in turn implies a dependency on `GenericKubernetesApi`.
107 |
108 | ```java
109 | @Bean
110 | GenericKubernetesApi foosApi(ApiClient apiClient) {
111 | return new GenericKubernetesApi<>(V1Foo.class, V1FooList.class, "spring.io", "v1",
112 | "foos", apiClient);
113 | }
114 | ```
115 |
116 | We'll also need a `GenericKubernetesApi` for `Deployment`s, too, so let's get that out of the way now:
117 |
118 |
119 | ```java
120 | @Bean
121 | GenericKubernetesApi deploymentsApi(ApiClient apiClient) {
122 | return new GenericKubernetesApi<>(V1Deployment.class, V1DeploymentList.class,
123 | "", "v1", "deployments",
124 | apiClient);
125 | }
126 |
127 | ```
128 |
129 | They're identical, except for their generic parameters and the string definitions of the group, and pluralized form of their nouns. These API clients let us talk to the API server about a given type of CRD, in this case `Foo` and `Deployment`, respectively.
130 |
131 | We need the `SharedIndexInformer`, too.
132 |
133 | What's an `Informer`, you ask? An informer "is a" - and we're not making this iup - controller together with the ability to distribute its `Queue`-related operations to an appropriate event handler. There are `SharedInformer`s that share data across multiple instances of the `Informer` so that they're not duplicated. A `SharedInformer` has a shared data cache and is capable of distributing notifications for changes to the cache to multiple listeners who registered with it. There is one behavior change compared to a standard `Informer`: when you receive a notification, the cache will be _at least_ as fresh as the notification, but it _may_ be more fresh. You should not depend on the contents of the cache exactly matching the state implied by the notification. The notification is binding. `SharedIndexInformer` only adds one more thing to the picture: the ability to lookup items by various keys. So, a controller sometimes needs a conceptually-a-controller to be a controller. Got it? Got it.
134 |
135 | Next, we'll need to define the `Reconciler` itself:
136 |
137 | ```java
138 | @Bean
139 | Reconciler reconciler(
140 | @Value("classpath:/deployment.yaml") Resource resourceForDeploymentYaml,
141 | AppsV1Api coreV1Api,
142 | SharedIndexInformer fooNodeInformer,
143 | GenericKubernetesApi deploymentApi) {
144 | return new FooReconciler(coreV1Api, resourceForDeploymentYaml, fooNodeInformer, deploymentApi);
145 | }
146 | ```
147 |
148 | Here's where the rubber meets the road: our reconciler will create a new `Deployment` every time a new `Foo` is created. We like you too much to programmatically build up the `Deployment` from scratch in Java, so we'll just reuse a pre-written YAML definition (`/deployment.yaml`) of a `Deployment` and then reify it, changing some of its parameters, and submit that.
149 |
150 | We'll also need references fo the `GenericKubernetesAPi` for `Deployments` and a new thing, called the `AppsV1Api`. This API sidesteps all the caching and indexing and allows us to talk directly to the API server. You could achieve this without using the API, but it simplifies things sometimes and it's instructional to see it in action, so:
151 |
152 | ```java
153 | @Bean
154 | AppsV1Api appsV1Api(ApiClient apiClient) {
155 | return new AppsV1Api(apiClient);
156 | }
157 | ```
158 |
159 | ## Deploy an Instance of the `foo` Object
160 |
161 | ```shell
162 | k apply -f bin/test.yaml
163 | ```
164 |
165 | ## Run the Program
166 |
167 | If you're using Apache Maven: `./mvnw spring-boot:run`
168 |
169 | If you're using Gradle: `./gradlew bootRun`
170 |
171 |
172 | ## Compile a GraalVM Native Image
173 |
174 |
175 | If you're using Apache Maven: `./mvnw -Pnative native:compile`
176 |
177 | If you're using Gradle: `./gradlew nativeCompile`
178 |
179 |
180 | ## Resources
181 | - [we found the following post supremely useful for navigating this nightmare world](https://lairdnelson.wordpress.com/2018/01/07/understanding-kubernetes-tools-cache-package-part-3/)
182 | - [Generating models from CRD YAML definitions for fun and profit](https://github.com/kubernetes-client/java/blob/master/docs/generate-model-from-third-party-resources.md)
183 |
184 |
--------------------------------------------------------------------------------
/bin/foo.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apiextensions.k8s.io/v1
2 | kind: CustomResourceDefinition
3 | metadata:
4 | name: foos.spring.io
5 | spec:
6 | preserveUnknownFields: false
7 | group: spring.io
8 | names:
9 | kind: Foo
10 | listKind: FooList
11 | plural: foos
12 | singular: foo
13 | scope: Namespaced
14 | versions:
15 | - name: v1
16 | served: true
17 | storage: true
18 | # additionalPrinterColumns:
19 | # - jsonPath: .spec.name
20 | # description: name
21 | # name: Foo
22 | # type: string
23 | subresources:
24 | status: {}
25 | schema:
26 | openAPIV3Schema:
27 | description: Foo is the Schema for the foo API
28 | properties:
29 | apiVersion:
30 | description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
31 | type: string
32 | kind:
33 | description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
34 | type: string
35 | metadata:
36 | type: object
37 | spec:
38 | description: FooSpec defines the desired state of Foo
39 | properties:
40 | name:
41 | description: Name your foo, fool!
42 | type: string
43 | type: object
44 | status:
45 | description: FooStatus defines the observed state of Foo
46 | properties:
47 | name:
48 | type: string
49 | type: object
50 | type: object
51 | status:
52 | acceptedNames:
53 | kind: ""
54 | plural: ""
55 | conditions: []
56 | storedVersions: []
--------------------------------------------------------------------------------
/bin/regen_crds.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # the value u pass to -n and -p (io.spring) HAS to match the value
4 | # in the crd definition itself, but reversed. So, in the foo.yaml we
5 | # have `group: spring.io`, and this Java package becomes io.spring
6 | CURRENT_DIR=$(cd `dirname $0` && pwd)
7 | LOCAL_MANIFEST_FILE=${CURRENT_DIR}/foo.yaml
8 | echo "CURRENT_DIR=${CURRENT_DIR}"
9 | echo "LOCAL_MANIFEST_FILE=${LOCAL_MANIFEST_FILE}"
10 | mkdir -p /tmp/java && cd /tmp/java
11 | docker run \
12 | --rm \
13 | -v "$LOCAL_MANIFEST_FILE":"$LOCAL_MANIFEST_FILE" \
14 | -v /var/run/docker.sock:/var/run/docker.sock \
15 | -v "$(pwd)":"$(pwd)" \
16 | -ti \
17 | --network host \
18 | ghcr.io/kubernetes-client/java/crd-model-gen:v1.0.6 \
19 | /generate.sh \
20 | -u $LOCAL_MANIFEST_FILE \
21 | -n io.spring \
22 | -p io.spring \
23 | -o "$(pwd)"
24 | cp -r /tmp/java/src/main/java/io/spring/models ${CURRENT_DIR}/../src/main/java/io/spring/
25 |
--------------------------------------------------------------------------------
/bin/test.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: spring.io/v1
2 | kind: Foo
3 | metadata:
4 | name: demo2
5 | spec:
6 | name: SpringOne Tour 1
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | id 'org.springframework.boot' version '3.0.4'
4 | id 'io.spring.dependency-management' version '1.1.0'
5 | id 'org.graalvm.buildtools.native' version '0.9.20'
6 | }
7 |
8 | group = 'io.spring'
9 | version = '0.0.1-SNAPSHOT'
10 | sourceCompatibility = '17'
11 |
12 | configurations {
13 | compileOnly {
14 | extendsFrom annotationProcessor
15 | }
16 | }
17 |
18 | repositories {
19 | mavenCentral()
20 | }
21 |
22 | dependencies {
23 | compileOnly 'org.projectlombok:lombok'
24 | annotationProcessor 'org.projectlombok:lombok'
25 | implementation 'io.kubernetes:client-java-spring-aot-integration:17.0.0'
26 | implementation 'org.springframework.boot:spring-boot-starter'
27 | testImplementation 'org.springframework.boot:spring-boot-starter-test'
28 | }
29 |
30 | tasks.named('test') {
31 | useJUnitPlatform()
32 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kubernetes-native-java/controllers-101/237953b529104eae847902d82b96eb29a4132579/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
84 |
85 | APP_NAME="Gradle"
86 | APP_BASE_NAME=${0##*/}
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | MAX_FD=$( ulimit -H -n ) ||
147 | warn "Could not query maximum file descriptor limit"
148 | esac
149 | case $MAX_FD in #(
150 | '' | soft) :;; #(
151 | *)
152 | ulimit -n "$MAX_FD" ||
153 | warn "Could not set maximum file descriptor limit to $MAX_FD"
154 | esac
155 | fi
156 |
157 | # Collect all arguments for the java command, stacking in reverse order:
158 | # * args from the command line
159 | # * the main class name
160 | # * -classpath
161 | # * -D...appname settings
162 | # * --module-path (only if needed)
163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
164 |
165 | # For Cygwin or MSYS, switch paths to Windows format before running java
166 | if "$cygwin" || "$msys" ; then
167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
169 |
170 | JAVACMD=$( cygpath --unix "$JAVACMD" )
171 |
172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
173 | for arg do
174 | if
175 | case $arg in #(
176 | -*) false ;; # don't mess with options #(
177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
178 | [ -e "$t" ] ;; #(
179 | *) false ;;
180 | esac
181 | then
182 | arg=$( cygpath --path --ignore --mixed "$arg" )
183 | fi
184 | # Roll the args list around exactly as many times as the number of
185 | # args, so each arg winds up back in the position where it started, but
186 | # possibly modified.
187 | #
188 | # NB: a `for` loop captures its iteration list before it begins, so
189 | # changing the positional parameters here affects neither the number of
190 | # iterations, nor the values presented in `arg`.
191 | shift # remove old arg
192 | set -- "$@" "$arg" # push replacement arg
193 | done
194 | fi
195 |
196 | # Collect all arguments for the java command;
197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
198 | # shell script including quotes and variable substitutions, so put them in
199 | # double quotes to make sure that they get re-expanded; and
200 | # * put everything else in single quotes, so that it's not re-expanded.
201 |
202 | set -- \
203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
204 | -classpath "$CLASSPATH" \
205 | org.gradle.wrapper.GradleWrapperMain \
206 | "$@"
207 |
208 | # Stop when "xargs" is not available.
209 | if ! command -v xargs >/dev/null 2>&1
210 | then
211 | die "xargs is not available"
212 | fi
213 |
214 | # Use "xargs" to parse quoted args.
215 | #
216 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
217 | #
218 | # In Bash we could simply go:
219 | #
220 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
221 | # set -- "${ARGS[@]}" "$@"
222 | #
223 | # but POSIX shell has neither arrays nor command substitution, so instead we
224 | # post-process each arg (as a line of input to sed) to backslash-escape any
225 | # character that might be a shell metacharacter, then use eval to reverse
226 | # that process (while maintaining the separation between arguments), and wrap
227 | # the whole thing up as a single "set" statement.
228 | #
229 | # This will of course break if any of these variables contains a newline or
230 | # an unmatched quote.
231 | #
232 |
233 | eval "set -- $(
234 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
235 | xargs -n1 |
236 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
237 | tr '\n' ' '
238 | )" '"$@"'
239 |
240 | exec "$JAVACMD" "$@"
241 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if %ERRORLEVEL% equ 0 goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if %ERRORLEVEL% equ 0 goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | set EXIT_CODE=%ERRORLEVEL%
84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
86 | exit /b %EXIT_CODE%
87 |
88 | :mainEnd
89 | if "%OS%"=="Windows_NT" endlocal
90 |
91 | :omega
92 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'controllers'
2 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/ControllersApplication.java:
--------------------------------------------------------------------------------
1 | package io.spring;
2 |
3 | import io.spring.models.V1Foo;
4 | import io.spring.models.V1FooList;
5 | import org.springframework.boot.SpringApplication;
6 | import org.springframework.boot.autoconfigure.SpringBootApplication;
7 | import io.kubernetes.client.common.KubernetesObject;
8 | import io.kubernetes.client.extended.controller.Controller;
9 | import io.kubernetes.client.extended.controller.builder.ControllerBuilder;
10 | import io.kubernetes.client.extended.controller.builder.DefaultControllerBuilder;
11 | import io.kubernetes.client.extended.controller.reconciler.Reconciler;
12 | import io.kubernetes.client.extended.controller.reconciler.Result;
13 | import io.kubernetes.client.informer.SharedIndexInformer;
14 | import io.kubernetes.client.informer.SharedInformerFactory;
15 | import io.kubernetes.client.openapi.ApiClient;
16 | import io.kubernetes.client.openapi.ApiException;
17 | import io.kubernetes.client.openapi.apis.AppsV1Api;
18 | import io.kubernetes.client.openapi.apis.CoreV1Api;
19 | import io.kubernetes.client.openapi.models.*;
20 | import io.kubernetes.client.util.Yaml;
21 | import io.kubernetes.client.util.generic.GenericKubernetesApi;
22 | import lombok.SneakyThrows;
23 | import lombok.extern.slf4j.Slf4j;
24 | import org.springframework.aot.hint.RuntimeHints;
25 | import org.springframework.aot.hint.RuntimeHintsRegistrar;
26 | import org.springframework.beans.factory.annotation.Value;
27 | import org.springframework.boot.ApplicationRunner;
28 | import org.springframework.context.annotation.Bean;
29 | import org.springframework.context.annotation.ImportRuntimeHints;
30 | import org.springframework.core.io.ClassPathResource;
31 | import org.springframework.core.io.Resource;
32 | import org.springframework.util.Assert;
33 | import org.springframework.util.FileCopyUtils;
34 |
35 | import java.io.InputStreamReader;
36 | import java.time.Duration;
37 | import java.time.Instant;
38 | import java.util.List;
39 | import java.util.Map;
40 | import java.util.Objects;
41 | import java.util.concurrent.Executors;
42 |
43 |
44 | @Slf4j
45 | @ImportRuntimeHints(ControllersApplication.FooControllerRuntimeHints.class)
46 | @SpringBootApplication
47 | public class ControllersApplication {
48 |
49 | public static void main(String[] args) {
50 | SpringApplication.run(ControllersApplication.class, args);
51 | }
52 |
53 | static class FooControllerRuntimeHints implements RuntimeHintsRegistrar {
54 |
55 | @Override
56 | public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
57 | for (var path : new String[] { "/configmap.yaml", "/deployment.yaml" }) {
58 | hints.resources().registerResource(new ClassPathResource(path));
59 | }
60 | }
61 |
62 | }
63 |
64 | @Bean
65 | GenericKubernetesApi foosApi(ApiClient apiClient) {
66 | return new GenericKubernetesApi<>(V1Foo.class, V1FooList.class, "spring.io", "v1", "foos", apiClient);
67 | }
68 |
69 | @Bean
70 | SharedIndexInformer foosSharedIndexInformer(SharedInformerFactory sharedInformerFactory,
71 | GenericKubernetesApi api) {
72 | return sharedInformerFactory.sharedIndexInformerFor(api, V1Foo.class, 0);
73 | }
74 |
75 | @Bean
76 | AppsV1Api appsV1Api(ApiClient apiClient) {
77 | return new AppsV1Api(apiClient);
78 | }
79 |
80 | @Bean
81 | CoreV1Api coreV1Api(ApiClient apiClient) {
82 | return new CoreV1Api(apiClient);
83 | }
84 |
85 | @Bean(destroyMethod = "shutdown")
86 | Controller fooController(SharedInformerFactory sharedInformerFactory, SharedIndexInformer fooNodeInformer,
87 | Reconciler reconciler) {
88 |
89 | DefaultControllerBuilder builder = ControllerBuilder //
90 | .defaultBuilder(sharedInformerFactory)//
91 | .watch(fooQ -> ControllerBuilder //
92 | .controllerWatchBuilder(V1Foo.class, fooQ)//
93 | .withResyncPeriod(Duration.ofSeconds(1))//
94 | .build()) //
95 | .withWorkerCount(2);
96 | return builder//
97 | .withReconciler(reconciler) //
98 | .withReadyFunc(fooNodeInformer::hasSynced) // optional: only start once
99 | // the index is synced
100 | .withName("fooController") ///
101 | .build();
102 |
103 | }
104 |
105 | @Bean
106 | ApplicationRunner runner(SharedInformerFactory sharedInformerFactory, Controller controller) {
107 | var executorService = Executors.newCachedThreadPool();
108 | return args -> executorService.execute(() -> {
109 | sharedInformerFactory.startAllRegisteredInformers();
110 | controller.run();
111 | });
112 | }
113 |
114 | @FunctionalInterface
115 | interface ApiSupplier {
116 |
117 | T get() throws ApiException;
118 |
119 | }
120 |
121 | /**
122 | * the Reconciler won't get an event telling it that the cluster has changed, but
123 | * instead it looks at cluster state and determines that something has changed
124 | */
125 | @Bean
126 | Reconciler reconciler(@Value("classpath:configmap.yaml") Resource configMapYaml,
127 | @Value("classpath:deployment.yaml") Resource deploymentYaml,
128 | SharedIndexInformer v1FooSharedIndexInformer, AppsV1Api appsV1Api, CoreV1Api coreV1Api) {
129 | return request -> {
130 | try {
131 | // create new one on k apply -f foo.yaml
132 | String requestName = request.getName();
133 | String key = request.getNamespace() + '/' + requestName;
134 | V1Foo foo = v1FooSharedIndexInformer.getIndexer().getByKey(key);
135 | if (foo == null) { // deleted. we use ownerreferences so dont need to do
136 | // anything special here
137 | return new Result(false);
138 | }
139 |
140 | String namespace = foo.getMetadata().getNamespace();
141 | String pretty = "true";
142 | String dryRun = null;
143 | String fieldManager = "";
144 | String fieldValidation = "";
145 |
146 | // parameterize configmap
147 | String configMapName = "configmap-" + requestName;
148 | V1ConfigMap configMap = loadYamlAs(configMapYaml, V1ConfigMap.class);
149 | String html = " Hello, " + foo.getSpec().getName() + "
";
150 | configMap.getData().put("index.html", html);
151 | configMap.getMetadata().setName(configMapName);
152 | createOrUpdate(V1ConfigMap.class, () -> {
153 | addOwnerReference(requestName, foo, configMap);
154 | return coreV1Api.createNamespacedConfigMap(namespace, configMap, pretty, dryRun, fieldManager,
155 | fieldValidation);
156 | }, () -> coreV1Api.replaceNamespacedConfigMap(configMapName, namespace, configMap,
157 | pretty, dryRun, fieldManager, fieldValidation));
158 |
159 | // parameterize deployment
160 | String deploymentName = "deployment-" + requestName;
161 | V1Deployment deployment = loadYamlAs(deploymentYaml, V1Deployment.class);
162 | deployment.getMetadata().setName(deploymentName);
163 | List volumes = deployment.getSpec().getTemplate().getSpec().getVolumes();
164 | Assert.isTrue(volumes.size() == 1, () -> "there should be only one V1Volume");
165 | volumes.forEach(vol -> vol.getConfigMap().setName(configMapName));
166 | createOrUpdate(V1Deployment.class, () -> {
167 | deployment.getSpec().getTemplate().getMetadata()
168 | .setAnnotations(Map.of("bootiful-update", Instant.now().toString()));
169 | addOwnerReference(requestName, foo, deployment);
170 | return appsV1Api.createNamespacedDeployment(namespace, deployment, pretty, dryRun, fieldManager,
171 | fieldValidation);
172 | }, () -> {
173 | updateAnnotation(deployment);
174 | return appsV1Api.replaceNamespacedDeployment(deploymentName, namespace, deployment, pretty, dryRun,
175 | fieldManager, fieldValidation);
176 | });
177 | } //
178 | catch (Throwable e) {
179 | log.error("we've got an outer error.", e);
180 | return new Result(true, Duration.ofSeconds(60));
181 | }
182 | return new Result(false);
183 | };
184 | }
185 |
186 | private void updateAnnotation(V1Deployment deployment) {
187 | Objects.requireNonNull(Objects.requireNonNull(deployment.getSpec()).getTemplate().getMetadata())
188 | .setAnnotations(Map.of("bootiful-update", Instant.now().toString()));
189 | }
190 |
191 | static private void createOrUpdate(Class clazz, ApiSupplier creator, ApiSupplier updater) {
192 | try {
193 | creator.get();
194 | log.info("It worked! we created a new " + clazz.getName() + "!");
195 | } //
196 | catch (ApiException throwable) {
197 | int code = throwable.getCode();
198 | if (code == 409) { // already exists
199 | log.info("the " + clazz.getName() + " already exists. Replacing.");
200 | try {
201 | updater.get();
202 | log.info("successfully updated the " + clazz.getName());
203 | }
204 | catch (ApiException ex) {
205 | log.error("got an error on update", ex);
206 | }
207 | } //
208 | else {
209 | log.info("got an exception with code " + code + " while trying to create the " + clazz.getName());
210 | }
211 | }
212 | }
213 |
214 | private static V1ObjectMeta addOwnerReference(String requestName, V1Foo foo, KubernetesObject kubernetesObject) {
215 | Assert.notNull(foo, () -> "the V1Foo must not be null");
216 | return kubernetesObject.getMetadata().addOwnerReferencesItem(new V1OwnerReference().kind(foo.getKind())
217 | .apiVersion(foo.getApiVersion()).controller(true).uid(foo.getMetadata().getUid()).name(requestName));
218 | }
219 |
220 | @SneakyThrows
221 | private static T loadYamlAs(Resource resource, Class clzz) {
222 | var yaml = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
223 | return Yaml.loadAs(yaml, clzz);
224 | }
225 |
226 | }
--------------------------------------------------------------------------------
/src/main/java/io/spring/models/V1Foo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kubernetes
3 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
4 | *
5 | * The version of the OpenAPI document: v1.21.1
6 | *
7 | *
8 | * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
9 | * https://openapi-generator.tech
10 | * Do not edit the class manually.
11 | */
12 |
13 |
14 | package io.spring.models;
15 |
16 | import java.util.Objects;
17 | import java.util.Arrays;
18 | import com.google.gson.TypeAdapter;
19 | import com.google.gson.annotations.JsonAdapter;
20 | import com.google.gson.annotations.SerializedName;
21 | import com.google.gson.stream.JsonReader;
22 | import com.google.gson.stream.JsonWriter;
23 | import io.kubernetes.client.openapi.models.V1ObjectMeta;
24 | import io.spring.models.V1FooSpec;
25 | import io.spring.models.V1FooStatus;
26 | import io.swagger.annotations.ApiModel;
27 | import io.swagger.annotations.ApiModelProperty;
28 | import java.io.IOException;
29 |
30 | /**
31 | * Foo is the Schema for the foo API
32 | */
33 | @ApiModel(description = "Foo is the Schema for the foo API")
34 | @javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2023-03-14T03:40:36.644Z[Etc/UTC]")
35 | public class V1Foo implements io.kubernetes.client.common.KubernetesObject {
36 | public static final String SERIALIZED_NAME_API_VERSION = "apiVersion";
37 | @SerializedName(SERIALIZED_NAME_API_VERSION)
38 | private String apiVersion;
39 |
40 | public static final String SERIALIZED_NAME_KIND = "kind";
41 | @SerializedName(SERIALIZED_NAME_KIND)
42 | private String kind;
43 |
44 | public static final String SERIALIZED_NAME_METADATA = "metadata";
45 | @SerializedName(SERIALIZED_NAME_METADATA)
46 | private V1ObjectMeta metadata = null;
47 |
48 | public static final String SERIALIZED_NAME_SPEC = "spec";
49 | @SerializedName(SERIALIZED_NAME_SPEC)
50 | private V1FooSpec spec;
51 |
52 | public static final String SERIALIZED_NAME_STATUS = "status";
53 | @SerializedName(SERIALIZED_NAME_STATUS)
54 | private V1FooStatus status;
55 |
56 |
57 | public V1Foo apiVersion(String apiVersion) {
58 |
59 | this.apiVersion = apiVersion;
60 | return this;
61 | }
62 |
63 | /**
64 | * APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
65 | * @return apiVersion
66 | **/
67 | @javax.annotation.Nullable
68 | @ApiModelProperty(value = "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources")
69 |
70 | public String getApiVersion() {
71 | return apiVersion;
72 | }
73 |
74 |
75 | public void setApiVersion(String apiVersion) {
76 | this.apiVersion = apiVersion;
77 | }
78 |
79 |
80 | public V1Foo kind(String kind) {
81 |
82 | this.kind = kind;
83 | return this;
84 | }
85 |
86 | /**
87 | * Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
88 | * @return kind
89 | **/
90 | @javax.annotation.Nullable
91 | @ApiModelProperty(value = "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds")
92 |
93 | public String getKind() {
94 | return kind;
95 | }
96 |
97 |
98 | public void setKind(String kind) {
99 | this.kind = kind;
100 | }
101 |
102 |
103 | public V1Foo metadata(V1ObjectMeta metadata) {
104 |
105 | this.metadata = metadata;
106 | return this;
107 | }
108 |
109 | /**
110 | * Get metadata
111 | * @return metadata
112 | **/
113 | @javax.annotation.Nullable
114 | @ApiModelProperty(value = "")
115 |
116 | public V1ObjectMeta getMetadata() {
117 | return metadata;
118 | }
119 |
120 |
121 | public void setMetadata(V1ObjectMeta metadata) {
122 | this.metadata = metadata;
123 | }
124 |
125 |
126 | public V1Foo spec(V1FooSpec spec) {
127 |
128 | this.spec = spec;
129 | return this;
130 | }
131 |
132 | /**
133 | * Get spec
134 | * @return spec
135 | **/
136 | @javax.annotation.Nullable
137 | @ApiModelProperty(value = "")
138 |
139 | public V1FooSpec getSpec() {
140 | return spec;
141 | }
142 |
143 |
144 | public void setSpec(V1FooSpec spec) {
145 | this.spec = spec;
146 | }
147 |
148 |
149 | public V1Foo status(V1FooStatus status) {
150 |
151 | this.status = status;
152 | return this;
153 | }
154 |
155 | /**
156 | * Get status
157 | * @return status
158 | **/
159 | @javax.annotation.Nullable
160 | @ApiModelProperty(value = "")
161 |
162 | public V1FooStatus getStatus() {
163 | return status;
164 | }
165 |
166 |
167 | public void setStatus(V1FooStatus status) {
168 | this.status = status;
169 | }
170 |
171 |
172 | @Override
173 | public boolean equals(Object o) {
174 | if (this == o) {
175 | return true;
176 | }
177 | if (o == null || getClass() != o.getClass()) {
178 | return false;
179 | }
180 | V1Foo v1Foo = (V1Foo) o;
181 | return Objects.equals(this.apiVersion, v1Foo.apiVersion) &&
182 | Objects.equals(this.kind, v1Foo.kind) &&
183 | Objects.equals(this.metadata, v1Foo.metadata) &&
184 | Objects.equals(this.spec, v1Foo.spec) &&
185 | Objects.equals(this.status, v1Foo.status);
186 | }
187 |
188 | @Override
189 | public int hashCode() {
190 | return Objects.hash(apiVersion, kind, metadata, spec, status);
191 | }
192 |
193 |
194 | @Override
195 | public String toString() {
196 | StringBuilder sb = new StringBuilder();
197 | sb.append("class V1Foo {\n");
198 | sb.append(" apiVersion: ").append(toIndentedString(apiVersion)).append("\n");
199 | sb.append(" kind: ").append(toIndentedString(kind)).append("\n");
200 | sb.append(" metadata: ").append(toIndentedString(metadata)).append("\n");
201 | sb.append(" spec: ").append(toIndentedString(spec)).append("\n");
202 | sb.append(" status: ").append(toIndentedString(status)).append("\n");
203 | sb.append("}");
204 | return sb.toString();
205 | }
206 |
207 | /**
208 | * Convert the given object to string with each line indented by 4 spaces
209 | * (except the first line).
210 | */
211 | private String toIndentedString(Object o) {
212 | if (o == null) {
213 | return "null";
214 | }
215 | return o.toString().replace("\n", "\n ");
216 | }
217 |
218 | }
219 |
220 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/models/V1FooList.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kubernetes
3 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
4 | *
5 | * The version of the OpenAPI document: v1.21.1
6 | *
7 | *
8 | * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
9 | * https://openapi-generator.tech
10 | * Do not edit the class manually.
11 | */
12 |
13 |
14 | package io.spring.models;
15 |
16 | import java.util.Objects;
17 | import java.util.Arrays;
18 | import com.google.gson.TypeAdapter;
19 | import com.google.gson.annotations.JsonAdapter;
20 | import com.google.gson.annotations.SerializedName;
21 | import com.google.gson.stream.JsonReader;
22 | import com.google.gson.stream.JsonWriter;
23 | import io.kubernetes.client.openapi.models.V1ListMeta;
24 | import io.spring.models.V1Foo;
25 | import io.swagger.annotations.ApiModel;
26 | import io.swagger.annotations.ApiModelProperty;
27 | import java.io.IOException;
28 | import java.util.ArrayList;
29 | import java.util.List;
30 |
31 | /**
32 | * FooList is a list of Foo
33 | */
34 | @ApiModel(description = "FooList is a list of Foo")
35 | @javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2023-03-14T03:40:36.644Z[Etc/UTC]")
36 | public class V1FooList implements io.kubernetes.client.common.KubernetesListObject {
37 | public static final String SERIALIZED_NAME_API_VERSION = "apiVersion";
38 | @SerializedName(SERIALIZED_NAME_API_VERSION)
39 | private String apiVersion;
40 |
41 | public static final String SERIALIZED_NAME_ITEMS = "items";
42 | @SerializedName(SERIALIZED_NAME_ITEMS)
43 | private List items = new ArrayList<>();
44 |
45 | public static final String SERIALIZED_NAME_KIND = "kind";
46 | @SerializedName(SERIALIZED_NAME_KIND)
47 | private String kind;
48 |
49 | public static final String SERIALIZED_NAME_METADATA = "metadata";
50 | @SerializedName(SERIALIZED_NAME_METADATA)
51 | private V1ListMeta metadata = null;
52 |
53 |
54 | public V1FooList apiVersion(String apiVersion) {
55 |
56 | this.apiVersion = apiVersion;
57 | return this;
58 | }
59 |
60 | /**
61 | * APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
62 | * @return apiVersion
63 | **/
64 | @javax.annotation.Nullable
65 | @ApiModelProperty(value = "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources")
66 |
67 | public String getApiVersion() {
68 | return apiVersion;
69 | }
70 |
71 |
72 | public void setApiVersion(String apiVersion) {
73 | this.apiVersion = apiVersion;
74 | }
75 |
76 |
77 | public V1FooList items(List items) {
78 |
79 | this.items = items;
80 | return this;
81 | }
82 |
83 | public V1FooList addItemsItem(V1Foo itemsItem) {
84 | this.items.add(itemsItem);
85 | return this;
86 | }
87 |
88 | /**
89 | * List of foos. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md
90 | * @return items
91 | **/
92 | @ApiModelProperty(required = true, value = "List of foos. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md")
93 |
94 | public List getItems() {
95 | return items;
96 | }
97 |
98 |
99 | public void setItems(List items) {
100 | this.items = items;
101 | }
102 |
103 |
104 | public V1FooList kind(String kind) {
105 |
106 | this.kind = kind;
107 | return this;
108 | }
109 |
110 | /**
111 | * Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
112 | * @return kind
113 | **/
114 | @javax.annotation.Nullable
115 | @ApiModelProperty(value = "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds")
116 |
117 | public String getKind() {
118 | return kind;
119 | }
120 |
121 |
122 | public void setKind(String kind) {
123 | this.kind = kind;
124 | }
125 |
126 |
127 | public V1FooList metadata(V1ListMeta metadata) {
128 |
129 | this.metadata = metadata;
130 | return this;
131 | }
132 |
133 | /**
134 | * Get metadata
135 | * @return metadata
136 | **/
137 | @javax.annotation.Nullable
138 | @ApiModelProperty(value = "")
139 |
140 | public V1ListMeta getMetadata() {
141 | return metadata;
142 | }
143 |
144 |
145 | public void setMetadata(V1ListMeta metadata) {
146 | this.metadata = metadata;
147 | }
148 |
149 |
150 | @Override
151 | public boolean equals(Object o) {
152 | if (this == o) {
153 | return true;
154 | }
155 | if (o == null || getClass() != o.getClass()) {
156 | return false;
157 | }
158 | V1FooList v1FooList = (V1FooList) o;
159 | return Objects.equals(this.apiVersion, v1FooList.apiVersion) &&
160 | Objects.equals(this.items, v1FooList.items) &&
161 | Objects.equals(this.kind, v1FooList.kind) &&
162 | Objects.equals(this.metadata, v1FooList.metadata);
163 | }
164 |
165 | @Override
166 | public int hashCode() {
167 | return Objects.hash(apiVersion, items, kind, metadata);
168 | }
169 |
170 |
171 | @Override
172 | public String toString() {
173 | StringBuilder sb = new StringBuilder();
174 | sb.append("class V1FooList {\n");
175 | sb.append(" apiVersion: ").append(toIndentedString(apiVersion)).append("\n");
176 | sb.append(" items: ").append(toIndentedString(items)).append("\n");
177 | sb.append(" kind: ").append(toIndentedString(kind)).append("\n");
178 | sb.append(" metadata: ").append(toIndentedString(metadata)).append("\n");
179 | sb.append("}");
180 | return sb.toString();
181 | }
182 |
183 | /**
184 | * Convert the given object to string with each line indented by 4 spaces
185 | * (except the first line).
186 | */
187 | private String toIndentedString(Object o) {
188 | if (o == null) {
189 | return "null";
190 | }
191 | return o.toString().replace("\n", "\n ");
192 | }
193 |
194 | }
195 |
196 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/models/V1FooSpec.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kubernetes
3 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
4 | *
5 | * The version of the OpenAPI document: v1.21.1
6 | *
7 | *
8 | * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
9 | * https://openapi-generator.tech
10 | * Do not edit the class manually.
11 | */
12 |
13 |
14 | package io.spring.models;
15 |
16 | import java.util.Objects;
17 | import java.util.Arrays;
18 | import com.google.gson.TypeAdapter;
19 | import com.google.gson.annotations.JsonAdapter;
20 | import com.google.gson.annotations.SerializedName;
21 | import com.google.gson.stream.JsonReader;
22 | import com.google.gson.stream.JsonWriter;
23 | import io.swagger.annotations.ApiModel;
24 | import io.swagger.annotations.ApiModelProperty;
25 | import java.io.IOException;
26 |
27 | /**
28 | * FooSpec defines the desired state of Foo
29 | */
30 | @ApiModel(description = "FooSpec defines the desired state of Foo")
31 | @javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2023-03-14T03:40:36.644Z[Etc/UTC]")
32 | public class V1FooSpec {
33 | public static final String SERIALIZED_NAME_NAME = "name";
34 | @SerializedName(SERIALIZED_NAME_NAME)
35 | private String name;
36 |
37 |
38 | public V1FooSpec name(String name) {
39 |
40 | this.name = name;
41 | return this;
42 | }
43 |
44 | /**
45 | * Name your foo, fool!
46 | * @return name
47 | **/
48 | @javax.annotation.Nullable
49 | @ApiModelProperty(value = "Name your foo, fool!")
50 |
51 | public String getName() {
52 | return name;
53 | }
54 |
55 |
56 | public void setName(String name) {
57 | this.name = name;
58 | }
59 |
60 |
61 | @Override
62 | public boolean equals(Object o) {
63 | if (this == o) {
64 | return true;
65 | }
66 | if (o == null || getClass() != o.getClass()) {
67 | return false;
68 | }
69 | V1FooSpec v1FooSpec = (V1FooSpec) o;
70 | return Objects.equals(this.name, v1FooSpec.name);
71 | }
72 |
73 | @Override
74 | public int hashCode() {
75 | return Objects.hash(name);
76 | }
77 |
78 |
79 | @Override
80 | public String toString() {
81 | StringBuilder sb = new StringBuilder();
82 | sb.append("class V1FooSpec {\n");
83 | sb.append(" name: ").append(toIndentedString(name)).append("\n");
84 | sb.append("}");
85 | return sb.toString();
86 | }
87 |
88 | /**
89 | * Convert the given object to string with each line indented by 4 spaces
90 | * (except the first line).
91 | */
92 | private String toIndentedString(Object o) {
93 | if (o == null) {
94 | return "null";
95 | }
96 | return o.toString().replace("\n", "\n ");
97 | }
98 |
99 | }
100 |
101 |
--------------------------------------------------------------------------------
/src/main/java/io/spring/models/V1FooStatus.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Kubernetes
3 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
4 | *
5 | * The version of the OpenAPI document: v1.21.1
6 | *
7 | *
8 | * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
9 | * https://openapi-generator.tech
10 | * Do not edit the class manually.
11 | */
12 |
13 |
14 | package io.spring.models;
15 |
16 | import java.util.Objects;
17 | import java.util.Arrays;
18 | import com.google.gson.TypeAdapter;
19 | import com.google.gson.annotations.JsonAdapter;
20 | import com.google.gson.annotations.SerializedName;
21 | import com.google.gson.stream.JsonReader;
22 | import com.google.gson.stream.JsonWriter;
23 | import io.swagger.annotations.ApiModel;
24 | import io.swagger.annotations.ApiModelProperty;
25 | import java.io.IOException;
26 |
27 | /**
28 | * FooStatus defines the observed state of Foo
29 | */
30 | @ApiModel(description = "FooStatus defines the observed state of Foo")
31 | @javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2023-03-14T03:40:36.644Z[Etc/UTC]")
32 | public class V1FooStatus {
33 | public static final String SERIALIZED_NAME_NAME = "name";
34 | @SerializedName(SERIALIZED_NAME_NAME)
35 | private String name;
36 |
37 |
38 | public V1FooStatus name(String name) {
39 |
40 | this.name = name;
41 | return this;
42 | }
43 |
44 | /**
45 | * Get name
46 | * @return name
47 | **/
48 | @javax.annotation.Nullable
49 | @ApiModelProperty(value = "")
50 |
51 | public String getName() {
52 | return name;
53 | }
54 |
55 |
56 | public void setName(String name) {
57 | this.name = name;
58 | }
59 |
60 |
61 | @Override
62 | public boolean equals(Object o) {
63 | if (this == o) {
64 | return true;
65 | }
66 | if (o == null || getClass() != o.getClass()) {
67 | return false;
68 | }
69 | V1FooStatus v1FooStatus = (V1FooStatus) o;
70 | return Objects.equals(this.name, v1FooStatus.name);
71 | }
72 |
73 | @Override
74 | public int hashCode() {
75 | return Objects.hash(name);
76 | }
77 |
78 |
79 | @Override
80 | public String toString() {
81 | StringBuilder sb = new StringBuilder();
82 | sb.append("class V1FooStatus {\n");
83 | sb.append(" name: ").append(toIndentedString(name)).append("\n");
84 | sb.append("}");
85 | return sb.toString();
86 | }
87 |
88 | /**
89 | * Convert the given object to string with each line indented by 4 spaces
90 | * (except the first line).
91 | */
92 | private String toIndentedString(Object o) {
93 | if (o == null) {
94 | return "null";
95 | }
96 | return o.toString().replace("\n", "\n ");
97 | }
98 |
99 | }
100 |
101 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/main/resources/configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: index-html-configmap
5 | namespace: default
6 | data:
7 | index.html: |
8 | Hello, world!
--------------------------------------------------------------------------------
/src/main/resources/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: nginx-deployment
5 | namespace: default
6 | spec:
7 | selector:
8 | matchLabels:
9 | app: nginx
10 | replicas: 2
11 | template:
12 | metadata:
13 | labels:
14 | app: nginx
15 |
16 | spec:
17 |
18 | containers:
19 | - name: nginx
20 | image: nginx:latest
21 | ports:
22 | - containerPort: 80
23 | volumeMounts:
24 | - name: nginx-index-file
25 | mountPath: /usr/share/nginx/html/
26 | volumes:
27 | - name: nginx-index-file
28 | configMap:
29 | name: index-html-configmap
30 |
--------------------------------------------------------------------------------
/src/test/java/io/spring/ControllersApplicationTests.java:
--------------------------------------------------------------------------------
1 | package io.spring;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | class ControllersApplicationTests {
8 |
9 | @Test
10 | void contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------