diff --git a/deploy/crds/wildfly.org_wildflyservers_crd.yaml b/deploy/crds/wildfly.org_wildflyservers_crd.yaml index 4cd3f45c..9fa2f708 100644 --- a/deploy/crds/wildfly.org_wildflyservers_crd.yaml +++ b/deploy/crds/wildfly.org_wildflyservers_crd.yaml @@ -50,9 +50,23 @@ spec: configMaps: description: ConfigMaps is a list of ConfigMaps in the same namespace as the WildFlyServer object, which shall be mounted into the WildFlyServer - Pods. The ConfigMaps are mounted into /etc/configmaps/. + Pods. It can optionally specify the path, as an absolute or relative + path, within the container at which the volume should be mounted. + If the specified mount path is a relative path, then it will be treated + relative to JBOSS_HOME. If a MountPath is not specified, then the + ConfigMap is mount by default into /etc/configmaps/. items: - type: string + description: ConfigMapSpec represents a ConfigMap definition with + a name and a mount path. MountPath cannot contains ':' + properties: + mountPath: + pattern: ^[^:]+$ + type: string + name: + type: string + required: + - name + type: object minItems: 1 type: array disableHTTPRoute: @@ -502,6 +516,7 @@ spec: - replicas - scalingdownPods type: object + type: object version: v1alpha1 versions: - name: v1alpha1 diff --git a/doc/apis.adoc b/doc/apis.adoc index 61c768af..d3f8b4a3 100644 --- a/doc/apis.adoc +++ b/doc/apis.adoc @@ -47,7 +47,7 @@ It uses a `StatefulSet` with a pod spec that mounts the volume specified by `sto | `envFrom` | List of environment variable present in the containers from source (either `ConfigMap` or `Secret`) | []https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#envfromsource-v1-core[corev1.EnvFromSource] |false | `env` | List of environment variable present in the containers | []https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#envvar-v1-core[corev1.EnvVar] | false | `secrets` | List of secret names to mount as volumes in the containers. Each secret is mounted as a read-only volume under `/etc/secrets/` | string[] | false -| `configMaps` | List of ConfigMap names to mount as volumes in the containers. Each config map is mounted as a read-only volume under `/etc/configmaps/` | string[] | false +| `configMaps` | List of ConfigMap definitions to mount as volumes in the containers. | []<> | false | `disableHTTPRoute`| Disable the creation a route to the HTTP port of the application service (false if omitted) | bool | false | `sessionAffinity`| If connections from the same client IP are passed to the same WildFlyServer instance/pod each time (false if omitted) | bool | false |======================= @@ -82,6 +82,16 @@ transaction, make sure to specify a `volumeClaimTemplate` that so that the same | `key` | Key of the ConfigMap whose value is the standalone configuration XML file. If omitted, the spec will look for the `standalone.xml` key. | string |false |======================= +[[configmapspec]] +## `ConfigMapSpec` + +`ConfigMapSpec` defines a `ConfigMap` name to mount as volume in the container. By specifying `mountPath` it is possible to configure the path where this config map should be mounted. +[options="header,footer"] +|======================= +| Field | Description |Scheme| Required +| `name` | Name of the `ConfigMap` to mount as a volume. | string | true +| `mountPath` | Path within the volume from which the container's volume should be mounted. Must not contain ':'. In case of a relative path, the path is treated as relative to `JBOSS_HOME`. It defaults to `/etc/configmaps/` | string |false +|======================= [[wildflyserverstatus]] ## `WildFlyServerStatus` diff --git a/doc/user-guide.adoc b/doc/user-guide.adoc index 35f89f9a..75365879 100644 --- a/doc/user-guide.adoc +++ b/doc/user-guide.adoc @@ -140,34 +140,53 @@ my-very-secure-pasword ConfigMaps can be mounted as volumes to be accessed from the application. -The config maps must be created *before* the WildFly Operator deploys the application. For example we can create a config map named `my-config` with a command such as: +The config maps must be created *before* the WildFly Operator deploys the application. We can configure the Operator to mount multiple config maps. + +For example, we can create one config map named `my-literals` to supply two literal values with a command such as: + +[source,shell] +---- +$ kubectl create configmap my-literals --from-literal=key1=value1 --from-literal=key2=value2 +configmap/my-literals created +---- + +We can create another config map named `my-extensions` to supply two files with a command such as: [source,shell] ---- -$ kubectl create configmap my-config --from-literal=key1=value1 --from-literal=key2=value2 -configmap/my-config created +$ kubectl create configmap my-extensions --from-file=preconfigure.sh --from-file=postconfigure.sh +configmap/extesions created ---- -Once the config map has been created, we can specify its name in the WildFlyServer Spec to have it mounted as a volume in the pods running the application: +Once our config maps have been created, we can configure them in the WildFlyServer Spec to have them mounted as a volumes in the pods running the application. + +For each config map, we have to specify its name and, optionally, an absolute or relative path where we want them mounted. +If we do not specify a mount path, then the config map will be mounted by default under `/etc/configmaps/`. If the mount path is relative, then the mount path is treated as relative to `JBOSS_HOME`. This path cannot contains ':', if so, the resource creation will be rejected. Notice when the config maps are mounted, if they are mounted to an existing filesystem directory, the files on that directly will not be accessible to the image until the config map is unmounted. It is left to the user to take the precautions to do not override a directory that is used by the container to work properly. + +For example, given two config maps `my-literals` and `my-extensions`, we could configure them in the WildFlyServer Spec as: [source,yaml] .Example of mounting config maps ---- spec: configMaps: - - my-config + - name: my-literals + - name: my-extensions + mountPath: extensions ---- -The config maps will then be mounted under `/etc/configmaps/` and each key/value will be stored in a file (whose name is the key and the content is the value). +This configuration will result in the following content available in our running pod: [source,shell] -.Config Map is mounted as a volume inside the Pod +.Config Maps mounted as a volume inside the Pod ---- -[jboss@quickstart-0 ~]$ ls /etc/configmaps/my-config/ +[jboss@quickstart-0 ~]$ ls $JBOSS_HOME/extensions +preconfigure.sh postconfigure.sh +[jboss@quickstart-0 ~]$ ls /etc/configmaps/my-literals key1 key2 -[jboss@quickstart-0 ~]$ cat /etc/configmaps/my-config/key1 +[jboss@quickstart-0 ~]$ cat /etc/configmaps/my-literals/key1 value1 -[jboss@quickstart-0 ~]$ cat /etc/configmaps/my-config/key2 +[jboss@quickstart-0 ~]$ cat /etc/configmaps/my-literals/key2 value2 ---- diff --git a/examples/extensions/README.adoc b/examples/extensions/README.adoc new file mode 100644 index 00000000..91aa5dd8 --- /dev/null +++ b/examples/extensions/README.adoc @@ -0,0 +1,151 @@ += The Extension mechanism + +This example shows how to use the extension mechanism available on the WildFly images by using the WildFly Operator. + +The extension mechanism allows the execution of arbitrary bash scripts before and after server configuration by using environment variables. + +When the server is launched, the `$JBOSS_HOME/extensions` directory on the image filesystem is examined to look for any of these two files: + +* `$JBOSS_HOME/extensions/preconfigure.sh` +* `$JBOSS_HOME/extensions/postconfigure.sh` + +If `preconfigure.sh` exists, then it is executed as an initial step before configuring the server by using the environment variables. Similarly, once the server is configured, if `postconfigure.sh` exists, it is executed. Those specific scripts give you the opportunity to prepare the image for the server configuration and to execute any task once the server is configured. + +In this example, we will use the WildFly Operator to place our scripts under `$JBOSS_HOME/extensions/`. We can do it by using a config map to supply the scripts to the image. The WildFly Operator allows specifying the mount path where a config map should be mounted. That makes the use of the extension mechanism pretty simply; the files on the config map must be mounted under `$JBOSS_HOME/extensions/`, that is all. + +== Prerequisites + +* Install OpenShift (or Code Ready Containers, minishift or minikube) +* Install the WildFly Operator as explained in the https://github.com/wildfly/wildfly-operator#install-the-operator-and-associate-resources[README]. + +== Create a ConfigMap to supply our scripts + +The `preconfigure.sh` and `postconfigure.sh` scripts are under the https://github.com/wildfly/wildfly-operator/tree/master/examples/extensions/config[config] directory of this example. The command to add both to a config map is the following: + +[source,shell] +---- +$ oc create configmap extensions-configmap --from-file=./config/ +configmap/extensions-configmap created +---- + +Let us examine its content: + +https://github.com/wildfly/wildfly-operator/blob/master/examples/extensions/config/preconfigure.sh[preconfigure.sh] +[source,shell] +.... +#!/bin/bash + +echo "========== Executing preconfigure.sh ==========" + +for ((i=0;i<5;i++)); do echo ${i}; sleep 1; done + +echo "========== End preconfigure.sh ==========" +.... + +https://github.com/wildfly/wildfly-operator/blob/master/examples/extensions/config/postconfigure.sh[postconfigure.sh] +[source,shell] +.... +#!/bin/bash + +echo "========== Executing postconfigure.sh ==========" + +for ((i=0;i<5;i++)); do echo ${i}; sleep 1; done + +echo "========== End postconfigure.sh ==========" +.... + +The files are regular bash scripts for testing purposes that print messages simulating a task being executed before and after the server is configured. + +Once we have configured the config map, we can post our custom resource to the Openshift cluster. This resource will be instantiated by the WildFly Operator. + +== Deploy the image + +The custom resource for this example is defined in the https://github.com/wildfly/wildfly-operator/blob/master/examples/extensions/crds/extensions-app.yaml[extensions-app.yaml file]: + +[source,yaml] +---- +apiVersion: wildfly.org/v1alpha1 +kind: WildFlyServer +metadata: + name: extensions-app +spec: + applicationImage: "quay.io/wildfly/wildfly-centos7:latest" + replicas: 2 + configMaps: + - name: extensions-configmap + mountPath: extensions +---- + +This custom resource will run the https://quay.io/repository/wildfly/wildfly-centos7?tab=tags[wildfly-centos7 image], which is the server image without any application deployed. We do not need an application deployed for this example, so we have used just the server image. + +The custom resource defines a config map with a mount path being relative; the mount path does not start with the slash character. Since it is a relative path, this config map will be mounted under `$JBOSS_HOME/extensions`. This directory will include our scripts that will be executed by the extension mechanism. + +The image can be deployed on the OpenShift cluster using the WildFly Operator: + +[source,shell] +---- +$ oc apply -f crds/extensions-app.yaml +wildflyserver.wildfly.org/extensions-app created +---- + +You can then check the status of the image: + +[source,shell] +---- +$ oc describe wildflyserver extensions-app +Name: extensions-app +Namespace: op-default +Labels: +Annotations: kubectl.kubernetes.io/last-applied-configuration: + {"apiVersion":"wildfly.org/v1alpha1","kind":"WildFlyServer","metadata":{"annotations":{},"name":"extensions-app","namespace":"op-default"}... +API Version: wildfly.org/v1alpha1 +Kind: WildFlyServer +Metadata: + Creation Timestamp: 2020-03-10T15:15:31Z + Generation: 1 + Resource Version: 385073 + Self Link: /apis/wildfly.org/v1alpha1/namespaces/op-default/wildflyservers/extensions-app + UID: fb98cc9b-62e1-11ea-86d4-0ef0e3c74fbe +Spec: + Application Image: quay.io/wildfly/wildfly-centos7:latest + Config Maps: + Mount Path: extensions + Name: extensions-configmap + Replicas: 2 +Status: + Hosts: + extensions-app-route-op-default.apps-crc.testing + Pods: + Name: extensions-app-0 + Pod IP: 10.128.1.188 + State: ACTIVE + Name: extensions-app-1 + Pod IP: 10.128.1.187 + State: ACTIVE + Replicas: 2 + Scalingdown Pods: 0 +Events: +---- + + +== Validate the configuration + +Once we have created our custom resource, we will see the pods created, each of them running the WildFly server: + +[source,shell] +---- +$ oc get pods +NAME READY STATUS RESTARTS AGE +extensions-app-0 1/1 Running 0 17s +extensions-app-1 1/1 Running 0 17s +wildfly-operator-667b5dfb57-b2wp9 1/1 Running 0 98s +---- + +We can check the logs of each application pod to verify the echo messages of our scripts were printed: + +[source,shell] +---- +$ oc logs extensions-app-0 +---- + +You will see how our scripts were executed before and after the server is configured. \ No newline at end of file diff --git a/examples/extensions/config/postconfigure.sh b/examples/extensions/config/postconfigure.sh new file mode 100644 index 00000000..c7888540 --- /dev/null +++ b/examples/extensions/config/postconfigure.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +echo "========== Executing postconfigure.sh ==========" + +for ((i=0;i<5;i++)); do echo ${i}; sleep 1; done + +echo "========== End postconfigure.sh ==========" \ No newline at end of file diff --git a/examples/extensions/config/preconfigure.sh b/examples/extensions/config/preconfigure.sh new file mode 100644 index 00000000..98b91a04 --- /dev/null +++ b/examples/extensions/config/preconfigure.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +echo "========== Executing preconfigure.sh ==========" + +for ((i=0;i<5;i++)); do echo ${i}; sleep 1; done + +echo "========== End preconfigure.sh ==========" diff --git a/examples/extensions/crds/extensions-app.yaml b/examples/extensions/crds/extensions-app.yaml new file mode 100644 index 00000000..7f434b1a --- /dev/null +++ b/examples/extensions/crds/extensions-app.yaml @@ -0,0 +1,10 @@ +apiVersion: wildfly.org/v1alpha1 +kind: WildFlyServer +metadata: + name: extensions-app +spec: + applicationImage: "quay.io/wildfly/wildfly-centos7:latest" + replicas: 2 + configMaps: + - name: extensions-configmap + mountPath: extensions \ No newline at end of file diff --git a/pkg/apis/wildfly/v1alpha1/wildflyserver_types.go b/pkg/apis/wildfly/v1alpha1/wildflyserver_types.go index f852ce36..2a3b9dd7 100644 --- a/pkg/apis/wildfly/v1alpha1/wildflyserver_types.go +++ b/pkg/apis/wildfly/v1alpha1/wildflyserver_types.go @@ -40,10 +40,12 @@ type WildFlyServerSpec struct { Secrets []string `json:"secrets,omitempty"` // ConfigMaps is a list of ConfigMaps in the same namespace as the WildFlyServer // object, which shall be mounted into the WildFlyServer Pods. - // The ConfigMaps are mounted into /etc/configmaps/. + // It can optionally specify the path, as an absolute or relative path, within the container at which the volume should be mounted. + // If the specified mount path is a relative path, then it will be treated relative to JBOSS_HOME. + // If a MountPath is not specified, then the ConfigMap is mount by default into /etc/configmaps/. // +kubebuilder:validation:MinItems=1 // +listType=set - ConfigMaps []string `json:"configMaps,omitempty"` + ConfigMaps []ConfigMapSpec `json:"configMaps,omitempty"` } // StandaloneConfigMapSpec defines the desired configMap configuration to obtain the standalone configuration for WildFlyServer @@ -135,6 +137,14 @@ type WildFlyServerList struct { Items []WildFlyServer `json:"items"` } +// ConfigMapSpec represents a ConfigMap definition with a name and a mount path. MountPath cannot contains ':' +// +k8s:openapi-gen=true +type ConfigMapSpec struct { + Name string `json:"name"` + // +kubebuilder:validation:Pattern=`^[^:]+$` + MountPath string `json:"mountPath,omitempty"` +} + func init() { SchemeBuilder.Register(&WildFlyServer{}, &WildFlyServerList{}) } diff --git a/pkg/apis/wildfly/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/wildfly/v1alpha1/zz_generated.deepcopy.go index 272ff3c9..72372856 100644 --- a/pkg/apis/wildfly/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/wildfly/v1alpha1/zz_generated.deepcopy.go @@ -9,6 +9,22 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigMapSpec) DeepCopyInto(out *ConfigMapSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapSpec. +func (in *ConfigMapSpec) DeepCopy() *ConfigMapSpec { + if in == nil { + return nil + } + out := new(ConfigMapSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodStatus) DeepCopyInto(out *PodStatus) { *out = *in @@ -158,7 +174,7 @@ func (in *WildFlyServerSpec) DeepCopyInto(out *WildFlyServerSpec) { } if in.ConfigMaps != nil { in, out := &in.ConfigMaps, &out.ConfigMaps - *out = make([]string, len(*in)) + *out = make([]ConfigMapSpec, len(*in)) copy(*out, *in) } return diff --git a/pkg/apis/wildfly/v1alpha1/zz_generated.openapi.go b/pkg/apis/wildfly/v1alpha1/zz_generated.openapi.go index 1a78fa4d..a535122c 100644 --- a/pkg/apis/wildfly/v1alpha1/zz_generated.openapi.go +++ b/pkg/apis/wildfly/v1alpha1/zz_generated.openapi.go @@ -13,12 +13,39 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "./pkg/apis/wildfly/v1alpha1.PodStatus": schema_pkg_apis_wildfly_v1alpha1_PodStatus(ref), - "./pkg/apis/wildfly/v1alpha1.StandaloneConfigMapSpec": schema_pkg_apis_wildfly_v1alpha1_StandaloneConfigMapSpec(ref), - "./pkg/apis/wildfly/v1alpha1.StorageSpec": schema_pkg_apis_wildfly_v1alpha1_StorageSpec(ref), - "./pkg/apis/wildfly/v1alpha1.WildFlyServer": schema_pkg_apis_wildfly_v1alpha1_WildFlyServer(ref), - "./pkg/apis/wildfly/v1alpha1.WildFlyServerSpec": schema_pkg_apis_wildfly_v1alpha1_WildFlyServerSpec(ref), - "./pkg/apis/wildfly/v1alpha1.WildFlyServerStatus": schema_pkg_apis_wildfly_v1alpha1_WildFlyServerStatus(ref), + "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.ConfigMapSpec": schema_pkg_apis_wildfly_v1alpha1_ConfigMapSpec(ref), + "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.PodStatus": schema_pkg_apis_wildfly_v1alpha1_PodStatus(ref), + "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.StandaloneConfigMapSpec": schema_pkg_apis_wildfly_v1alpha1_StandaloneConfigMapSpec(ref), + "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.StorageSpec": schema_pkg_apis_wildfly_v1alpha1_StorageSpec(ref), + "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.WildFlyServer": schema_pkg_apis_wildfly_v1alpha1_WildFlyServer(ref), + "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.WildFlyServerSpec": schema_pkg_apis_wildfly_v1alpha1_WildFlyServerSpec(ref), + "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.WildFlyServerStatus": schema_pkg_apis_wildfly_v1alpha1_WildFlyServerStatus(ref), + } +} + +func schema_pkg_apis_wildfly_v1alpha1_ConfigMapSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ConfigMapSpec represents a ConfigMap definition with a name and a mount path. MountPath cannot contains ':'", + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "mountPath": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + Dependencies: []string{}, } } @@ -134,19 +161,19 @@ func schema_pkg_apis_wildfly_v1alpha1_WildFlyServer(ref common.ReferenceCallback }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/wildfly/v1alpha1.WildFlyServerSpec"), + Ref: ref("github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.WildFlyServerSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/wildfly/v1alpha1.WildFlyServerStatus"), + Ref: ref("github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.WildFlyServerStatus"), }, }, }, }, }, Dependencies: []string{ - "./pkg/apis/wildfly/v1alpha1.WildFlyServerSpec", "./pkg/apis/wildfly/v1alpha1.WildFlyServerStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.WildFlyServerSpec", "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.WildFlyServerStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -186,13 +213,13 @@ func schema_pkg_apis_wildfly_v1alpha1_WildFlyServerSpec(ref common.ReferenceCall }, "standaloneConfigMap": { SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/wildfly/v1alpha1.StandaloneConfigMapSpec"), + Ref: ref("github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.StandaloneConfigMapSpec"), }, }, "storage": { SchemaProps: spec.SchemaProps{ Description: "StorageSpec defines specific storage required for the server own data directory. If omitted, an EmptyDir is used (that will not persist data across pod restart).", - Ref: ref("./pkg/apis/wildfly/v1alpha1.StorageSpec"), + Ref: ref("github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.StorageSpec"), }, }, "serviceAccountName": { @@ -258,13 +285,12 @@ func schema_pkg_apis_wildfly_v1alpha1_WildFlyServerSpec(ref common.ReferenceCall }, }, SchemaProps: spec.SchemaProps{ - Description: "ConfigMaps is a list of ConfigMaps in the same namespace as the WildFlyServer object, which shall be mounted into the WildFlyServer Pods. The ConfigMaps are mounted into /etc/configmaps/.", + Description: "ConfigMaps is a list of ConfigMaps in the same namespace as the WildFlyServer object, which shall be mounted into the WildFlyServer Pods. It can optionally specify the path, as an absolute or relative path, within the container at which the volume should be mounted. If the specified mount path is a relative path, then it will be treated relative to JBOSS_HOME. If a MountPath is not specified, then the ConfigMap is mount by default into /etc/configmaps/.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", + Ref: ref("github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.ConfigMapSpec"), }, }, }, @@ -275,7 +301,7 @@ func schema_pkg_apis_wildfly_v1alpha1_WildFlyServerSpec(ref common.ReferenceCall }, }, Dependencies: []string{ - "./pkg/apis/wildfly/v1alpha1.StandaloneConfigMapSpec", "./pkg/apis/wildfly/v1alpha1.StorageSpec", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar"}, + "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.ConfigMapSpec", "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.StandaloneConfigMapSpec", "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.StorageSpec", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar"}, } } @@ -303,7 +329,7 @@ func schema_pkg_apis_wildfly_v1alpha1_WildFlyServerStatus(ref common.ReferenceCa Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: ref("./pkg/apis/wildfly/v1alpha1.PodStatus"), + Ref: ref("github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.PodStatus"), }, }, }, @@ -339,6 +365,6 @@ func schema_pkg_apis_wildfly_v1alpha1_WildFlyServerStatus(ref common.ReferenceCa }, }, Dependencies: []string{ - "./pkg/apis/wildfly/v1alpha1.PodStatus"}, + "github.com/wildfly/wildfly-operator/pkg/apis/wildfly/v1alpha1.PodStatus"}, } } diff --git a/pkg/controller/wildflyserver/wildflyserver_controller_test.go b/pkg/controller/wildflyserver/wildflyserver_controller_test.go index 040f6c44..a78dcda2 100644 --- a/pkg/controller/wildflyserver/wildflyserver_controller_test.go +++ b/pkg/controller/wildflyserver/wildflyserver_controller_test.go @@ -2,6 +2,8 @@ package wildflyserver import ( "context" + "github.com/wildfly/wildfly-operator/pkg/resources" + "path" "testing" "time" @@ -421,7 +423,21 @@ func TestWildFlyServerWithConfigMap(t *testing.T) { logf.SetLogger(logf.ZapLogger(true)) assert := assert.New(t) - configMapName := "my-config" + cmNoPath := wildflyv1alpha1.ConfigMapSpec{ + Name: "my-config", + } + + cmAbsPath := wildflyv1alpha1.ConfigMapSpec{ + Name: "my-config-two", + MountPath: "/test/test/", + } + + cmRelPath := wildflyv1alpha1.ConfigMapSpec{ + Name: "my-config-three", + MountPath: "test/test", + } + + configMapsUnderTest := []wildflyv1alpha1.ConfigMapSpec{cmNoPath, cmAbsPath, cmRelPath} // A WildFlyServer resource with metadata and spec. wildflyServer := &wildflyv1alpha1.WildFlyServer{ @@ -432,7 +448,7 @@ func TestWildFlyServerWithConfigMap(t *testing.T) { Spec: wildflyv1alpha1.WildFlyServerSpec{ ApplicationImage: applicationImage, Replicas: replicas, - ConfigMaps: []string{configMapName}, + ConfigMaps: configMapsUnderTest, }, } // Objects to track in the fake client. @@ -478,27 +494,36 @@ func TestWildFlyServerWithConfigMap(t *testing.T) { assert.Equal(replicas, *statefulSet.Spec.Replicas) assert.Equal(applicationImage, statefulSet.Spec.Template.Spec.Containers[0].Image) - foundVolume := false - for _, v := range statefulSet.Spec.Template.Spec.Volumes { - if v.Name == "configmap-"+configMapName { - source := v.VolumeSource - if source.ConfigMap.LocalObjectReference.Name == configMapName { - foundVolume = true - break + for _, cm := range configMapsUnderTest { + foundVolume := false + for _, v := range statefulSet.Spec.Template.Spec.Volumes { + if v.Name == "configmap-"+cm.Name { + source := v.VolumeSource + if source.ConfigMap.LocalObjectReference.Name == cm.Name { + foundVolume = true + break + } } } - } - assert.True(foundVolume) - - foundVolumeMount := false - for _, vm := range statefulSet.Spec.Template.Spec.Containers[0].VolumeMounts { - if vm.Name == "configmap-"+configMapName { - assert.Equal("/etc/configmaps/"+configMapName, vm.MountPath) - assert.True(vm.ReadOnly) - foundVolumeMount = true + assert.True(foundVolume) + + foundVolumeMount := false + for _, vm := range statefulSet.Spec.Template.Spec.Containers[0].VolumeMounts { + if vm.Name == "configmap-"+cm.Name { + switch cm.Name { + case cmNoPath.Name: + assert.Equal(path.Join(resources.ConfigMapsDir, cm.Name), vm.MountPath) + case cmAbsPath.Name: + assert.Equal(cmAbsPath.MountPath, vm.MountPath) + case cmRelPath.Name: + assert.Equal(path.Join(resources.JBossHome, cmRelPath.MountPath), vm.MountPath) + } + assert.True(vm.ReadOnly) + foundVolumeMount = true + } } + assert.True(foundVolumeMount) } - assert.True(foundVolumeMount) } type eventRecorderMock struct { diff --git a/pkg/resources/constants.go b/pkg/resources/constants.go index a685f9ef..18e630b4 100644 --- a/pkg/resources/constants.go +++ b/pkg/resources/constants.go @@ -19,6 +19,8 @@ const ( SecretsDir = "/etc/secrets/" // ConfigMapsDir is the the directory to mount volumes from ConfigMaps ConfigMapsDir = "/etc/configmaps/" + // Mode bits to use on created config maps files + ConfigMapFileDefaultMode int32 = 0755 ) var ( diff --git a/pkg/resources/statefulsets/statefulset.go b/pkg/resources/statefulsets/statefulset.go index 9a7bc2f5..a0fec6fc 100644 --- a/pkg/resources/statefulsets/statefulset.go +++ b/pkg/resources/statefulsets/statefulset.go @@ -2,6 +2,7 @@ package statefulsets import ( "os" + "path" "k8s.io/apimachinery/pkg/api/errors" k8slabels "k8s.io/apimachinery/pkg/labels" @@ -149,7 +150,7 @@ func NewStatefulSet(w *wildflyv1alpha1.WildFlyServer, labels map[string]string, // mount the volume for the server standatalone data directory volumeMounts = append(volumeMounts, corev1.VolumeMount{ Name: standaloneDataVolumeName, - MountPath: resources.JBossHome + "/" + resources.StandaloneServerDataDirRelativePath, + MountPath: path.Join(resources.JBossHome, resources.StandaloneServerDataDirRelativePath), }) // mount the volume to read the standalone XML configuration from a ConfigMap @@ -180,7 +181,7 @@ func NewStatefulSet(w *wildflyv1alpha1.WildFlyServer, labels map[string]string, }) volumeMounts = append(volumeMounts, corev1.VolumeMount{ Name: "standalone-config-volume", - MountPath: resources.JBossHome + "/standalone/configuration/standalone.xml", + MountPath: path.Join(resources.JBossHome, "standalone/configuration/standalone.xml"), SubPath: "standalone.xml", }) } @@ -199,27 +200,29 @@ func NewStatefulSet(w *wildflyv1alpha1.WildFlyServer, labels map[string]string, volumeMounts = append(volumeMounts, corev1.VolumeMount{ Name: volumeName, ReadOnly: true, - MountPath: resources.SecretsDir + s, + MountPath: path.Join(resources.SecretsDir, s), }) } // mount volumes from config maps + defaultMode := resources.ConfigMapFileDefaultMode for _, cm := range w.Spec.ConfigMaps { - volumeName := wildflyutil.SanitizeVolumeName("configmap-" + cm) + volumeName := wildflyutil.SanitizeVolumeName("configmap-" + cm.Name) volumes = append(volumes, corev1.Volume{ Name: volumeName, VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ LocalObjectReference: corev1.LocalObjectReference{ - Name: cm, + Name: cm.Name, }, + DefaultMode: &defaultMode, }, }, }) volumeMounts = append(volumeMounts, corev1.VolumeMount{ Name: volumeName, ReadOnly: true, - MountPath: resources.ConfigMapsDir + cm, + MountPath: ResolveMountPath(cm), }) } @@ -302,3 +305,16 @@ func envForEJBRecovery(w *wildflyv1alpha1.WildFlyServer) []corev1.EnvVar { }, } } + +// ResolveMountPath resolves the final mount path for the current ConfigMapSpec +func ResolveMountPath(in wildflyv1alpha1.ConfigMapSpec) string { + result := path.Join(resources.ConfigMapsDir, in.Name) + if in.MountPath != "" { + if path.IsAbs(in.MountPath) { + result = in.MountPath + } else { + result = path.Join(resources.JBossHome, in.MountPath) + } + } + return result +}