You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by pc...@apache.org on 2022/01/07 13:48:21 UTC
[camel-k] 15/24: feat(cmd/run): convert openapi to trait
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 <pa...@gmail.com>
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{