wmedvede commented on code in PR #3013:
URL: 
https://github.com/apache/incubator-kie-tools/pull/3013#discussion_r2023256325


##########
packages/sonataflow-operator/internal/controller/sonataflow_controller.go:
##########
@@ -118,6 +122,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")

Review Comment:
   I think the error message should include the name / namespace of the failing 
workflow. That information will be very helpful when we need to see the logs in 
case of failues.



##########
packages/sonataflow-operator/internal/controller/validation/ImageValidator.go:
##########
@@ -0,0 +1,128 @@
+// 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"
+       "regexp"
+
+       "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"
+
+       operatorapi 
"github.com/apache/incubator-kie-tools/packages/sonataflow-operator/api/v1alpha08"
+)
+
+type ImageValidator struct{}
+
+var pattern = `^.*\.sw\.(yaml|yml|json)$`
+
+var readablePattern = `*.sw.(yaml|yml|json)`
+var compiled = regexp.MustCompile(pattern)
+
+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)

Review Comment:
   If we are validating a `workflow'  that was just read the instant before 
calling the Validators sequence, I don't believe we need to re-read it again.
   
   But if you do, I don't believe we should read it and storing the value in 
the parameter sonataflow *operatorapi.SonataFlow paramter, I think this could 
make the validator has side effects on that variable value. When multiple 
validators are executed I think this code wont be clear.
   
   Another nitpick, when we do reading of the resource as part of the 
reconciliation, we normally guard the code with something like this:
   
   ```
        err := r.Client.Get(ctx, req.NamespacedName, workflow)
        if err != nil {
                if errors.IsNotFound(err) {
                        not good, return the error or whatever, but we know 
exactly that the given resource don't exist at this time.
                }
                not good, return the error or whatever, something went wrong, 
we don't know what.
        }
   ```
   



##########
packages/sonataflow-operator/internal/controller/validation/Validator.go:
##########
@@ -0,0 +1,41 @@
+// 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"
+
+       ctrl "sigs.k8s.io/controller-runtime"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+
+       operatorapi 
"github.com/apache/incubator-kie-tools/packages/sonataflow-operator/api/v1alpha08"
+)
+
+var validators = []Validator{ImageValidator{}}
+
+type Validator interface {

Review Comment:
   Nice approach!



##########
packages/sonataflow-operator/internal/controller/validation/Validator.go:
##########
@@ -0,0 +1,41 @@
+// 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"
+
+       ctrl "sigs.k8s.io/controller-runtime"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+
+       operatorapi 
"github.com/apache/incubator-kie-tools/packages/sonataflow-operator/api/v1alpha08"
+)
+
+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

Review Comment:
   nitpick, validate here, goes with uppercase Validate instead.



##########
packages/sonataflow-operator/internal/controller/validation/ImageValidator.go:
##########
@@ -0,0 +1,128 @@
+// Copyright 2024 Apache Software Foundation (ASF)

Review Comment:
   wrong licence format, copyright not needed



##########
packages/sonataflow-operator/internal/controller/validation/ImageValidator.go:
##########
@@ -0,0 +1,128 @@
+// 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"
+       "regexp"
+
+       "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"
+
+       operatorapi 
"github.com/apache/incubator-kie-tools/packages/sonataflow-operator/api/v1alpha08"
+)
+
+type ImageValidator struct{}
+
+var pattern = `^.*\.sw\.(yaml|yml|json)$`
+
+var readablePattern = `*.sw.(yaml|yml|json)`
+var compiled = regexp.MustCompile(pattern)
+
+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)
+       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) (*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]); err == nil && reader != 
nil {
+                       return reader, nil
+               } else if err != nil {
+                       return nil, err
+               }
+       }
+       return nil, fmt.Errorf("file matching the pattern %s,  not found in 
container image", readablePattern)
+}
+
+func readLayer(layer v1.Layer) (*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' && compiled.MatchString(header.Name) {
+                       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 {

Review Comment:
   Excuse me if I'm missing something, but do this Unmarshalling work both when 
the workflow inside the image is in json and yaml format? If so, maybe this 
function can be called workflowFromDockerImage.



##########
packages/sonataflow-operator/internal/controller/sonataflow_controller.go:
##########
@@ -118,6 +122,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")

Review Comment:
   This invocation to the validator should go after the code below.
   That `if` indicates that the WF being deleted right now, so, we don't need 
to validate it.
   
        if workflow.DeletionTimestamp != nil {
                return r.applyFinalizers(ctx, workflow)
        }
   
   



##########
packages/sonataflow-operator/internal/controller/validation/ImageValidator.go:
##########
@@ -0,0 +1,128 @@
+// 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"
+       "regexp"
+
+       "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"
+
+       operatorapi 
"github.com/apache/incubator-kie-tools/packages/sonataflow-operator/api/v1alpha08"
+)
+
+type ImageValidator struct{}
+
+var pattern = `^.*\.sw\.(yaml|yml|json)$`
+
+var readablePattern = `*.sw.(yaml|yml|json)`
+var compiled = regexp.MustCompile(pattern)
+
+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)

Review Comment:
   if we don't do the consideration above, in case of error the sonataflow 
value is uncertain.



##########
packages/sonataflow-operator/internal/controller/validation/Validator.go:
##########
@@ -0,0 +1,41 @@
+// Copyright 2024 Apache Software Foundation (ASF)

Review Comment:
   same here, copyright we don't need



##########
packages/sonataflow-operator/internal/controller/validation/ImageValidator.go:
##########
@@ -0,0 +1,128 @@
+// 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"
+       "regexp"
+
+       "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"
+
+       operatorapi 
"github.com/apache/incubator-kie-tools/packages/sonataflow-operator/api/v1alpha08"
+)
+
+type ImageValidator struct{}
+
+var pattern = `^.*\.sw\.(yaml|yml|json)$`
+
+var readablePattern = `*.sw.(yaml|yml|json)`
+var compiled = regexp.MustCompile(pattern)
+
+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)

Review Comment:
   workflow name/namepace would be nice to include in the message.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to