Skip to content

Commit

Permalink
kie-kogito-serverless-operator-405: Add external built image integri…
Browse files Browse the repository at this point in the history
…ty validation
  • Loading branch information
treblereel committed Sep 20, 2024
1 parent e91495d commit 4b5da8e
Show file tree
Hide file tree
Showing 19 changed files with 316 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
apiVersion: v1
data:
DEFAULT_WORKFLOW_EXTENSION: .sw.json
Dockerfile: "FROM docker.io/apache/incubator-kie-sonataflow-builder:main AS builder\n\n#
variables that can be overridden by the builder\n# To add a Quarkus extension
to your application\nARG QUARKUS_EXTENSIONS\n# Args to pass to the Quarkus CLI
add extension command\nARG QUARKUS_ADD_EXTENSION_ARGS\n# Additional java/mvn arguments
to pass to the builder\nARG MAVEN_ARGS_APPEND\n\n# Copy from build context to
skeleton resources project\nCOPY --chown=1001 . ./resources\n\nRUN /home/kogito/launch/build-app.sh
./resources\n \n#=============================\n# Runtime Run\n#=============================\nFROM
registry.access.redhat.com/ubi9/openjdk-17-runtime:latest\n\nENV LANG='en_US.UTF-8'
LANGUAGE='en_US:en'\n \n# We make four distinct layers so if there are application
changes the library layers can be re-used\nCOPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/lib/
/deployments/lib/\nCOPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/*.jar
/deployments/\nCOPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/app/
/deployments/app/\nCOPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/quarkus/
/deployments/quarkus/\n\nEXPOSE 8080\nUSER 185\nENV AB_JOLOKIA_OFF=\"\"\nENV JAVA_OPTS=\"-Dquarkus.http.host=0.0.0.0
-Djava.util.logging.manager=org.jboss.logmanager.LogManager\"\nENV JAVA_APP_JAR=\"/deployments/quarkus-run.jar\"\n"
Dockerfile: |
FROM docker.io/apache/incubator-kie-sonataflow-builder:main AS builder
# variables that can be overridden by the builder
# To add a Quarkus extension to your application
ARG QUARKUS_EXTENSIONS
# Args to pass to the Quarkus CLI add extension command
ARG QUARKUS_ADD_EXTENSION_ARGS
# Additional java/mvn arguments to pass to the builder
ARG MAVEN_ARGS_APPEND
# Copy from build context to skeleton resources project
COPY --chown=1001 . ./resources
RUN /home/kogito/launch/build-app.sh ./resources
#=============================
# Runtime Run
#=============================
FROM registry.access.redhat.com/ubi9/openjdk-17-runtime:latest
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/lib/ /deployments/lib/
COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/*.jar /deployments/
COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/app/ /deployments/app/
COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/quarkus/ /deployments/quarkus/
COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/classes/workflow.sw.json /deployments/app/workflow.sw.json
EXPOSE 8080
USER 185
ENV AB_JOLOKIA_OFF=""
ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
kind: ConfigMap
metadata:
name: sonataflow-operator-builder-config
5 changes: 3 additions & 2 deletions config/manager/SonataFlow-Builder.containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,20 @@ ARG MAVEN_ARGS_APPEND
COPY --chown=1001 . ./resources

RUN /home/kogito/launch/build-app.sh ./resources

#=============================
# Runtime Run
#=============================
FROM registry.access.redhat.com/ubi9/openjdk-17-runtime:latest

ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'

# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/lib/ /deployments/lib/
COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/*.jar /deployments/
COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/app/ /deployments/app/
COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/quarkus/ /deployments/quarkus/
COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/classes/workflow.sw.json /deployments/app/workflow.sw.json

EXPOSE 8080
USER 185
Expand Down
Binary file added container-builder/bin/builder
Binary file not shown.
Binary file removed container-builder/bin/controller-gen
Binary file not shown.
3 changes: 2 additions & 1 deletion container-builder/builder/kubernetes/testdata/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ COPY --from=builder --chown=185 /home/kogito/kogito-sw-base/target/quarkus-app/l
COPY --from=builder --chown=185 /home/kogito/kogito-sw-base/target/quarkus-app/*.jar /deployments/
COPY --from=builder --chown=185 /home/kogito/kogito-sw-base/target/quarkus-app/app/ /deployments/app/
COPY --from=builder --chown=185 /home/kogito/kogito-sw-base/target/quarkus-app/quarkus/ /deployments/quarkus/
COPY --from=builder --chown=185 /home/kogito/kogito-sw-base/target/classes/workflow.sw.json /deployments/app/workflow.sw.json

EXPOSE 8080
USER 185
ENV AB_JOLOKIA_OFF=""
ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/
COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/*.jar /deployments/
COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/app/ /deployments/app/
COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/quarkus-app/quarkus/ /deployments/quarkus/
COPY --from=builder --chown=185 /home/kogito/serverless-workflow-project/target/classes/workflow.sw.json /deployments/app/workflow.sw.json

EXPOSE 8080
USER 185
Expand Down
2 changes: 1 addition & 1 deletion container-builder/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ require (
)

require (
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/containerd/log v0.1.0 // indirect
Expand Down
3 changes: 1 addition & 2 deletions container-builder/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
Expand Down
15 changes: 15 additions & 0 deletions controllers/sonataflow_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (

"github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/common/constants"
profiles "github.com/apache/incubator-kie-kogito-serverless-operator/controllers/profiles/factory"
"github.com/apache/incubator-kie-kogito-serverless-operator/controllers/validation"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/rest"
Expand Down Expand Up @@ -96,6 +97,11 @@ func (r *SonataFlowReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return ctrl.Result{}, err
}

if err := r.Validate(ctx, workflow, req); err != nil {
klog.V(log.E).ErrorS(err, "Failed to validate SonataFlow")
return reconcile.Result{}, nil
}

r.setDefaults(workflow)
// If the workflow is being deleted, clean up the triggers on a different namespace
if workflow.DeletionTimestamp != nil && controllerutil.ContainsFinalizer(workflow, constants.TriggerFinalizer) {
Expand Down Expand Up @@ -244,3 +250,12 @@ func (r *SonataFlowReconciler) SetupWithManager(mgr ctrl.Manager) error {
Watches(&eventingv1.Trigger{}, handler.EnqueueRequestsFromMapFunc(knative.MapTriggerToPlatformRequests)).
Complete(r)
}

func (r *SonataFlowReconciler) Validate(ctx context.Context, sonataflow *operatorapi.SonataFlow, req ctrl.Request) error {
if sonataflow.Status.ObservedGeneration < sonataflow.Generation {
if err := validation.Validate(ctx, r.Client, sonataflow, req); err != nil {
return err
}
}
return nil
}
123 changes: 123 additions & 0 deletions controllers/validation/ImageValidator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2024 Apache Software Foundation (ASF)
//
// 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.

package validation

import (
"archive/tar"
"context"
"fmt"
"io"

operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08"
"github.com/google/go-cmp/cmp"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"k8s.io/apimachinery/pkg/util/yaml"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type ImageValidator struct{}

var workflowName = "deployments/app/workflow.sw.json"

func (v ImageValidator) Validate(ctx context.Context, client client.Client, sonataflow *operatorapi.SonataFlow, req ctrl.Request) error {
if sonataflow.HasContainerSpecImage() {
err := client.Get(ctx, req.NamespacedName, sonataflow)
equals, err := validateImage(sonataflow, sonataflow.Spec.PodTemplate.Container.Image)
if err != nil {
return err
}
if !equals {
return fmt.Errorf("Workflow, defined in the image %s doesn't match deployment workflow", sonataflow.Spec.PodTemplate.Container.Image)
}
}
return nil
}

func validateImage(sonataflow *operatorapi.SonataFlow, image string) (bool, error) {
imageRef, err := name.ParseReference(image)
if err != nil {
return false, err
}

ref, err := remote.Image(imageRef)
if err != nil {
return false, err
}

reader, err := readLayers(ref, workflowName)
if err != nil {
return false, err
}

workflowDockerImage, err := jsonFromDockerImage(reader)
if err != nil {
return false, err
}

return cmp.Equal(workflowDockerImage, sonataflow.Spec.Flow), nil
}

func readLayers(image v1.Image, workflow string) (*tar.Reader, error) {
layers, err := image.Layers()
if err != nil {
return nil, err
}

for i := len(layers) - 1; i >= 0; i-- {
if reader, err := readLayer(layers[i], workflow); err == nil && reader != nil {
return reader, nil
} else if err != nil {
return nil, err
}
}
return nil, fmt.Errorf("file not found %s in docker image", workflow)
}

func readLayer(layer v1.Layer, workflow string) (*tar.Reader, error) {
uncompressedLayer, err := layer.Uncompressed()
if err != nil {
return nil, fmt.Errorf("failed to get uncompressed layer: %v", err)
}
defer uncompressedLayer.Close()

tarReader := tar.NewReader(uncompressedLayer)
for {
header, err := tarReader.Next()
if err != nil {
if err.Error() == "EOF" {
break
}
return nil, fmt.Errorf("error reading tar: %v", err)
}

if header.Typeflag == '0' && header.Name == workflow {
return tarReader, nil
}
}

return nil, nil
}

func jsonFromDockerImage(reader io.Reader) (operatorapi.Flow, error) {
data, err := io.ReadAll(reader)
workflow := &operatorapi.Flow{}
if err = yaml.Unmarshal(data, workflow); err != nil {
return *workflow, err
}
return *workflow, nil
}
40 changes: 40 additions & 0 deletions controllers/validation/Validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2024 Apache Software Foundation (ASF)
//
// 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.

package validation

import (
"context"

operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var validators = []Validator{ImageValidator{}}

type Validator interface {
Validate(ctx context.Context, client client.Client, sonataflow *operatorapi.SonataFlow, req ctrl.Request) error
}

// validate the SonataFlow object, right now it's only check if workflow is in deployment has image declared as that image
// is the same as the image in the SonataFlow object
func Validate(ctx context.Context, client client.Client, sonataflow *operatorapi.SonataFlow, req ctrl.Request) error {
for _, validator := range validators {
if err := validator.Validate(ctx, client, sonataflow, req); err != nil {
return err
}
}
return nil
}
12 changes: 10 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ require (

require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/google/go-cmp v0.6.0
github.com/google/go-containerregistry v0.20.2
github.com/imdario/mergo v0.3.16
k8s.io/klog/v2 v2.100.1
k8s.io/utils v0.0.0-20230711102312-30195339c3c7
Expand All @@ -50,6 +52,10 @@ require (
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cloudevents/sdk-go/v2 v2.15.2 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/docker/cli v27.1.1+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
Expand All @@ -69,8 +75,6 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-containerregistry v0.13.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect
github.com/google/uuid v1.6.0 // indirect
Expand All @@ -80,6 +84,7 @@ require (
github.com/jpillora/backoff v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kelseyhightower/envconfig v1.4.0 // indirect
github.com/klauspost/compress v1.16.6 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
Expand All @@ -89,6 +94,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pb33f/libopenapi v0.8.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.17.0 // indirect
Expand All @@ -102,7 +108,9 @@ require (
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/santhosh-tekuri/jsonschema/v5 v5.3.0 // indirect
github.com/senseyeio/duration v0.0.0-20180430131211-7c2a214ada46 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/vbatts/tar-split v0.11.3 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
Expand Down
Loading

0 comments on commit 4b5da8e

Please sign in to comment.