This is an automated email from the ASF dual-hosted git repository.

pcongiusti pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit a73dce39ce2ceec9dc55492d518caac042812b67
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Tue Dec 7 16:22:04 2021 +0100

    feat(cmd/run): convert openapi to trait
---
 e2e/common/traits/files/openapi/petstore-api.yaml | 128 ++++++++++++++++++++++
 e2e/common/traits/files/openapi/petstore.groovy   |  29 +++++
 e2e/common/traits/openapi_test.go                 |  69 ++++++++++++
 pkg/apis/camel/v1/integration_types.go            |  11 +-
 pkg/cmd/run.go                                    |  92 +++++++---------
 pkg/cmd/run_test.go                               |  17 ++-
 pkg/trait/openapi.go                              |  12 +-
 7 files changed, 296 insertions(+), 62 deletions(-)

diff --git a/e2e/common/traits/files/openapi/petstore-api.yaml 
b/e2e/common/traits/files/openapi/petstore-api.yaml
new file mode 100644
index 0000000..1b6d69c
--- /dev/null
+++ b/e2e/common/traits/files/openapi/petstore-api.yaml
@@ -0,0 +1,128 @@
+# ---------------------------------------------------------------------------
+# 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.
+# ---------------------------------------------------------------------------
+
+openapi: "3.0.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+  license:
+    name: MIT
+servers:
+  - url: http://petstore.swagger.io/v1
+paths:
+  /pets:
+    get:
+      summary: List all pets
+      operationId: listPets
+      tags:
+        - pets
+      parameters:
+        - name: limit
+          in: query
+          description: How many items to return at one time (max 100)
+          required: false
+          schema:
+            type: integer
+            format: int32
+      responses:
+        '200':
+          description: A paged array of pets
+          headers:
+            x-next:
+              description: A link to the next page of responses
+              schema:
+                type: string
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Pets"
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Error"
+    post:
+      summary: Create a pet
+      operationId: createPets
+      tags:
+        - pets
+      responses:
+        '201':
+          description: Null response
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Error"
+  /pets/{petId}:
+    get:
+      summary: Info for a specific pet
+      operationId: showPetById
+      tags:
+        - pets
+      parameters:
+        - name: petId
+          in: path
+          required: true
+          description: The id of the pet to retrieve
+          schema:
+            type: string
+      responses:
+        '200':
+          description: Expected response to a valid request
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Pet"
+        default:
+          description: unexpected error
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Error"
+components:
+  schemas:
+    Pet:
+      type: object
+      required:
+        - id
+        - name
+      properties:
+        id:
+          type: integer
+          format: int64
+        name:
+          type: string
+        tag:
+          type: string
+    Pets:
+      type: array
+      items:
+        $ref: "#/components/schemas/Pet"
+    Error:
+      type: object
+      required:
+        - code
+        - message
+      properties:
+        code:
+          type: integer
+          format: int32
+        message:
+          type: string
diff --git a/e2e/common/traits/files/openapi/petstore.groovy 
b/e2e/common/traits/files/openapi/petstore.groovy
new file mode 100644
index 0000000..d382b21
--- /dev/null
+++ b/e2e/common/traits/files/openapi/petstore.groovy
@@ -0,0 +1,29 @@
+// camel-k: language=groovy
+/*
+ * 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.
+ */
+
+//
+//  kamel run --dev --name petstore --open-api examples/petstore-api.yaml 
examples/petstore.groovy
+// 
+
+from('direct:listPets')
+    .log('listPets')
+from('direct:createPets')
+    .log('createPets')
+from('direct:showPetById')
+    .log('showPetById')
+
diff --git a/e2e/common/traits/openapi_test.go 
b/e2e/common/traits/openapi_test.go
new file mode 100644
index 0000000..7318ec7
--- /dev/null
+++ b/e2e/common/traits/openapi_test.go
@@ -0,0 +1,69 @@
+//go:build integration
+// +build integration
+
+// To enable compilation of this file in Goland, go to "Settings -> Go -> 
Vendoring & Build Tags -> Custom Tags" and add "integration"
+
+/*
+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 knative
+
+import (
+       "io/ioutil"
+       "testing"
+
+       . "github.com/onsi/gomega"
+       "github.com/stretchr/testify/assert"
+
+       corev1 "k8s.io/api/core/v1"
+
+       . "github.com/apache/camel-k/e2e/support"
+)
+
+func TestOpenAPIConfigmap(t *testing.T) {
+       WithNewTestNamespace(t, func(ns string) {
+               Expect(Kamel("install", "-n", ns).Execute()).To(Succeed())
+
+               openapiContent, err := 
ioutil.ReadFile("./files/openapi/petstore-api.yaml")
+               assert.Nil(t, err)
+               var cmDataProps = make(map[string]string)
+               cmDataProps["petstore-api.yaml"] = string(openapiContent)
+               NewPlainTextConfigmap(ns, "my-openapi", cmDataProps)
+
+               Expect(Kamel(
+                       "run",
+                       "-n", ns,
+                       "--name", "petstore",
+                       "--open-api", "configmap:my-openapi",
+                       "files/openapi/petstore.groovy",
+               ).Execute()).To(Succeed())
+
+               Eventually(IntegrationPodPhase(ns, "petstore"), 
TestTimeoutLong).
+                       Should(Equal(corev1.PodRunning))
+               Eventually(Deployment(ns, "petstore"), TestTimeoutLong).
+                       Should(Not(BeNil()))
+
+               Eventually(IntegrationLogs(ns, "petstore"), TestTimeoutMedium).
+                       Should(ContainSubstring("Started listPets 
(rest://get:/v1:/pets)"))
+               Eventually(IntegrationLogs(ns, "petstore"), TestTimeoutMedium).
+                       Should(ContainSubstring("Started createPets 
(rest://post:/v1:/pets)"))
+               Eventually(IntegrationLogs(ns, "petstore"), TestTimeoutMedium).
+                       Should(ContainSubstring("Started showPetById 
(rest://get:/v1:/pets/%7BpetId%7D)"))
+
+               Expect(Kamel("delete", "--all", "-n", 
ns).Execute()).To(Succeed())
+       })
+}
diff --git a/pkg/apis/camel/v1/integration_types.go 
b/pkg/apis/camel/v1/integration_types.go
index 71b38ec..9f9686a 100644
--- a/pkg/apis/camel/v1/integration_types.go
+++ b/pkg/apis/camel/v1/integration_types.go
@@ -27,10 +27,13 @@ import (
 
 // IntegrationSpec defines the desired state of Integration
 type IntegrationSpec struct {
-       Replicas  *int32         `json:"replicas,omitempty"`
-       Sources   []SourceSpec   `json:"sources,omitempty"`
-       Flows     []Flow         `json:"flows,omitempty"`
-       Resources []ResourceSpec `json:"resources,omitempty"`
+       Replicas *int32       `json:"replicas,omitempty"`
+       Sources  []SourceSpec `json:"sources,omitempty"`
+       Flows    []Flow       `json:"flows,omitempty"`
+       // Deprecated:
+       // Use container trait (container.resources) to manage resources
+       // Use openapi trait (openapi.configmaps) to manage OpenAPIs 
specifications
+       Resources []ResourceSpec 
`json:"resources,deprecatedInFavorOf,omitempty,deprecated"`
        // Deprecated: use the IntegrationKit field
        Kit            string                  `json:"kit,omitempty"`
        IntegrationKit *corev1.ObjectReference `json:"integrationKit,omitempty"`
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index 771215e..1c1b183 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -23,7 +23,6 @@ import (
        "fmt"
        "os"
        "os/signal"
-       "path"
        "reflect"
        "regexp"
        "strings"
@@ -92,7 +91,7 @@ func newCmdRun(rootCmdOptions *RootCmdOptions) 
(*cobra.Command, *runCmdOptions)
        cmd.Flags().StringArrayP("trait", "t", nil, "Configure a trait. E.g. 
\"-t service.enabled=false\"")
        cmd.Flags().StringP("output", "o", "", "Output format. One of: 
json|yaml")
        cmd.Flags().Bool("compression", false, "Enable storage of sources and 
resources as a compressed binary blobs")
-       cmd.Flags().StringArray("open-api", nil, "Add an OpenAPI v2 spec")
+       cmd.Flags().StringArray("open-api", nil, "Add an OpenAPI spec (syntax: 
[configmap|file]:name)")
        cmd.Flags().StringArrayP("volume", "v", nil, "Mount a volume into the 
integration container. E.g \"-v pvcname:/container/path\"")
        cmd.Flags().StringArrayP("env", "e", nil, "Set an environment variable 
in the integration container. E.g \"-e MY_VAR=my-value\"")
        cmd.Flags().StringArray("property-file", nil, "[Deprecated] Bind a 
property file to the integration. E.g. \"--property-file 
integration.properties\"")
@@ -262,6 +261,13 @@ func (o *runCmdOptions) validate() error {
                }
        }
 
+       for _, openapi := range o.OpenAPIs {
+               // We support only local file and cluster configmaps
+               if !(strings.HasPrefix(openapi, "file:") || 
strings.HasPrefix(openapi, "configmap:")) {
+                       return fmt.Errorf(`invalid openapi specification "%s". 
It supports only file or configmap`, openapi)
+               }
+       }
+
        return nil
 }
 
@@ -417,7 +423,7 @@ func (o *runCmdOptions) syncIntegration(cmd *cobra.Command, 
c client.Client, sou
        files = append(files, filterFileLocation(o.Properties)...)
        files = append(files, filterFileLocation(o.BuildProperties)...)
        files = append(files, o.PropertyFiles...)
-       files = append(files, o.OpenAPIs...)
+       files = append(files, filterFileLocation(o.OpenAPIs)...)
 
        for _, s := range files {
                ok, err := isLocalAndFileExists(s)
@@ -570,42 +576,21 @@ func (o *runCmdOptions) createOrUpdateIntegration(cmd 
*cobra.Command, c client.C
        }
 
        generatedConfigmaps := make([]*corev1.ConfigMap, 0)
-       for _, res := range o.Resources {
-               config, err := resource.ParseResource(res)
-               if err != nil {
-                       return nil, err
-               }
-               // We try to autogenerate a configmap
-               maybeGenCm, err := parseConfigAndGenCm(o.Context, c, config, 
integration, o.Compression)
-               if err != nil {
-                       return nil, err
-               }
-               if maybeGenCm != nil {
-                       generatedConfigmaps = append(generatedConfigmaps, 
maybeGenCm)
-               }
-               o.Traits = append(o.Traits, convertToTrait(config.String(), 
"container.resources"))
+       resCms, err := o.parseAndConvertToTrait(c, integration, o.Resources, 
resource.ParseResource, func(c *resource.Config) string { return c.String() }, 
"container.resources")
+       if err != nil {
+               return nil, err
        }
-       for _, conf := range o.Configs {
-               config, err := resource.ParseResource(conf)
-               if err != nil {
-                       return nil, err
-               }
-               // We try to autogenerate a configmap
-               maybeGenCm, err := parseConfigAndGenCm(o.Context, c, config, 
integration, o.Compression)
-               if err != nil {
-                       return nil, err
-               }
-               if maybeGenCm != nil {
-                       generatedConfigmaps = append(generatedConfigmaps, 
maybeGenCm)
-               }
-               o.Traits = append(o.Traits, convertToTrait(config.String(), 
"container.configs"))
+       generatedConfigmaps = append(generatedConfigmaps, resCms...)
+       confCms, err := o.parseAndConvertToTrait(c, integration, o.Configs, 
resource.ParseConfig, func(c *resource.Config) string { return c.String() }, 
"container.configs")
+       if err != nil {
+               return nil, err
        }
-
-       for _, resource := range o.OpenAPIs {
-               if err = addResource(o.Context, resource, &integration.Spec, 
o.Compression, v1.ResourceTypeOpenAPI); err != nil {
-                       return nil, err
-               }
+       generatedConfigmaps = append(generatedConfigmaps, confCms...)
+       oAPICms, err := o.parseAndConvertToTrait(c, integration, o.OpenAPIs, 
resource.ParseConfig, func(c *resource.Config) string { return c.Name() }, 
"openapi.configmaps")
+       if err != nil {
+               return nil, err
        }
+       generatedConfigmaps = append(generatedConfigmaps, oAPICms...)
 
        for _, item := range o.Dependencies {
                integration.Spec.AddDependency(item)
@@ -703,21 +688,28 @@ func (o *runCmdOptions) createOrUpdateIntegration(cmd 
*cobra.Command, c client.C
        return integration, nil
 }
 
-func addResource(ctx context.Context, resourceLocation string, integrationSpec 
*v1.IntegrationSpec, enableCompression bool, resourceType v1.ResourceType) 
error {
-       if data, _, compressed, err := loadTextContent(ctx, resourceLocation, 
enableCompression); err == nil {
-               integrationSpec.AddResources(v1.ResourceSpec{
-                       DataSpec: v1.DataSpec{
-                               Name:        path.Base(resourceLocation),
-                               Content:     data,
-                               Compression: compressed,
-                       },
-                       Type: resourceType,
-               })
-       } else {
-               return err
+func (o *runCmdOptions) parseAndConvertToTrait(
+       c client.Client, integration *v1.Integration, params []string,
+       parse func(string) (*resource.Config, error),
+       convert func(*resource.Config) string,
+       traitParam string) ([]*corev1.ConfigMap, error) {
+       generatedCms := make([]*corev1.ConfigMap, 0)
+       for _, param := range params {
+               config, err := parse(param)
+               if err != nil {
+                       return nil, err
+               }
+               // We try to autogenerate a configmap
+               maybeGenCm, err := parseConfigAndGenCm(o.Context, c, config, 
integration, o.Compression)
+               if err != nil {
+                       return nil, err
+               }
+               if maybeGenCm != nil {
+                       generatedCms = append(generatedCms, maybeGenCm)
+               }
+               o.Traits = append(o.Traits, convertToTrait(convert(config), 
traitParam))
        }
-
-       return nil
+       return generatedCms, nil
 }
 
 func convertToTrait(value, traitParameter string) string {
diff --git a/pkg/cmd/run_test.go b/pkg/cmd/run_test.go
index 67c0453..aa25816 100644
--- a/pkg/cmd/run_test.go
+++ b/pkg/cmd/run_test.go
@@ -190,13 +190,22 @@ func TestRunNameFlag(t *testing.T) {
 func TestRunOpenApiFlag(t *testing.T) {
        runCmdOptions, rootCmd, _ := initializeRunCmdOptions(t)
        _, err := test.ExecuteCommand(rootCmd, cmdRun,
-               "--open-api", "oapi1",
-               "--open-api", "oapi2",
+               "--open-api", "file:oapi1",
+               "--open-api", "configmap:oapi2",
                integrationSource)
        assert.Nil(t, err)
        assert.Len(t, runCmdOptions.OpenAPIs, 2)
-       assert.Equal(t, "oapi1", runCmdOptions.OpenAPIs[0])
-       assert.Equal(t, "oapi2", runCmdOptions.OpenAPIs[1])
+       assert.Equal(t, "file:oapi1", runCmdOptions.OpenAPIs[0])
+       assert.Equal(t, "configmap:oapi2", runCmdOptions.OpenAPIs[1])
+}
+
+func TestRunOpenApiInvalidFlag(t *testing.T) {
+       _, rootCmd, _ := initializeRunCmdOptions(t)
+       _, err := test.ExecuteCommand(rootCmd, cmdRun,
+               "--open-api", "secret:oapi1",
+               "--open-api", "oapi2",
+               integrationSource)
+       assert.NotNil(t, err)
 }
 
 func TestRunOutputFlag(t *testing.T) {
diff --git a/pkg/trait/openapi.go b/pkg/trait/openapi.go
index b1ff98d..43c8791 100644
--- a/pkg/trait/openapi.go
+++ b/pkg/trait/openapi.go
@@ -100,21 +100,22 @@ func (t *openAPITrait) Apply(e *Environment) error {
        if err != nil {
                return err
        }
+       defer os.RemoveAll(tmpDir)
 
        generatedFromResources, err := t.generateFromResources(e, tmpDir)
        if err != nil {
-               return os.RemoveAll(tmpDir)
+               return err
        }
        generatedFromConfigmaps, err := t.generateFromConfigmaps(e, tmpDir)
        if err != nil {
-               return os.RemoveAll(tmpDir)
+               return err
        }
-       if generatedFromConfigmaps != nil && len(generatedFromConfigmaps) > 0 {
+       if len(generatedFromConfigmaps) > 0 {
                generatedFromResources = append(generatedFromResources, 
generatedFromConfigmaps...)
        }
        e.Integration.Status.GeneratedSources = generatedFromResources
 
-       return os.RemoveAll(tmpDir)
+       return nil
 }
 
 func (t *openAPITrait) generateFromResources(e *Environment, tmpDir string) 
([]v1.SourceSpec, error) {
@@ -136,6 +137,9 @@ func (t *openAPITrait) generateFromConfigmaps(e 
*Environment, tmpDir string) ([]
        dataSpecs := make([]v1.DataSpec, 0, len(t.Configmaps))
        for _, configmap := range t.Configmaps {
                cm := kubernetes.LookupConfigmap(e.Ctx, e.Client, 
e.Integration.Namespace, configmap)
+               if cm == nil {
+                       return nil, fmt.Errorf("could not find any configmap 
with name: %s", configmap)
+               }
                // Iterate over each configmap key which may hold a different 
OpenAPI spec
                for k, v := range cm.Data {
                        dataSpecs = append(dataSpecs, v1.DataSpec{

Reply via email to