From fc85372c9a306bdc386c552121678ea79b8fbe1a Mon Sep 17 00:00:00 2001 From: Pasquale Congiusti Date: Tue, 7 Dec 2021 11:32:02 +0100 Subject: [PATCH] feat(trait/openapi): support configmap configuration Closes #2772 --- examples/openapi/README.md | 23 +++++++++- examples/openapi/greetings.groovy | 2 +- examples/openapi/petstore.groovy | 2 +- pkg/trait/openapi.go | 73 ++++++++++++++++++++++++------- pkg/util/digest/digest.go | 8 +--- 5 files changed, 81 insertions(+), 27 deletions(-) diff --git a/examples/openapi/README.md b/examples/openapi/README.md index 41193d7fb8..c8ca3c06a7 100644 --- a/examples/openapi/README.md +++ b/examples/openapi/README.md @@ -1,3 +1,24 @@ # Open API Camel K examples -Find useful examples about how to expose an Open API specification in a Camel K integration. \ No newline at end of file +Find useful examples about how to expose an Open API specification in a Camel K integration. + +## Greetings example + +Deploy the examples running + +``` +kamel run --dev --name greetings --open-api greetings-api.json greetings.groovy +``` + +Then you can test by calling the hello endpoint, ie: + +``` +$ curl -i http://192.168.49.2:31373/camel/greetings/hello +HTTP/1.1 200 OK +Accept: */* +name: hello +User-Agent: curl/7.68.0 +transfer-encoding: chunked + +Hello from hello +``` \ No newline at end of file diff --git a/examples/openapi/greetings.groovy b/examples/openapi/greetings.groovy index a96fa64e89..90a09ce22c 100644 --- a/examples/openapi/greetings.groovy +++ b/examples/openapi/greetings.groovy @@ -17,7 +17,7 @@ */ // -// kamel run --dev --name greetings --open-api examples/greetings-api.json examples/greetings.groovy +// kamel run --dev --name greetings --open-api greetings-api.json greetings.groovy // from('direct:greeting-api') diff --git a/examples/openapi/petstore.groovy b/examples/openapi/petstore.groovy index d382b217c6..880e22b685 100644 --- a/examples/openapi/petstore.groovy +++ b/examples/openapi/petstore.groovy @@ -17,7 +17,7 @@ */ // -// kamel run --dev --name petstore --open-api examples/petstore-api.yaml examples/petstore.groovy +// kamel run --dev --name petstore --open-api petstore-api.yaml petstore.groovy // from('direct:listPets') diff --git a/pkg/trait/openapi.go b/pkg/trait/openapi.go index 09d7ab5943..b1ff98db27 100644 --- a/pkg/trait/openapi.go +++ b/pkg/trait/openapi.go @@ -27,8 +27,6 @@ import ( "strconv" "strings" - "go.uber.org/multierr" - "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" @@ -52,6 +50,8 @@ import ( // +camel-k:trait=openapi. type openAPITrait struct { BaseTrait `property:",squash"` + // The configmaps holding the spec of the OpenAPI + Configmaps []string `property:"configmaps" json:"configmaps,omitempty"` } func newOpenAPITrait() Trait { @@ -85,6 +85,10 @@ func (t *openAPITrait) Configure(e *Environment) (bool, error) { } } + if t.Configmaps != nil { + return e.IntegrationInPhase(v1.IntegrationPhaseInitialization), nil + } + return false, nil } @@ -97,26 +101,63 @@ func (t *openAPITrait) Apply(e *Environment) error { return err } - for i, resource := range e.Integration.Spec.Resources { + generatedFromResources, err := t.generateFromResources(e, tmpDir) + if err != nil { + return os.RemoveAll(tmpDir) + } + generatedFromConfigmaps, err := t.generateFromConfigmaps(e, tmpDir) + if err != nil { + return os.RemoveAll(tmpDir) + } + if generatedFromConfigmaps != nil && len(generatedFromConfigmaps) > 0 { + generatedFromResources = append(generatedFromResources, generatedFromConfigmaps...) + } + e.Integration.Status.GeneratedSources = generatedFromResources + + return os.RemoveAll(tmpDir) +} + +func (t *openAPITrait) generateFromResources(e *Environment, tmpDir string) ([]v1.SourceSpec, error) { + dataSpecs := make([]v1.DataSpec, 0, len(e.Integration.Spec.Resources)) + for _, resource := range e.Integration.Spec.Resources { if resource.Type != v1.ResourceTypeOpenAPI { continue } if resource.Name == "" { - return multierr.Append( - fmt.Errorf("no name defined for the openapi resource: %v", resource), - os.RemoveAll(tmpDir)) + return nil, fmt.Errorf("no name defined for the openapi resource: %v", resource) + } + dataSpecs = append(dataSpecs, resource.DataSpec) + } + + return t.generateFromDataSpecs(e, tmpDir, dataSpecs) +} + +func (t *openAPITrait) generateFromConfigmaps(e *Environment, tmpDir string) ([]v1.SourceSpec, error) { + dataSpecs := make([]v1.DataSpec, 0, len(t.Configmaps)) + for _, configmap := range t.Configmaps { + cm := kubernetes.LookupConfigmap(e.Ctx, e.Client, e.Integration.Namespace, configmap) + // Iterate over each configmap key which may hold a different OpenAPI spec + for k, v := range cm.Data { + dataSpecs = append(dataSpecs, v1.DataSpec{ + Name: k, + Content: v, + Compression: false, + }) } + } - generatedContentName := fmt.Sprintf("%s-openapi-%03d", e.Integration.Name, i) + return t.generateFromDataSpecs(e, tmpDir, dataSpecs) +} +func (t *openAPITrait) generateFromDataSpecs(e *Environment, tmpDir string, specs []v1.DataSpec) ([]v1.SourceSpec, error) { + generatedSources := make([]v1.SourceSpec, 0, len(e.Integration.Status.GeneratedSources)) + for i, resource := range specs { + generatedContentName := fmt.Sprintf("%s-openapi-%03d", e.Integration.Name, i) + generatedSourceName := strings.TrimSuffix(resource.Name, filepath.Ext(resource.Name)) + ".xml" // Generate configmap or reuse existing one if err := t.generateOpenAPIConfigMap(e, resource, tmpDir, generatedContentName); err != nil { - return errors.Wrapf(err, "cannot generate configmap for openapi resource %s", resource.Name) + return nil, errors.Wrapf(err, "cannot generate configmap for openapi resource %s", resource.Name) } - - generatedSourceName := strings.TrimSuffix(resource.Name, filepath.Ext(resource.Name)) + ".xml" - generatedSources := make([]v1.SourceSpec, 0, len(e.Integration.Status.GeneratedSources)) - if e.Integration.Status.GeneratedSources != nil { // Filter out the previously generated source for _, x := range e.Integration.Status.GeneratedSources { @@ -135,14 +176,12 @@ func (t *openAPITrait) Apply(e *Environment) error { }, Language: v1.LanguageXML, }) - - e.Integration.Status.GeneratedSources = generatedSources } - return os.RemoveAll(tmpDir) + return generatedSources, nil } -func (t *openAPITrait) generateOpenAPIConfigMap(e *Environment, resource v1.ResourceSpec, tmpDir, generatedContentName string) error { +func (t *openAPITrait) generateOpenAPIConfigMap(e *Environment, resource v1.DataSpec, tmpDir, generatedContentName string) error { cm := corev1.ConfigMap{} key := client.ObjectKey{ Namespace: e.Integration.Namespace, @@ -176,7 +215,7 @@ func (t *openAPITrait) generateOpenAPIConfigMap(e *Environment, resource v1.Reso return t.createNewOpenAPIConfigMap(e, resource, tmpDir, generatedContentName) } -func (t *openAPITrait) createNewOpenAPIConfigMap(e *Environment, resource v1.ResourceSpec, tmpDir, generatedContentName string) error { +func (t *openAPITrait) createNewOpenAPIConfigMap(e *Environment, resource v1.DataSpec, tmpDir, generatedContentName string) error { tmpDir = path.Join(tmpDir, generatedContentName) err := os.MkdirAll(tmpDir, os.ModePerm) if err != nil { diff --git a/pkg/util/digest/digest.go b/pkg/util/digest/digest.go index 4d999a11cb..566a8738d9 100644 --- a/pkg/util/digest/digest.go +++ b/pkg/util/digest/digest.go @@ -160,7 +160,7 @@ func ComputeForIntegrationKit(kit *v1.IntegrationKit) (string, error) { } // ComputeForResource returns a digest for the specific resource. -func ComputeForResource(res v1.ResourceSpec) (string, error) { +func ComputeForResource(res v1.DataSpec) (string, error) { hash := sha256.New() // Operator version is relevant if _, err := hash.Write([]byte(defaults.Version)); err != nil { @@ -173,18 +173,12 @@ func ComputeForResource(res v1.ResourceSpec) (string, error) { if _, err := hash.Write([]byte(res.Name)); err != nil { return "", err } - if _, err := hash.Write([]byte(res.Type)); err != nil { - return "", err - } if _, err := hash.Write([]byte(res.ContentKey)); err != nil { return "", err } if _, err := hash.Write([]byte(res.ContentRef)); err != nil { return "", err } - if _, err := hash.Write([]byte(res.MountPath)); err != nil { - return "", err - } if _, err := hash.Write([]byte(strconv.FormatBool(res.Compression))); err != nil { return "", err }