This is an automated email from the ASF dual-hosted git repository. nferraro pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel-k.git
The following commit(s) were added to refs/heads/master by this push: new d8708ca fix(api): Declare integration Flow as json.RawMessage d8708ca is described below commit d8708cadd68e01890c822f4f20ffe2a9501a75d7 Author: Antonin Stefanutti <anto...@stefanutti.fr> AuthorDate: Fri Jul 3 19:31:46 2020 +0200 fix(api): Declare integration Flow as json.RawMessage --- deploy/crd-integration.yaml | 4 +- .../integrations.camel.apache.org.crd.yaml | 4 +- deploy/resources.go | 4 +- e2e/knative/knative_platform_test.go | 20 ++++-- go.sum | 4 ++ helm/camel-k/crds/crd-integration.yaml | 4 +- pkg/apis/camel/go.mod | 3 +- pkg/apis/camel/go.sum | 2 + pkg/apis/camel/v1/common_types.go | 7 +- pkg/apis/camel/v1/zz_generated.deepcopy.go | 27 +++++++- pkg/client/camel/go.sum | 1 + pkg/cmd/run.go | 7 +- pkg/trait/init.go | 13 ++-- pkg/util/digest/digest.go | 9 ++- pkg/util/flow/flow.go | 76 ++++++++++++++++++++++ pkg/util/flow/flow_test.go | 53 +++++++++++++++ 16 files changed, 210 insertions(+), 28 deletions(-) diff --git a/deploy/crd-integration.yaml b/deploy/crd-integration.yaml index 0366bde..6157ce4 100644 --- a/deploy/crd-integration.yaml +++ b/deploy/crd-integration.yaml @@ -88,9 +88,7 @@ spec: type: array flows: items: - description: Flow is an unstructured object representing a Camel Flow - in YAML/JSON DSL - type: string + type: object type: array kit: type: string diff --git a/deploy/olm-catalog/camel-k-dev/1.1.0-snapshot/integrations.camel.apache.org.crd.yaml b/deploy/olm-catalog/camel-k-dev/1.1.0-snapshot/integrations.camel.apache.org.crd.yaml index 0366bde..6157ce4 100644 --- a/deploy/olm-catalog/camel-k-dev/1.1.0-snapshot/integrations.camel.apache.org.crd.yaml +++ b/deploy/olm-catalog/camel-k-dev/1.1.0-snapshot/integrations.camel.apache.org.crd.yaml @@ -88,9 +88,7 @@ spec: type: array flows: items: - description: Flow is an unstructured object representing a Camel Flow - in YAML/JSON DSL - type: string + type: object type: array kit: type: string diff --git a/deploy/resources.go b/deploy/resources.go index 573461f..307bd13 100644 --- a/deploy/resources.go +++ b/deploy/resources.go @@ -140,9 +140,9 @@ var assets = func() http.FileSystem { "/crd-integration.yaml": &vfsgen۰CompressedFileInfo{ name: "crd-integration.yaml", modTime: time.Time{}, - uncompressedSize: 11649, + uncompressedSize: 11528, - compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x1a\x4d\x6f\xe3\xb8\xf5\xae\x5f\xf1\x10\x1f\x66\x17\x88\xed\xce\xee\xa5\x70\x4f\xae\x27\x41\xdd\xc9\x38\x81\xe5\xd9\xc5\x1c\x69\xe9\x59\x66\x43\x91\x2a\x49\xd9\x49\x8b\xfe\xf7\xe2\x91\x92\x2c\xc9\x92\xe3\x38\x33\x58\x60\x11\xdd\x2c\xbe\xef\x6f\x3e\x79\x00\xc3\xef\xf7\x04\x03\xb8\xe3\x11\x4a\x83\x31\x58\x05\x76\x8b\x30\xcd\x58\xb4\x45\x08\xd5\xc6\xee\x99\x46\xb8\x55\xb9\x8c\x99\xe5\x4a\xc2\x4f\xd3\xf0\xf6\x67\xc8\x [...] + compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x1a\x4d\x73\xe2\x38\xf6\xee\x5f\xf1\x2a\x1c\x7a\xa6\x2a\xc0\xf6\xcc\x65\x8b\x3d\xb1\x74\x52\xcb\x76\x9a\xa4\x80\x9e\xa9\x39\x0a\xfb\x61\xb4\x91\x25\xad\x24\x43\xb2\x5b\xfb\xdf\xb7\x9e\x64\x1b\x1b\x6c\x42\x48\x77\x4d\x55\x57\x7c\xc3\x7a\xdf\xdf\x7a\xa6\x07\xfd\x6f\xf7\x44\x3d\xb8\xe3\x31\x4a\x8b\x09\x38\x05\x6e\x83\x30\xd6\x2c\xde\x20\x2c\xd4\xda\xed\x98\x41\xb8\x55\xb9\x4c\x98\xe3\x4a\xc2\x4f\xe3\xc5\xed\xcf\x90\x [...] }, "/operator-deployment.yaml": &vfsgen۰CompressedFileInfo{ name: "operator-deployment.yaml", diff --git a/e2e/knative/knative_platform_test.go b/e2e/knative/knative_platform_test.go index f3aa846..857a72f 100644 --- a/e2e/knative/knative_platform_test.go +++ b/e2e/knative/knative_platform_test.go @@ -25,11 +25,16 @@ import ( "strings" "testing" + . "github.com/onsi/gomega" + + "github.com/stretchr/testify/assert" + + corev1 "k8s.io/api/core/v1" + . "github.com/apache/camel-k/e2e/support" "github.com/apache/camel-k/pkg/apis/camel/v1" + "github.com/apache/camel-k/pkg/util/flow" "github.com/apache/camel-k/pkg/util/knative" - . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" ) func TestKnativePlatformTest(t *testing.T) { @@ -53,11 +58,12 @@ func TestKnativePlatformTest(t *testing.T) { // Change something in the integration to produce a redeploy Expect(UpdateIntegration(ns, "yaml", func(it *v1.Integration) { it.Spec.Profile = "" - var flows []v1.Flow - for _, flow := range it.Spec.Flows { - flows = append(flows, v1.Flow(strings.ReplaceAll(string(flow), "string!", "string!!!"))) - } - it.Spec.Flows = flows + content, err := flow.Marshal(it.Spec.Flows) + assert.NoError(t, err) + newData := strings.ReplaceAll(string(content), "string!", "string!!!") + newFlows, err := flow.UnmarshalString(newData) + assert.NoError(t, err) + it.Spec.Flows = newFlows })).To(BeNil()) // Spec profile should be reset by "kamel run" Eventually(IntegrationSpecProfile(ns, "yaml")).Should(Equal(v1.TraitProfile(""))) diff --git a/go.sum b/go.sum index c5771d7..3f5902b 100644 --- a/go.sum +++ b/go.sum @@ -1375,12 +1375,16 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= sigs.k8s.io/controller-runtime v0.5.2 h1:pyXbUfoTo+HA3jeIfr0vgi+1WtmNh0CwlcnQGLXwsSw= sigs.k8s.io/controller-runtime v0.5.2/go.mod h1:JZUwSMVbxDupo0lTJSSFP5pimEyxGynROImSsqIOx1A= +sigs.k8s.io/controller-tools v0.0.0-20200528125929-5c0c6ae3b64b/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI= sigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3LtJvd/jIzA= sigs.k8s.io/controller-tools v0.2.8/go.mod h1:9VKHPszmf2DHz/QmHkcfZoewO6BL7pPs9uAiBVsaJSE= +sigs.k8s.io/controller-tools v0.3.0/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= diff --git a/helm/camel-k/crds/crd-integration.yaml b/helm/camel-k/crds/crd-integration.yaml index 0366bde..6157ce4 100644 --- a/helm/camel-k/crds/crd-integration.yaml +++ b/helm/camel-k/crds/crd-integration.yaml @@ -88,9 +88,7 @@ spec: type: array flows: items: - description: Flow is an unstructured object representing a Camel Flow - in YAML/JSON DSL - type: string + type: object type: array kit: type: string diff --git a/pkg/apis/camel/go.mod b/pkg/apis/camel/go.mod index d50e11b..c7bcb9f 100644 --- a/pkg/apis/camel/go.mod +++ b/pkg/apis/camel/go.mod @@ -5,6 +5,7 @@ go 1.13 require ( k8s.io/api v0.18.2 k8s.io/apimachinery v0.18.2 - sigs.k8s.io/controller-tools v0.3.0 // indirect + // Required to get https://github.com/kubernetes-sigs/controller-tools/pull/428 + sigs.k8s.io/controller-tools v0.0.0-20200528125929-5c0c6ae3b64b // indirect sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e // indirect ) diff --git a/pkg/apis/camel/go.sum b/pkg/apis/camel/go.sum index afd0e7e..60acd80 100644 --- a/pkg/apis/camel/go.sum +++ b/pkg/apis/camel/go.sum @@ -415,6 +415,8 @@ k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= +sigs.k8s.io/controller-tools v0.0.0-20200528125929-5c0c6ae3b64b h1:jVf/McoMd0tHALAJrr4VgEVakuOhEYQ+m00kJTseL3s= +sigs.k8s.io/controller-tools v0.0.0-20200528125929-5c0c6ae3b64b/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI= sigs.k8s.io/controller-tools v0.3.0 h1:y3YD99XOyWaXkiF1kd41uRvfp/64teWcrEZFuHxPhJ4= sigs.k8s.io/controller-tools v0.3.0/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= diff --git a/pkg/apis/camel/v1/common_types.go b/pkg/apis/camel/v1/common_types.go index ee660f7..47d4afc 100644 --- a/pkg/apis/camel/v1/common_types.go +++ b/pkg/apis/camel/v1/common_types.go @@ -18,6 +18,8 @@ limitations under the License. package v1 import ( + "encoding/json" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -133,4 +135,7 @@ type ResourceCondition interface { } // Flow is an unstructured object representing a Camel Flow in YAML/JSON DSL -type Flow string +// +kubebuilder:validation:Type=object +type Flow struct { + json.RawMessage `json:",inline"` +} diff --git a/pkg/apis/camel/v1/zz_generated.deepcopy.go b/pkg/apis/camel/v1/zz_generated.deepcopy.go index 3cd5a5f..f8f17f1 100644 --- a/pkg/apis/camel/v1/zz_generated.deepcopy.go +++ b/pkg/apis/camel/v1/zz_generated.deepcopy.go @@ -5,6 +5,8 @@ package v1 import ( + json "encoding/json" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" @@ -618,6 +620,27 @@ func (in *FailureRecovery) DeepCopy() *FailureRecovery { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Flow) DeepCopyInto(out *Flow) { + *out = *in + if in.RawMessage != nil { + in, out := &in.RawMessage, &out.RawMessage + *out = make(json.RawMessage, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Flow. +func (in *Flow) DeepCopy() *Flow { + if in == nil { + return nil + } + out := new(Flow) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ImageTask) DeepCopyInto(out *ImageTask) { *out = *in in.ContainerTask.DeepCopyInto(&out.ContainerTask) @@ -1086,7 +1109,9 @@ func (in *IntegrationSpec) DeepCopyInto(out *IntegrationSpec) { if in.Flows != nil { in, out := &in.Flows, &out.Flows *out = make([]Flow, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } if in.Resources != nil { in, out := &in.Resources, &out.Resources diff --git a/pkg/client/camel/go.sum b/pkg/client/camel/go.sum index 29c91ed..13fbe75 100644 --- a/pkg/client/camel/go.sum +++ b/pkg/client/camel/go.sum @@ -464,6 +464,7 @@ modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03 modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= +sigs.k8s.io/controller-tools v0.0.0-20200528125929-5c0c6ae3b64b/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI= sigs.k8s.io/controller-tools v0.3.0/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go index fce05e7..5927a47 100644 --- a/pkg/cmd/run.go +++ b/pkg/cmd/run.go @@ -36,6 +36,7 @@ import ( "github.com/apache/camel-k/pkg/client" "github.com/apache/camel-k/pkg/trait" "github.com/apache/camel-k/pkg/util" + "github.com/apache/camel-k/pkg/util/flow" "github.com/apache/camel-k/pkg/util/gzip" "github.com/apache/camel-k/pkg/util/kubernetes" k8slog "github.com/apache/camel-k/pkg/util/kubernetes/log" @@ -471,7 +472,11 @@ func (o *runCmdOptions) updateIntegrationCode(c client.Client, sources []string) } if o.UseFlows && (strings.HasSuffix(source, ".yaml") || strings.HasSuffix(source, ".yml")) { - integration.Spec.AddFlows(v1.Flow(data)) + flows, err := flow.UnmarshalString(data) + if err != nil { + return nil, err + } + integration.Spec.AddFlows(flows...) } else { integration.Spec.AddSources(v1.SourceSpec{ DataSpec: v1.DataSpec{ diff --git a/pkg/trait/init.go b/pkg/trait/init.go index d060021..32236e9 100644 --- a/pkg/trait/init.go +++ b/pkg/trait/init.go @@ -25,9 +25,10 @@ import ( v1 "github.com/apache/camel-k/pkg/apis/camel/v1" "github.com/apache/camel-k/pkg/util" + "github.com/apache/camel-k/pkg/util/flow" ) -const flowsInternalSourceName = "camel-k-embedded-flow-%d.yaml" +const flowsInternalSourceName = "camel-k-embedded-flow.yaml" // Internal trait type initTrait struct { @@ -52,11 +53,15 @@ func (t *initTrait) Apply(e *Environment) error { if e.IntegrationInPhase(v1.IntegrationPhaseInitialization) { // Flows need to be turned into a generated source - for i, flow := range e.Integration.Spec.Flows { + if len(e.Integration.Spec.Flows) > 0 { + content, err := flow.Marshal(e.Integration.Spec.Flows) + if err != nil { + return err + } e.Integration.Status.AddOrReplaceGeneratedSources(v1.SourceSpec{ DataSpec: v1.DataSpec{ - Name: fmt.Sprintf(flowsInternalSourceName, i), - Content: string(flow), + Name: flowsInternalSourceName, + Content: string(content), }, }) } diff --git a/pkg/util/digest/digest.go b/pkg/util/digest/digest.go index bc4ff63..1a39800 100644 --- a/pkg/util/digest/digest.go +++ b/pkg/util/digest/digest.go @@ -31,6 +31,7 @@ import ( v1 "github.com/apache/camel-k/pkg/apis/camel/v1" "github.com/apache/camel-k/pkg/util" "github.com/apache/camel-k/pkg/util/defaults" + "github.com/apache/camel-k/pkg/util/flow" ) // ComputeForIntegration a digest of the fields that are relevant for the deployment @@ -67,8 +68,12 @@ func ComputeForIntegration(integration *v1.Integration) (string, error) { } // Integration flows - for _, flow := range integration.Spec.Flows { - if _, err := hash.Write([]byte(flow)); err != nil { + if len(integration.Spec.Flows) > 0 { + flows, err := flow.Marshal(integration.Spec.Flows) + if err != nil { + return "", err + } + if _, err := hash.Write(flows); err != nil { return "", err } } diff --git a/pkg/util/flow/flow.go b/pkg/util/flow/flow.go new file mode 100644 index 0000000..aaabd4d --- /dev/null +++ b/pkg/util/flow/flow.go @@ -0,0 +1,76 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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 flow + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + + yaml2 "gopkg.in/yaml.v2" + + "k8s.io/apimachinery/pkg/util/yaml" + + v1 "github.com/apache/camel-k/pkg/apis/camel/v1" +) + +// UnmarshalString reads flows contained in a string +func UnmarshalString(flowsString string) ([]v1.Flow, error) { + return Unmarshal(bytes.NewReader([]byte(flowsString))) +} + +// Unmarshal flows from a stream +func Unmarshal(reader io.Reader) ([]v1.Flow, error) { + buffered, err := ioutil.ReadAll(reader) + if err != nil { + return nil, err + } + var flows []v1.Flow + // Using the Kubernetes decoder to turn them into JSON before unmarshal. + // This avoids having map[interface{}]interface{} objects which are not JSON compatible. + jsonData, err := yaml.ToJSON(buffered) + if err != nil { + return nil, err + } + + if err = json.Unmarshal(jsonData, &flows); err != nil { + return nil, err + } + return flows, err +} + +// Marshal flows as byte array +func Marshal(flows []v1.Flow) ([]byte, error) { + data, err := json.Marshal(&flows) + if err != nil { + return nil, err + } + jsondata := make([]map[string]interface{}, 0) + err = json.Unmarshal(data, &jsondata) + if err != nil { + return nil, fmt.Errorf("error unmarshalling json: %v", err) + } + yamldata, err := yaml2.Marshal(&jsondata) + if err != nil { + return nil, fmt.Errorf("error marshalling to yaml: %v", err) + } + + return yamldata, nil +} diff --git a/pkg/util/flow/flow_test.go b/pkg/util/flow/flow_test.go new file mode 100644 index 0000000..4afb2ef --- /dev/null +++ b/pkg/util/flow/flow_test.go @@ -0,0 +1,53 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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 flow + +import ( + "bytes" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReadWriteYaml(t *testing.T) { + // yaml in conventional form as marshalled by the go runtime + yaml := `- from: + steps: + - to: log:info + uri: timer:tick +` + + yamlReader := bytes.NewReader([]byte(yaml)) + flows, err := Unmarshal(yamlReader) + assert.NoError(t, err) + assert.NotNil(t, flows) + assert.Len(t, flows, 1) + + flow := map[string]interface{}{} + err = json.Unmarshal(flows[0].RawMessage, &flow) + assert.NoError(t, err) + + assert.NotNil(t, flow["from"]) + assert.Nil(t, flow["xx"]) + + data, err := Marshal(flows) + assert.NoError(t, err) + assert.NotNil(t, data) + assert.Equal(t, yaml, string(data)) +}