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 d0a228c feature(knative): support configmap and secrets d0a228c is described below commit d0a228c4e8e66af51230384ce63257a45aaffb0c Author: lburgazzoli <lburgazz...@gmail.com> AuthorDate: Mon Feb 25 22:08:04 2019 +0100 feature(knative): support configmap and secrets --- Gopkg.lock | 9 + docs/traits.adoc | 16 + pkg/builder/kaniko/publisher.go | 10 +- pkg/controller/integration/build_image.go | 40 +- .../integration/build_image_failure_recovery.go | 3 +- .../integrationcontext/build_failure_recovery.go | 1 - pkg/trait/builder_test.go | 1 + pkg/trait/deployer.go | 37 + pkg/trait/deployment.go | 340 +-------- pkg/trait/environment_test.go | 3 + pkg/trait/knative_service.go | 169 ++--- pkg/trait/knative_service_env.go | 156 ++++ ...knative_test.go => knative_service_env_test.go} | 14 +- pkg/trait/knative_service_vol.go | 43 ++ pkg/trait/knative_service_vol_test.go | 272 +++++++ pkg/trait/service.go | 3 +- pkg/trait/{catalog.go => trait_catalog.go} | 6 + pkg/trait/trait_test.go | 1 + pkg/trait/trait_types.go | 526 +++++++++++++ pkg/trait/types.go | 234 ------ pkg/util/kubernetes/collection.go | 16 + pkg/util/kubernetes/resolver.go | 120 +++ pkg/util/kubernetes/util.go | 25 + pkg/util/source/util.go | 41 - pkg/util/test/assertions.go | 46 ++ pkg/util/util.go | 45 ++ vendor/github.com/magiconair/properties/LICENSE | 25 + .../magiconair/properties/assert/assert.go | 90 +++ vendor/github.com/magiconair/properties/decode.go | 289 +++++++ vendor/github.com/magiconair/properties/doc.go | 156 ++++ .../github.com/magiconair/properties/integrate.go | 34 + vendor/github.com/magiconair/properties/lex.go | 407 ++++++++++ vendor/github.com/magiconair/properties/load.go | 292 ++++++++ vendor/github.com/magiconair/properties/parser.go | 95 +++ .../github.com/magiconair/properties/properties.go | 833 +++++++++++++++++++++ .../github.com/magiconair/properties/rangecheck.go | 31 + 36 files changed, 3699 insertions(+), 730 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 6cd009c..72f61a1 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -383,6 +383,14 @@ version = "v1.0.1" [[projects]] + digest = "1:4244266b65ea535b8ebd109a327720821707b59f9a37bda738946d52ec69442d" + name = "github.com/magiconair/properties" + packages = ["."] + pruneopts = "NT" + revision = "c2353362d570a7bfa228149c62842019201cfb71" + version = "v1.8.0" + +[[projects]] branch = "master" digest = "1:4925ec3736ef6c299cfcf61597782e3d66ec13114f7476019d04c742a7be55d0" name = "github.com/mailru/easyjson" @@ -1104,6 +1112,7 @@ "github.com/jpillora/backoff", "github.com/knative/eventing/pkg/apis/eventing/v1alpha1", "github.com/knative/serving/pkg/apis/serving/v1alpha1", + "github.com/magiconair/properties", "github.com/mitchellh/mapstructure", "github.com/openshift/api/apps/v1", "github.com/openshift/api/authorization/v1", diff --git a/docs/traits.adoc b/docs/traits.adoc index 1d3baa8..d976d3d 100644 --- a/docs/traits.adoc +++ b/docs/traits.adoc @@ -62,6 +62,22 @@ The following is a list of common traits that can be configured by the end users + It's enabled by default. +| deployer +| Kubernetes, OpenShift +| Configure deployer behavior. + + + + + It's enabled by default. + + +[cols="m,"] +!=== + +! deployer.container-image +! Generates a container image for the Integration that includes the sources and resources in the generated images instead of mounting them to the pod. + +!=== + | deployment | Kubernetes, OpenShift | Creates a standard Kubernetes deployment for running the integration. diff --git a/pkg/builder/kaniko/publisher.go b/pkg/builder/kaniko/publisher.go index fd7b5b4..b897b74 100644 --- a/pkg/builder/kaniko/publisher.go +++ b/pkg/builder/kaniko/publisher.go @@ -18,14 +18,15 @@ limitations under the License. package kaniko import ( + "fmt" "io/ioutil" "path" "time" "github.com/apache/camel-k/pkg/builder" + "github.com/apache/camel-k/pkg/util/kubernetes" "github.com/apache/camel-k/pkg/util/tar" - "github.com/apache/camel-k/pkg/util/kubernetes" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" @@ -81,7 +82,9 @@ func Publisher(ctx *builder.Context) error { "--cache", "--cache-dir=/workspace/cache", } + args := append(baseArgs, "--insecure") + args = append(args, "--insecure-pull") if ctx.Request.Platform.Build.PushSecret != "" { volumes = append(volumes, corev1.Volume{ @@ -156,8 +159,9 @@ func Publisher(ctx *builder.Context) error { if val, ok := obj.(*corev1.Pod); ok { if val.Status.Phase == corev1.PodSucceeded { return true, nil - } else if val.Status.Phase == corev1.PodFailed { - return false, errors.New("build failed") + } + if val.Status.Phase == corev1.PodFailed { + return false, fmt.Errorf("build failed: %s", val.Status.Message) } } return false, nil diff --git a/pkg/controller/integration/build_image.go b/pkg/controller/integration/build_image.go index 136b33e..3b9b565 100644 --- a/pkg/controller/integration/build_image.go +++ b/pkg/controller/integration/build_image.go @@ -26,8 +26,6 @@ import ( "github.com/apache/camel-k/pkg/util/cancellable" - "github.com/apache/camel-k/pkg/util/source" - "github.com/pkg/errors" "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" @@ -36,7 +34,6 @@ import ( "github.com/apache/camel-k/pkg/trait" "github.com/apache/camel-k/pkg/util/digest" "github.com/apache/camel-k/pkg/util/kubernetes" - corev1 "k8s.io/api/core/v1" ) // NewBuildImageAction create an action that handles integration image build @@ -130,12 +127,14 @@ func (action *buildImageAction) handleBuildImageSubmitted(ctx context.Context, i BuildDir: env.BuildDir, Platform: env.Platform.Spec, Image: ictx.Status.Image, - // Sources are added as part of the standard deployment bits - Resources: make([]builder.Resource, 0, len(integration.Spec.Sources)), + Resources: make([]builder.Resource, 0, len(integration.Spec.Sources)), } - // inline resources so they are copied over the generated + // inline source and resources so they are copied over the generated // container image + if err := action.inlineSources(ctx, integration, &r, env); err != nil { + return err + } if err := action.inlineResources(ctx, integration, &r, env); err != nil { return err } @@ -213,27 +212,32 @@ func (action *buildImageAction) handleBuildStateChange(ctx context.Context, res return nil } -func (action *buildImageAction) inlineResources(ctx context.Context, integration *v1alpha1.Integration, r *builder.Request, e *trait.Environment) error { - sources, err := source.Resolve(integration.Sources(), func(name string) (*corev1.ConfigMap, error) { - cm := e.Resources.GetConfigMap(func(cm *corev1.ConfigMap) bool { - return cm.Name == name - }) +func (action *buildImageAction) inlineSources(ctx context.Context, integration *v1alpha1.Integration, r *builder.Request, e *trait.Environment) error { + sources, err := kubernetes.ResolveIntegrationSources(ctx, action.client, integration, e.Resources) + if err != nil { + return err + } - if cm != nil { - return cm, nil - } + for _, data := range sources { + r.Resources = append(r.Resources, builder.Resource{ + Content: []byte(data.Content), + Target: path.Join("sources", data.Name), + }) + } - return kubernetes.GetConfigMap(ctx, action.client, name, integration.Namespace) - }) + return nil +} +func (action *buildImageAction) inlineResources(ctx context.Context, integration *v1alpha1.Integration, r *builder.Request, e *trait.Environment) error { + resources, err := kubernetes.ResolveIntegrationResources(ctx, action.client, integration, e.Resources) if err != nil { return err } - for _, data := range sources { + for _, data := range resources { r.Resources = append(r.Resources, builder.Resource{ Content: []byte(data.Content), - Target: path.Join("sources", data.Name), + Target: path.Join("resources", data.Name), }) } diff --git a/pkg/controller/integration/build_image_failure_recovery.go b/pkg/controller/integration/build_image_failure_recovery.go index 920c8aa..c32ce3e 100644 --- a/pkg/controller/integration/build_image_failure_recovery.go +++ b/pkg/controller/integration/build_image_failure_recovery.go @@ -92,8 +92,7 @@ func (action *errorRecoveryAction) Handle(ctx context.Context, integration *v1al target.Status.Failure.Recovery.Attempt = integration.Status.Failure.Recovery.Attempt + 1 target.Status.Failure.Recovery.AttemptTime = metav1.Now() - action.L.Info("Recovery attempt (%d/%d)", - integration.Name, + action.L.Infof("Recovery attempt (%d/%d)", target.Status.Failure.Recovery.Attempt, target.Status.Failure.Recovery.AttemptMax, ) diff --git a/pkg/controller/integrationcontext/build_failure_recovery.go b/pkg/controller/integrationcontext/build_failure_recovery.go index a32a751..f719fe3 100644 --- a/pkg/controller/integrationcontext/build_failure_recovery.go +++ b/pkg/controller/integrationcontext/build_failure_recovery.go @@ -93,7 +93,6 @@ func (action *errorRecoveryAction) Handle(ctx context.Context, ictx *v1alpha1.In target.Status.Failure.Recovery.AttemptTime = metav1.Now() action.L.Info("Recovery attempt (%d/%d)", - ictx.Name, target.Status.Failure.Recovery.Attempt, target.Status.Failure.Recovery.AttemptMax, ) diff --git a/pkg/trait/builder_test.go b/pkg/trait/builder_test.go index 1bd7da6..558d83d 100644 --- a/pkg/trait/builder_test.go +++ b/pkg/trait/builder_test.go @@ -126,6 +126,7 @@ func createBuilderTestEnv(cluster v1alpha1.IntegrationPlatformCluster, strategy return &Environment{ C: context.TODO(), CamelCatalog: c, + Catalog: NewCatalog(context.TODO(), nil), Integration: &v1alpha1.Integration{ ObjectMeta: metav1.ObjectMeta{ Name: "test", diff --git a/pkg/trait/deployer.go b/pkg/trait/deployer.go new file mode 100644 index 0000000..459ec30 --- /dev/null +++ b/pkg/trait/deployer.go @@ -0,0 +1,37 @@ +/* +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 trait + +type deployerTrait struct { + BaseTrait `property:",squash"` + ContainerImage bool `property:"container-image"` +} + +func newDeployerTrait() *deployerTrait { + return &deployerTrait{ + BaseTrait: newBaseTrait("deployer"), + } +} + +func (t *deployerTrait) Configure(e *Environment) (bool, error) { + return true, nil +} + +func (t *deployerTrait) Apply(e *Environment) error { + return nil +} diff --git a/pkg/trait/deployment.go b/pkg/trait/deployment.go index f91d002..7aebbee 100644 --- a/pkg/trait/deployment.go +++ b/pkg/trait/deployment.go @@ -18,24 +18,19 @@ limitations under the License. package trait import ( - "fmt" - "path" - "strconv" "strings" "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" "github.com/apache/camel-k/pkg/util/envvar" - "github.com/apache/camel-k/pkg/util/kubernetes" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" ) type deploymentTrait struct { - BaseTrait `property:",squash"` - ContainerImage bool `property:"container-image"` + BaseTrait `property:",squash"` + deployer deployerTrait } func newDeploymentTrait() *deploymentTrait { @@ -49,32 +44,38 @@ func (t *deploymentTrait) Configure(e *Environment) (bool, error) { return false, nil } + enabled := false + if e.IntegrationInPhase(v1alpha1.IntegrationPhaseDeploying) { // // Don't deploy when a different strategy is needed (e.g. Knative) // - var strategy ControllerStrategy - var err error - if strategy, err = e.DetermineControllerStrategy(t.ctx, t.client); err != nil { + strategy, err := e.DetermineControllerStrategy(t.ctx, t.client) + if err != nil { return false, err } - return strategy == ControllerStrategyDeployment, nil + + enabled = strategy == ControllerStrategyDeployment + } else if e.IntegrationContextInPhase(v1alpha1.IntegrationContextPhaseReady) && + e.IntegrationInPhase(v1alpha1.IntegrationPhaseBuildingContext, v1alpha1.IntegrationPhaseResolvingContext) { + enabled = true } - if e.IntegrationContextInPhase(v1alpha1.IntegrationContextPhaseReady) && - (e.IntegrationInPhase(v1alpha1.IntegrationPhaseBuildingContext) || - e.IntegrationInPhase(v1alpha1.IntegrationPhaseResolvingContext)) { - return true, nil + if enabled { + dt := e.Catalog.GetTrait("deployer") + if dt != nil { + t.deployer = *dt.(*deployerTrait) + } } - return false, nil + return enabled, nil } func (t *deploymentTrait) Apply(e *Environment) error { if e.IntegrationContextInPhase(v1alpha1.IntegrationContextPhaseReady) && - (e.IntegrationInPhase(v1alpha1.IntegrationPhaseBuildingContext) || - e.IntegrationInPhase(v1alpha1.IntegrationPhaseResolvingContext)) { - if t.ContainerImage { + e.IntegrationInPhase(v1alpha1.IntegrationPhaseBuildingContext, v1alpha1.IntegrationPhaseResolvingContext) { + + if t.deployer.ContainerImage { // trigger container image build e.Integration.Status.Phase = v1alpha1.IntegrationPhaseBuildImageSubmitted } else { @@ -84,7 +85,7 @@ func (t *deploymentTrait) Apply(e *Environment) error { } if e.Integration != nil && e.Integration.Status.Phase == v1alpha1.IntegrationPhaseDeploying { - e.Resources.AddAll(t.getConfigMapsFor(e)) + e.Resources.AddAll(e.ComputeConfigMaps(t.deployer.ContainerImage)) e.Resources.Add(t.getDeploymentFor(e)) } @@ -93,153 +94,12 @@ func (t *deploymentTrait) Apply(e *Environment) error { // ********************************** // -// ConfigMap -// -// ********************************** - -func (t *deploymentTrait) getConfigMapsFor(e *Environment) []runtime.Object { - sources := e.Integration.Sources() - maps := make([]runtime.Object, 0, len(sources)+1) - - // combine properties of integration with context, integration - // properties have the priority - properties := "" - - VisitKeyValConfigurations("property", e.IntegrationContext, e.Integration, func(key string, val string) { - properties += fmt.Sprintf("%s=%s\n", key, val) - }) - - maps = append( - maps, - &corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: e.Integration.Name + "-properties", - Namespace: e.Integration.Namespace, - Labels: map[string]string{ - "camel.apache.org/integration": e.Integration.Name, - }, - }, - Data: map[string]string{ - "properties": properties, - }, - }, - ) - - if !t.ContainerImage { - - // do not create 'source' or 'resource' ConfigMap if a docker images for deployment - // is required - - for i, s := range sources { - if s.ContentRef != "" { - continue - } - - cm := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-source-%03d", e.Integration.Name, i), - Namespace: e.Integration.Namespace, - Labels: map[string]string{ - "camel.apache.org/integration": e.Integration.Name, - }, - Annotations: map[string]string{ - "camel.apache.org/source.language": string(s.InferLanguage()), - "camel.apache.org/source.name": s.Name, - "camel.apache.org/source.compression": strconv.FormatBool(s.Compression), - }, - }, - Data: map[string]string{ - "content": s.Content, - }, - } - - maps = append(maps, &cm) - } - - for i, s := range e.Integration.Spec.Resources { - if s.Type != v1alpha1.ResourceTypeData { - continue - } - - cm := corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-resource-%03d", e.Integration.Name, i), - Namespace: e.Integration.Namespace, - Labels: map[string]string{ - "camel.apache.org/integration": e.Integration.Name, - }, - Annotations: map[string]string{ - "camel.apache.org/resource.name": s.Name, - "camel.apache.org/resource.compression": strconv.FormatBool(s.Compression), - }, - }, - Data: map[string]string{ - "content": s.Content, - }, - } - - maps = append(maps, &cm) - } - } - - return maps -} - -// ********************************** -// // Deployment // // ********************************** -func (t *deploymentTrait) getSources(e *Environment) []string { - sources := e.Integration.Sources() - paths := make([]string, 0, len(sources)) - - for _, s := range sources { - root := "/etc/camel/sources" - - if t.ContainerImage { - - // assume sources are copied over the standard deployments folder - root = "/deployments/sources" - } - - srcName := strings.TrimPrefix(s.Name, "/") - src := path.Join(root, srcName) - src = "file:" + src - - params := make([]string, 0) - if s.InferLanguage() != "" { - params = append(params, "language="+string(s.InferLanguage())) - } - if s.Compression { - params = append(params, "compression=true") - } - - if len(params) > 0 { - src = fmt.Sprintf("%s?%s", src, strings.Join(params, "&")) - } - - paths = append(paths, src) - } - - return paths -} - func (t *deploymentTrait) getDeploymentFor(e *Environment) *appsv1.Deployment { - paths := t.getSources(e) + paths := e.ComputeSourcesURI(t.deployer.ContainerImage) environment := make([]corev1.EnvVar, 0) // combine Environment of integration with context, integration @@ -312,157 +172,11 @@ func (t *deploymentTrait) getDeploymentFor(e *Environment) *appsv1.Deployment { }, } - // - // Volumes :: Setup - // - - vols := make([]corev1.Volume, 0) - mnts := make([]corev1.VolumeMount, 0) - - // - // Volumes :: Properties - // - - vols = append(vols, corev1.Volume{ - Name: "integration-properties", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: e.Integration.Name + "-properties", - }, - Items: []corev1.KeyToPath{ - { - Key: "properties", - Path: "application.properties", - }, - }, - }, - }, - }) - - mnts = append(mnts, corev1.VolumeMount{ - Name: "integration-properties", - MountPath: "/etc/camel/conf", - }) - - // - // Volumes :: Sources - // - - if !t.ContainerImage { - // We can configure the operator to generate a container images that include - // integration sources instead of mounting it at runtime and in such case we - // do not need to mount any 'source' ConfigMap to the pod - - for i, s := range e.Integration.Sources() { - cmName := fmt.Sprintf("%s-source-%03d", e.Integration.Name, i) - refName := fmt.Sprintf("integration-source-%03d", i) - resName := strings.TrimPrefix(s.Name, "/") - - if s.ContentRef != "" { - cmName = s.ContentRef - } - - vols = append(vols, corev1.Volume{ - Name: refName, - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: cmName, - }, - }, - }, - }) - - mnts = append(mnts, corev1.VolumeMount{ - Name: refName, - MountPath: path.Join("/etc/camel/sources", resName), - SubPath: "content", - }) - } - - for i, r := range e.Integration.Spec.Resources { - if r.Type != v1alpha1.ResourceTypeData { - continue - } - - cmName := fmt.Sprintf("%s-resource-%03d", e.Integration.Name, i) - refName := fmt.Sprintf("integration-resource-%03d", i) - resName := strings.TrimPrefix(r.Name, "/") - - vols = append(vols, corev1.Volume{ - Name: refName, - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: cmName, - }, - }, - }, - }) - - mnts = append(mnts, corev1.VolumeMount{ - Name: refName, - MountPath: path.Join("/etc/camel/resources", resName), - SubPath: "content", - }) - } - } - - // - // Volumes :: Additional ConfigMaps - // - - VisitConfigurations("configmap", e.IntegrationContext, e.Integration, func(cmName string) { - refName := kubernetes.SanitizeLabel(cmName) - fileName := "integration-cm-" + strings.ToLower(cmName) - - vols = append(vols, corev1.Volume{ - Name: refName, - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: cmName, - }, - }, - }, - }) - - mnts = append(mnts, corev1.VolumeMount{ - Name: refName, - MountPath: path.Join("/etc/camel/conf.d", fileName), - }) - }) - - // - // Volumes :: Additional Secrets - // - - VisitConfigurations("secret", e.IntegrationContext, e.Integration, func(secretName string) { - refName := kubernetes.SanitizeLabel(secretName) - fileName := "integration-secret-" + strings.ToLower(secretName) - - vols = append(vols, corev1.Volume{ - Name: refName, - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: secretName, - }, - }, - }) - - mnts = append(mnts, corev1.VolumeMount{ - Name: refName, - MountPath: path.Join("/etc/camel/conf.d", fileName), - }) - }) - - // - // Volumes - // - - deployment.Spec.Template.Spec.Volumes = vols - deployment.Spec.Template.Spec.Containers[0].VolumeMounts = mnts + e.ConfigureVolumesAndMounts( + t.deployer.ContainerImage, + &deployment.Spec.Template.Spec.Volumes, + &deployment.Spec.Template.Spec.Containers[0].VolumeMounts, + ) return &deployment } diff --git a/pkg/trait/environment_test.go b/pkg/trait/environment_test.go index 1f05ede..8e3340f 100644 --- a/pkg/trait/environment_test.go +++ b/pkg/trait/environment_test.go @@ -37,6 +37,7 @@ func TestDefaultEnvironment(t *testing.T) { env := Environment{ CamelCatalog: catalog, + Catalog: NewCatalog(context.TODO(), nil), Integration: &v1alpha1.Integration{ Status: v1alpha1.IntegrationStatus{ Phase: v1alpha1.IntegrationPhaseDeploying, @@ -89,6 +90,7 @@ func TestEnabledContainerMetaDataEnvVars(t *testing.T) { env := Environment{ CamelCatalog: c, + Catalog: NewCatalog(context.TODO(), nil), Integration: &v1alpha1.Integration{ Status: v1alpha1.IntegrationStatus{ Phase: v1alpha1.IntegrationPhaseDeploying, @@ -148,6 +150,7 @@ func TestDisabledContainerMetaDataEnvVars(t *testing.T) { env := Environment{ CamelCatalog: c, + Catalog: NewCatalog(context.TODO(), nil), Integration: &v1alpha1.Integration{ Status: v1alpha1.IntegrationStatus{ Phase: v1alpha1.IntegrationPhaseDeploying, diff --git a/pkg/trait/knative_service.go b/pkg/trait/knative_service.go index 2f16c84..b8df091 100644 --- a/pkg/trait/knative_service.go +++ b/pkg/trait/knative_service.go @@ -18,9 +18,9 @@ limitations under the License. package trait import ( - "fmt" "strconv" - "strings" + + "github.com/apache/camel-k/pkg/util/kubernetes" "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" "github.com/apache/camel-k/pkg/metadata" @@ -40,13 +40,15 @@ const ( ) type knativeServiceTrait struct { - BaseTrait `property:",squash"` - Class string `property:"autoscaling-class"` - Metric string `property:"autoscaling-metric"` - Target *int `property:"autoscaling-target"` - MinScale *int `property:"min-scale"` - MaxScale *int `property:"max-scale"` - Auto *bool `property:"auto"` + BaseTrait `property:",squash"` + Class string `property:"autoscaling-class"` + Metric string `property:"autoscaling-metric"` + Target *int `property:"autoscaling-target"` + MinScale *int `property:"min-scale"` + MaxScale *int `property:"max-scale"` + Auto *bool `property:"auto"` + ConfigurationType string `property:"configuration-type"` + deployer deployerTrait } func newKnativeServiceTrait() *knativeServiceTrait { @@ -64,9 +66,8 @@ func (t *knativeServiceTrait) Configure(e *Environment) (bool, error) { return false, nil } - var strategy ControllerStrategy - var err error - if strategy, err = e.DetermineControllerStrategy(t.ctx, t.client); err != nil { + strategy, err := e.DetermineControllerStrategy(t.ctx, t.client) + if err != nil { return false, err } if strategy != ControllerStrategyKnativeService { @@ -87,12 +88,12 @@ func (t *knativeServiceTrait) Configure(e *Environment) (bool, error) { if t.Auto == nil || *t.Auto { // Check the right value for minScale, as not all services are allowed to scale down to 0 if t.MinScale == nil { - var sources []v1alpha1.SourceSpec - if sources, err = e.ResolveSources(t.ctx, t.client); err != nil { + sources, err := kubernetes.ResolveIntegrationSources(t.ctx, t.client, e.Integration, e.Resources) + if err != nil { return false, err } - meta := metadata.ExtractAll(e.CamelCatalog, sources) + meta := metadata.ExtractAll(e.CamelCatalog, sources) if !meta.RequiresHTTPService || !meta.PassiveEndpoints { single := 1 t.MinScale = &single @@ -100,6 +101,11 @@ func (t *knativeServiceTrait) Configure(e *Environment) (bool, error) { } } + dt := e.Catalog.GetTrait("deployer") + if dt != nil { + t.deployer = *dt.(*deployerTrait) + } + return true, nil } @@ -109,107 +115,15 @@ func (t *knativeServiceTrait) Apply(e *Environment) error { return err } + maps := e.ComputeConfigMaps(t.deployer.ContainerImage) + e.Resources.Add(svc) + e.Resources.AddAll(maps) return nil } func (t *knativeServiceTrait) getServiceFor(e *Environment) (*serving.Service, error) { - // combine properties of integration with context, integration - // properties have the priority - properties := "" - - VisitKeyValConfigurations("property", e.IntegrationContext, e.Integration, func(key string, val string) { - properties += fmt.Sprintf("%s=%s\n", key, val) - }) - - environment := make([]corev1.EnvVar, 0) - - // combine Environment of integration with context, integration - // Environment has the priority - VisitKeyValConfigurations("env", e.IntegrationContext, e.Integration, func(key string, value string) { - envvar.SetVal(&environment, key, value) - }) - - sourcesSpecs, err := e.ResolveSources(t.ctx, t.client) - if err != nil { - return nil, err - } - - sources := make([]string, 0, len(e.Integration.Spec.Sources)) - for i, s := range sourcesSpecs { - if s.Content == "" { - t.L.Debug("Source %s has and empty content", s.Name) - } - - envName := fmt.Sprintf("CAMEL_K_ROUTE_%03d", i) - envvar.SetVal(&environment, envName, s.Content) - - params := make([]string, 0) - params = append(params, "name="+s.Name) - - if s.InferLanguage() != "" { - params = append(params, "language="+string(s.InferLanguage())) - } - if s.Compression { - params = append(params, "compression=true") - } - - src := fmt.Sprintf("env:%s", envName) - if len(params) > 0 { - src = fmt.Sprintf("%s?%s", src, strings.Join(params, "&")) - } - - sources = append(sources, src) - } - - for i, r := range e.Integration.Spec.Resources { - if r.Type != v1alpha1.ResourceTypeData { - continue - } - - envName := fmt.Sprintf("CAMEL_K_RESOURCE_%03d", i) - envvar.SetVal(&environment, envName, r.Content) - - params := make([]string, 0) - if r.Compression { - params = append(params, "compression=true") - } - - envValue := fmt.Sprintf("env:%s", envName) - if len(params) > 0 { - envValue = fmt.Sprintf("%s?%s", envValue, strings.Join(params, "&")) - } - - envName = r.Name - envName = strings.ToUpper(envName) - envName = strings.Replace(envName, "-", "_", -1) - envName = strings.Replace(envName, ".", "_", -1) - envName = strings.Replace(envName, " ", "_", -1) - - envvar.SetVal(&environment, envName, envValue) - } - - // set env vars needed by the runtime - envvar.SetVal(&environment, "JAVA_MAIN_CLASS", "org.apache.camel.k.jvm.Application") - - // camel-k runtime - envvar.SetVal(&environment, "CAMEL_K_ROUTES", strings.Join(sources, ",")) - envvar.SetVal(&environment, "CAMEL_K_CONF", "env:CAMEL_K_PROPERTIES") - envvar.SetVal(&environment, "CAMEL_K_PROPERTIES", properties) - - // add a dummy env var to trigger deployment if everything but the code - // has been changed - envvar.SetVal(&environment, "CAMEL_K_DIGEST", e.Integration.Status.Digest) - - // optimizations - envvar.SetVal(&environment, "AB_JOLOKIA_OFF", True) - - // add env vars from traits - for _, envVar := range e.EnvVars { - envvar.SetVar(&environment, envVar) - } - labels := map[string]string{ "camel.apache.org/integration": e.Integration.Name, } @@ -260,7 +174,7 @@ func (t *knativeServiceTrait) getServiceFor(e *Environment) (*serving.Service, e ServiceAccountName: e.Integration.Spec.ServiceAccountName, Container: corev1.Container{ Image: e.Integration.Status.Image, - Env: environment, + Env: make([]corev1.EnvVar, 0), }, }, }, @@ -269,5 +183,38 @@ func (t *knativeServiceTrait) getServiceFor(e *Environment) (*serving.Service, e }, } + environment := &svc.Spec.RunLatest.Configuration.RevisionTemplate.Spec.Container.Env + + // combine Environment of integration with context, integration + // Environment has the priority + VisitKeyValConfigurations("env", e.IntegrationContext, e.Integration, func(key string, value string) { + envvar.SetVal(environment, key, value) + }) + + // set env vars needed by the runtime + envvar.SetVal(environment, "JAVA_MAIN_CLASS", "org.apache.camel.k.jvm.Application") + + // add a dummy env var to trigger deployment if everything but the code + // has been changed + envvar.SetVal(environment, "CAMEL_K_DIGEST", e.Integration.Status.Digest) + + // optimizations + envvar.SetVal(environment, "AB_JOLOKIA_OFF", True) + + if t.ConfigurationType == "volume" { + if err := t.bindToVolumes(e, &svc); err != nil { + return nil, err + } + } else { + if err := t.bindToEnvVar(e, &svc); err != nil { + return nil, err + } + } + + // add env vars from traits + for _, envVar := range e.EnvVars { + envvar.SetVar(&svc.Spec.RunLatest.Configuration.RevisionTemplate.Spec.Container.Env, envVar) + } + return &svc, nil } diff --git a/pkg/trait/knative_service_env.go b/pkg/trait/knative_service_env.go new file mode 100644 index 0000000..04a181f --- /dev/null +++ b/pkg/trait/knative_service_env.go @@ -0,0 +1,156 @@ +/* +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 trait + +import ( + "fmt" + "strings" + + "github.com/apache/camel-k/pkg/util" + + "github.com/apache/camel-k/pkg/util/kubernetes" + + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + "github.com/apache/camel-k/pkg/util/envvar" + serving "github.com/knative/serving/pkg/apis/serving/v1alpha1" +) + +func (t *knativeServiceTrait) bindToEnvVar(e *Environment, service *serving.Service) error { + environment := &service.Spec.RunLatest.Configuration.RevisionTemplate.Spec.Container.Env + + // + // Properties + // + + properties := make(map[string]string) + + VisitKeyValConfigurations("property", e.IntegrationContext, e.Integration, func(key string, val string) { + properties[key] = val + }) + + VisitConfigurations("configmap", e.IntegrationContext, e.Integration, func(cmName string) { + cm, err := kubernetes.GetConfigMap(e.C, e.Client, e.Integration.Namespace, cmName) + if err != nil { + t.L.Errorf(err, "failed to lookup ConfigMap %s", cmName) + } + if cm != nil { + util.ExtractApplicationProperties(cm.Data, func(key string, val string) { + properties[key] = val + }) + } + }) + + VisitConfigurations("secret", e.IntegrationContext, e.Integration, func(secretName string) { + cm, err := kubernetes.GetSecret(e.C, e.Client, e.Integration.Namespace, secretName) + if err != nil { + t.L.Errorf(err, "failed to lookup Secret %s", secretName) + } + if cm != nil { + util.ExtractEncodedApplicationProperties(cm.Data, func(key string, val string) { + properties[key] = val + }) + } + }) + + p := "" + + for k, v := range properties { + p += fmt.Sprintf("%s=%s\n", k, v) + } + + envvar.SetVal(environment, "CAMEL_K_CONF", "env:CAMEL_K_PROPERTIES") + envvar.SetVal(environment, "CAMEL_K_PROPERTIES", p) + + // + // Sources + // + + sourcesSpecs, err := kubernetes.ResolveIntegrationSources(t.ctx, t.client, e.Integration, e.Resources) + if err != nil { + return err + } + + sources := make([]string, 0, len(e.Integration.Spec.Sources)) + + for i, s := range sourcesSpecs { + if s.Content == "" { + t.L.Debug("Source %s has and empty content", s.Name) + } + + envName := fmt.Sprintf("CAMEL_K_ROUTE_%03d", i) + envvar.SetVal(environment, envName, s.Content) + + params := make([]string, 0) + params = append(params, "name="+s.Name) + + if s.InferLanguage() != "" { + params = append(params, "language="+string(s.InferLanguage())) + } + if s.Compression { + params = append(params, "compression=true") + } + + src := fmt.Sprintf("env:%s", envName) + if len(params) > 0 { + src = fmt.Sprintf("%s?%s", src, strings.Join(params, "&")) + } + + sources = append(sources, src) + } + + // camel-k runtime + envvar.SetVal(environment, "CAMEL_K_ROUTES", strings.Join(sources, ",")) + + // + // Resources + // + + resourcesSpecs, err := kubernetes.ResolveIntegrationResources(t.ctx, t.client, e.Integration, e.Resources) + if err != nil { + return err + } + + for i, r := range resourcesSpecs { + if r.Type != v1alpha1.ResourceTypeData { + continue + } + + envName := fmt.Sprintf("CAMEL_K_RESOURCE_%03d", i) + envvar.SetVal(environment, envName, r.Content) + + params := make([]string, 0) + if r.Compression { + params = append(params, "compression=true") + } + + envValue := fmt.Sprintf("env:%s", envName) + if len(params) > 0 { + envValue = fmt.Sprintf("%s?%s", envValue, strings.Join(params, "&")) + } + + envName = r.Name + envName = strings.ToUpper(envName) + envName = strings.Replace(envName, "-", "_", -1) + envName = strings.Replace(envName, ".", "_", -1) + envName = strings.Replace(envName, " ", "_", -1) + + envvar.SetVal(environment, envName, envValue) + } + + return nil +} diff --git a/pkg/trait/knative_test.go b/pkg/trait/knative_service_env_test.go similarity index 96% rename from pkg/trait/knative_test.go rename to pkg/trait/knative_service_env_test.go index fcb8a48..51af21c 100644 --- a/pkg/trait/knative_test.go +++ b/pkg/trait/knative_service_env_test.go @@ -40,8 +40,11 @@ func TestKnativeTraitWithCompressedSources(t *testing.T) { catalog, err := test.DefaultCatalog() assert.Nil(t, err) + traitCatalog := NewCatalog(context.TODO(), nil) + environment := Environment{ CamelCatalog: catalog, + Catalog: traitCatalog, Integration: &v1alpha1.Integration{ ObjectMeta: metav1.ObjectMeta{ Name: "test", @@ -96,7 +99,7 @@ func TestKnativeTraitWithCompressedSources(t *testing.T) { Resources: kubernetes.NewCollection(), } - err = NewKnativeTestCatalog().apply(&environment) + err = traitCatalog.apply(&environment) assert.Nil(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -144,8 +147,11 @@ func TestKnativeTraitWithConfigMapSources(t *testing.T) { catalog, err := test.DefaultCatalog() assert.Nil(t, err) + traitCatalog := NewCatalog(context.TODO(), nil) + environment := Environment{ CamelCatalog: catalog, + Catalog: traitCatalog, Integration: &v1alpha1.Integration{ ObjectMeta: metav1.ObjectMeta{ Name: "test", @@ -194,7 +200,7 @@ func TestKnativeTraitWithConfigMapSources(t *testing.T) { }), } - err = NewKnativeTestCatalog().apply(&environment) + err = traitCatalog.apply(&environment) assert.Nil(t, err) assert.NotEmpty(t, environment.ExecutedTraits) @@ -219,7 +225,3 @@ func TestKnativeTraitWithConfigMapSources(t *testing.T) { assert.True(t, services > 0) assert.True(t, environment.Resources.Size() > 0) } - -func NewKnativeTestCatalog() *Catalog { - return NewCatalog(context.TODO(), nil) -} diff --git a/pkg/trait/knative_service_vol.go b/pkg/trait/knative_service_vol.go new file mode 100644 index 0000000..9bf1ee7 --- /dev/null +++ b/pkg/trait/knative_service_vol.go @@ -0,0 +1,43 @@ +/* +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 trait + +import ( + "strings" + + "github.com/apache/camel-k/pkg/util/envvar" + serving "github.com/knative/serving/pkg/apis/serving/v1alpha1" +) + +func (t *knativeServiceTrait) bindToVolumes(e *Environment, service *serving.Service) error { + e.ConfigureVolumesAndMounts( + t.deployer.ContainerImage, + &service.Spec.RunLatest.Configuration.RevisionTemplate.Spec.Volumes, + &service.Spec.RunLatest.Configuration.RevisionTemplate.Spec.Container.VolumeMounts, + ) + + paths := e.ComputeSourcesURI(t.deployer.ContainerImage) + + environment := &service.Spec.RunLatest.Configuration.RevisionTemplate.Spec.Container.Env + + envvar.SetVal(environment, "CAMEL_K_ROUTES", strings.Join(paths, ",")) + envvar.SetVal(environment, "CAMEL_K_CONF", "/etc/camel/conf/application.properties") + envvar.SetVal(environment, "CAMEL_K_CONF_D", "/etc/camel/conf.d") + + return nil +} diff --git a/pkg/trait/knative_service_vol_test.go b/pkg/trait/knative_service_vol_test.go new file mode 100644 index 0000000..01c8a7b --- /dev/null +++ b/pkg/trait/knative_service_vol_test.go @@ -0,0 +1,272 @@ +/* +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 trait + +import ( + "context" + "testing" + + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + "github.com/apache/camel-k/pkg/util/envvar" + "github.com/apache/camel-k/pkg/util/kubernetes" + "github.com/apache/camel-k/pkg/util/test" + + serving "github.com/knative/serving/pkg/apis/serving/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/stretchr/testify/assert" +) + +func TestKnativeWithVolumeBinding(t *testing.T) { + catalog, err := test.DefaultCatalog() + assert.Nil(t, err) + + traitCatalog := NewCatalog(context.TODO(), nil) + + environment := Environment{ + CamelCatalog: catalog, + Catalog: traitCatalog, + Integration: &v1alpha1.Integration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "ns", + }, + Status: v1alpha1.IntegrationStatus{ + Phase: v1alpha1.IntegrationPhaseDeploying, + }, + Spec: v1alpha1.IntegrationSpec{ + Profile: v1alpha1.TraitProfileKnative, + Sources: []v1alpha1.SourceSpec{ + { + DataSpec: v1alpha1.DataSpec{ + Name: "routes.js", + Content: `from("undertow:test").log("hello")`, + Compression: true, + }, + Language: v1alpha1.LanguageJavaScript, + }, + }, + Resources: []v1alpha1.ResourceSpec{ + { + DataSpec: v1alpha1.DataSpec{ + Name: "my-resource.txt", + Content: "", + Compression: false, + }, + Type: v1alpha1.ResourceTypeData, + }, + }, + Configuration: []v1alpha1.ConfigurationSpec{ + {Type: "configmap", Value: "my-cm"}, + {Type: "secret", Value: "my-secret"}, + {Type: "property", Value: "my-property=my-property-value"}, + }, + Traits: map[string]v1alpha1.IntegrationTraitSpec{ + "knative-service": { + Configuration: map[string]string{ + "configuration-type": "volume", + }, + }, + }, + }, + }, + Platform: &v1alpha1.IntegrationPlatform{ + Spec: v1alpha1.IntegrationPlatformSpec{ + Cluster: v1alpha1.IntegrationPlatformClusterOpenShift, + Build: v1alpha1.IntegrationPlatformBuildSpec{ + PublishStrategy: v1alpha1.IntegrationPlatformBuildPublishStrategyS2I, + Registry: "registry", + }, + }, + }, + EnvVars: make([]corev1.EnvVar, 0), + ExecutedTraits: make([]Trait, 0), + Resources: kubernetes.NewCollection(), + } + + err = traitCatalog.apply(&environment) + + assert.Nil(t, err) + assert.NotEmpty(t, environment.ExecutedTraits) + assert.NotNil(t, environment.GetTrait(ID("knative"))) + assert.NotNil(t, envvar.Get(environment.EnvVars, "CAMEL_KNATIVE_CONFIGURATION")) + assert.Equal(t, 4, environment.Resources.Size()) + + s := environment.Resources.GetKnativeService(func(service *serving.Service) bool { + return service.Name == "test" + }) + + assert.NotNil(t, s) + + spec := s.Spec.RunLatest.Configuration.RevisionTemplate.Spec + + assert.Len(t, spec.Container.VolumeMounts, 5) + assert.Len(t, spec.Volumes, 5) + + assert.Condition(t, func() bool { + for _, v := range spec.Container.VolumeMounts { + if v.Name == "integration-properties" { + return true + } + } + return false + }) + assert.Condition(t, func() bool { + for _, v := range spec.Container.VolumeMounts { + if v.Name == "my-cm" { + return true + } + } + return false + }) + assert.Condition(t, func() bool { + for _, v := range spec.Volumes { + if v.Name == "my-secret" { + return true + } + } + return false + }) + + names := make([]string, 0) + environment.Resources.VisitConfigMap(func(cm *corev1.ConfigMap) { + names = append(names, cm.Name) + }) + + assert.Contains(t, names, "test-properties") + assert.Contains(t, names, "test-source-000") + assert.Contains(t, names, "test-resource-000") + + environment.Resources.VisitConfigMap(func(cm *corev1.ConfigMap) { + if cm.Name == "test-properties" { + _, ok := cm.Data["application.properties"] + assert.True(t, ok) + } + }) + + test.EnvVarHasValue(t, spec.Container.Env, "CAMEL_K_ROUTES", "file:/etc/camel/sources/i-source-000/routes.js?language=js&compression=true") + test.EnvVarHasValue(t, spec.Container.Env, "CAMEL_K_CONF", "/etc/camel/conf/application.properties") + test.EnvVarHasValue(t, spec.Container.Env, "CAMEL_K_CONF_D", "/etc/camel/conf.d") +} + +func TestKnativeWithVolumeBindingAndContainerImage(t *testing.T) { + catalog, err := test.DefaultCatalog() + assert.Nil(t, err) + + traitCatalog := NewCatalog(context.TODO(), nil) + + environment := Environment{ + CamelCatalog: catalog, + Catalog: traitCatalog, + Integration: &v1alpha1.Integration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "ns", + }, + Status: v1alpha1.IntegrationStatus{ + Phase: v1alpha1.IntegrationPhaseDeploying, + }, + Spec: v1alpha1.IntegrationSpec{ + Profile: v1alpha1.TraitProfileKnative, + Sources: []v1alpha1.SourceSpec{ + { + DataSpec: v1alpha1.DataSpec{ + Name: "routes.js", + Content: `from("undertow:test").log("hello")`, + Compression: true, + }, + Language: v1alpha1.LanguageJavaScript, + }, + }, + Resources: []v1alpha1.ResourceSpec{ + { + DataSpec: v1alpha1.DataSpec{ + Name: "my-resource.txt", + Content: "", + Compression: false, + }, + Type: v1alpha1.ResourceTypeData, + }, + }, + Configuration: []v1alpha1.ConfigurationSpec{ + {Type: "configmap", Value: "my-cm"}, + {Type: "secret", Value: "my-secret"}, + }, + Traits: map[string]v1alpha1.IntegrationTraitSpec{ + "deployer": { + Configuration: map[string]string{ + "container-image": "true", + }, + }, + "knative-service": { + Configuration: map[string]string{ + "configuration-type": "volume", + }, + }, + }, + }, + }, + Platform: &v1alpha1.IntegrationPlatform{ + Spec: v1alpha1.IntegrationPlatformSpec{ + Cluster: v1alpha1.IntegrationPlatformClusterOpenShift, + Build: v1alpha1.IntegrationPlatformBuildSpec{ + PublishStrategy: v1alpha1.IntegrationPlatformBuildPublishStrategyS2I, + Registry: "registry", + }, + }, + }, + EnvVars: make([]corev1.EnvVar, 0), + ExecutedTraits: make([]Trait, 0), + Resources: kubernetes.NewCollection(), + } + + err = traitCatalog.apply(&environment) + + assert.Nil(t, err) + assert.NotEmpty(t, environment.ExecutedTraits) + assert.NotNil(t, environment.GetTrait(ID("knative"))) + assert.NotNil(t, envvar.Get(environment.EnvVars, "CAMEL_KNATIVE_CONFIGURATION")) + assert.Equal(t, 2, environment.Resources.Size()) + + names := make([]string, 0) + environment.Resources.VisitConfigMap(func(cm *corev1.ConfigMap) { + names = append(names, cm.Name) + }) + + assert.Contains(t, names, "test-properties") + + s := environment.Resources.GetKnativeService(func(service *serving.Service) bool { + return service.Name == "test" + }) + + assert.NotNil(t, s) + + spec := s.Spec.RunLatest.Configuration.RevisionTemplate.Spec + + assert.Len(t, spec.Container.VolumeMounts, 3) + assert.Len(t, spec.Volumes, 3) + + test.HasVolume(t, spec.Volumes, "my-cm") + test.HasVolume(t, spec.Volumes, "my-secret") + test.HasVolume(t, spec.Volumes, "integration-properties") + + test.EnvVarHasValue(t, spec.Container.Env, "CAMEL_K_ROUTES", "file:/deployments/sources/routes.js?language=js&compression=true") + test.EnvVarHasValue(t, spec.Container.Env, "CAMEL_K_CONF", "/etc/camel/conf/application.properties") + test.EnvVarHasValue(t, spec.Container.Env, "CAMEL_K_CONF_D", "/etc/camel/conf.d") +} diff --git a/pkg/trait/service.go b/pkg/trait/service.go index b0903fe..8e91366 100644 --- a/pkg/trait/service.go +++ b/pkg/trait/service.go @@ -20,6 +20,7 @@ package trait import ( "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" "github.com/apache/camel-k/pkg/metadata" + "github.com/apache/camel-k/pkg/util/kubernetes" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -52,7 +53,7 @@ func (t *serviceTrait) Configure(e *Environment) (bool, error) { } if t.Auto == nil || *t.Auto { - sources, err := e.ResolveSources(t.ctx, t.client) + sources, err := kubernetes.ResolveIntegrationSources(t.ctx, t.client, e.Integration, e.Resources) if err != nil { return false, err } diff --git a/pkg/trait/catalog.go b/pkg/trait/trait_catalog.go similarity index 98% rename from pkg/trait/catalog.go rename to pkg/trait/trait_catalog.go index 68336df..9c5c9c2 100644 --- a/pkg/trait/catalog.go +++ b/pkg/trait/trait_catalog.go @@ -34,6 +34,7 @@ type Catalog struct { tCamel Trait tDebug Trait tDependencies Trait + tDeployer Trait tDeployment Trait tGarbageCollector Trait tKnativeService Trait @@ -62,6 +63,7 @@ func NewCatalog(ctx context.Context, c client.Client) *Catalog { tRestDsl: newRestDslTrait(), tKnative: newKnativeTrait(), tDependencies: newDependenciesTrait(), + tDeployer: newDeployerTrait(), tDeployment: newDeploymentTrait(), tGarbageCollector: newGarbageCollectorTrait(), tKnativeService: newKnativeServiceTrait(), @@ -97,6 +99,7 @@ func (c *Catalog) allTraits() []Trait { c.tRestDsl, c.tKnative, c.tDependencies, + c.tDeployer, c.tDeployment, c.tGarbageCollector, c.tKnativeService, @@ -133,6 +136,7 @@ func (c *Catalog) traitsFor(environment *Environment) []Trait { c.tSpringBoot, c.tJolokia, c.tPrometheus, + c.tDeployer, c.tDeployment, c.tService, c.tRoute, @@ -152,6 +156,7 @@ func (c *Catalog) traitsFor(environment *Environment) []Trait { c.tSpringBoot, c.tJolokia, c.tPrometheus, + c.tDeployer, c.tDeployment, c.tService, c.tIngress, @@ -170,6 +175,7 @@ func (c *Catalog) traitsFor(environment *Environment) []Trait { c.tEnvironment, c.tClasspath, c.tSpringBoot, + c.tDeployer, c.tDeployment, c.tKnativeService, c.tIstio, diff --git a/pkg/trait/trait_test.go b/pkg/trait/trait_test.go index 0145e23..5d1d3da 100644 --- a/pkg/trait/trait_test.go +++ b/pkg/trait/trait_test.go @@ -175,6 +175,7 @@ func createTestEnv(t *testing.T, cluster v1alpha1.IntegrationPlatformCluster, sc return &Environment{ CamelCatalog: catalog, + Catalog: NewCatalog(context.TODO(), nil), Integration: &v1alpha1.Integration{ ObjectMeta: metav1.ObjectMeta{ Name: TestDeployment, diff --git a/pkg/trait/trait_types.go b/pkg/trait/trait_types.go new file mode 100644 index 0000000..364fd3b --- /dev/null +++ b/pkg/trait/trait_types.go @@ -0,0 +1,526 @@ +/* +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 trait + +import ( + "context" + "fmt" + "path" + "strconv" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + corev1 "k8s.io/api/core/v1" + + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + "github.com/apache/camel-k/pkg/builder" + "github.com/apache/camel-k/pkg/client" + "github.com/apache/camel-k/pkg/metadata" + "github.com/apache/camel-k/pkg/platform" + "github.com/apache/camel-k/pkg/util/camel" + "github.com/apache/camel-k/pkg/util/kubernetes" + "github.com/apache/camel-k/pkg/util/log" +) + +// Identifiable represent an identifiable type +type Identifiable interface { + ID() ID +} + +// ID uniquely identifies a trait +type ID string + +// Trait is the interface of all traits +type Trait interface { + Identifiable + client.Injectable + + // InjectContext to inject a context + InjectContext(context.Context) + + // Configure the trait + Configure(environment *Environment) (bool, error) + + // Apply executes a customization of the Environment + Apply(environment *Environment) error +} + +/* Base trait */ + +func newBaseTrait(id string) BaseTrait { + return BaseTrait{ + id: ID(id), + L: log.Log.WithName("traits").WithValues("trait", id), + } +} + +// BaseTrait is the root trait with noop implementations for hooks +type BaseTrait struct { + id ID + Enabled *bool `property:"enabled"` + client client.Client + ctx context.Context + L log.Logger +} + +// ID returns the identifier of the trait +func (trait *BaseTrait) ID() ID { + return trait.id +} + +// InjectClient implements client.ClientInject and allows to inject a client into the trait +func (trait *BaseTrait) InjectClient(c client.Client) { + trait.client = c +} + +// InjectContext allows to inject a context into the trait +func (trait *BaseTrait) InjectContext(ctx context.Context) { + trait.ctx = ctx +} + +/* Environment */ + +// A Environment provides the context where the trait is executed +type Environment struct { + CamelCatalog *camel.RuntimeCatalog + RuntimeVersion string + Catalog *Catalog + C context.Context + Client client.Client + Platform *v1alpha1.IntegrationPlatform + IntegrationContext *v1alpha1.IntegrationContext + Integration *v1alpha1.Integration + Resources *kubernetes.Collection + PostActions []func(*Environment) error + PostProcessors []func(*Environment) error + Steps []builder.Step + BuildDir string + ExecutedTraits []Trait + EnvVars []corev1.EnvVar +} + +// ControllerStrategy is used to determine the kind of controller that needs to be created for the integration +type ControllerStrategy string + +// List of controller strategies +const ( + ControllerStrategyDeployment = "deployment" + ControllerStrategyKnativeService = "knative-service" +) + +// GetTrait -- +func (e *Environment) GetTrait(id ID) Trait { + for _, t := range e.ExecutedTraits { + if t.ID() == id { + return t + } + } + + return nil +} + +// IntegrationInPhase -- +func (e *Environment) IntegrationInPhase(phases ...v1alpha1.IntegrationPhase) bool { + if e.Integration == nil { + return false + } + + for _, phase := range phases { + if e.Integration.Status.Phase == phase { + return true + } + } + + return false +} + +// IntegrationContextInPhase -- +func (e *Environment) IntegrationContextInPhase(phases ...v1alpha1.IntegrationContextPhase) bool { + if e.IntegrationContext == nil { + return false + } + + for _, phase := range phases { + if e.IntegrationContext.Status.Phase == phase { + return true + } + } + + return false +} + +// InPhase -- +func (e *Environment) InPhase(c v1alpha1.IntegrationContextPhase, i v1alpha1.IntegrationPhase) bool { + return e.IntegrationContextInPhase(c) && e.IntegrationInPhase(i) +} + +// DetermineProfile determines the TraitProfile of the environment. +// First looking at the Integration.Spec for a Profile, +// next looking at the IntegrationContext.Spec +// and lastly the Platform Profile +func (e *Environment) DetermineProfile() v1alpha1.TraitProfile { + if e.Integration != nil && e.Integration.Spec.Profile != "" { + return e.Integration.Spec.Profile + } + + if e.IntegrationContext != nil && e.IntegrationContext.Spec.Profile != "" { + return e.IntegrationContext.Spec.Profile + } + + return platform.GetProfile(e.Platform) +} + +// DetermineControllerStrategy determines the type of controller that should be used for the integration +func (e *Environment) DetermineControllerStrategy(ctx context.Context, c client.Client) (ControllerStrategy, error) { + if e.DetermineProfile() != v1alpha1.TraitProfileKnative { + return ControllerStrategyDeployment, nil + } + + var sources []v1alpha1.SourceSpec + var err error + if sources, err = kubernetes.ResolveIntegrationSources(ctx, c, e.Integration, e.Resources); err != nil { + return "", err + } + + // In Knative profile: use knative service only if needed + meta := metadata.ExtractAll(e.CamelCatalog, sources) + if !meta.RequiresHTTPService { + return ControllerStrategyDeployment, nil + } + + return ControllerStrategyKnativeService, nil +} + +// DetermineCamelVersion -- +func (e *Environment) DetermineCamelVersion() string { + var version string + + if e.Integration != nil { + version = e.Integration.Status.CamelVersion + } + if e.IntegrationContext != nil && version == "" { + version = e.IntegrationContext.Status.CamelVersion + } + if version == "" { + version = e.Platform.Spec.Build.CamelVersion + } + + return version +} + +// DetermineRuntimeVersion -- +func (e *Environment) DetermineRuntimeVersion() string { + var version string + + if e.Integration != nil { + version = e.Integration.Status.RuntimeVersion + } + if e.IntegrationContext != nil && version == "" { + version = e.IntegrationContext.Status.RuntimeVersion + } + if version == "" { + version = e.Platform.Spec.Build.RuntimeVersion + } + + return version +} + +// ComputeConfigMaps -- +func (e *Environment) ComputeConfigMaps(container bool) []runtime.Object { + sources := e.Integration.Sources() + maps := make([]runtime.Object, 0, len(sources)+1) + + // combine properties of integration with context, integration + // properties have the priority + properties := "" + + VisitKeyValConfigurations("property", e.IntegrationContext, e.Integration, func(key string, val string) { + properties += fmt.Sprintf("%s=%s\n", key, val) + }) + + maps = append( + maps, + &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: e.Integration.Name + "-properties", + Namespace: e.Integration.Namespace, + Labels: map[string]string{ + "camel.apache.org/integration": e.Integration.Name, + }, + }, + Data: map[string]string{ + "application.properties": properties, + }, + }, + ) + + if !container { + for i, s := range sources { + if s.ContentRef != "" { + continue + } + + cm := corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-source-%03d", e.Integration.Name, i), + Namespace: e.Integration.Namespace, + Labels: map[string]string{ + "camel.apache.org/integration": e.Integration.Name, + }, + Annotations: map[string]string{ + "camel.apache.org/source.language": string(s.InferLanguage()), + "camel.apache.org/source.name": s.Name, + "camel.apache.org/source.compression": strconv.FormatBool(s.Compression), + }, + }, + Data: map[string]string{ + "content": s.Content, + }, + } + + maps = append(maps, &cm) + } + + for i, s := range e.Integration.Spec.Resources { + if s.Type != v1alpha1.ResourceTypeData { + continue + } + + cm := corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-resource-%03d", e.Integration.Name, i), + Namespace: e.Integration.Namespace, + Labels: map[string]string{ + "camel.apache.org/integration": e.Integration.Name, + }, + Annotations: map[string]string{ + "camel.apache.org/resource.name": s.Name, + "camel.apache.org/resource.compression": strconv.FormatBool(s.Compression), + }, + }, + Data: map[string]string{ + "content": s.Content, + }, + } + + maps = append(maps, &cm) + } + } + + return maps +} + +// ComputeSourcesURI -- +func (e *Environment) ComputeSourcesURI(container bool) []string { + sources := e.Integration.Sources() + paths := make([]string, 0, len(sources)) + + for i, s := range sources { + root := "/etc/camel/sources" + + if container { + // assume sources are copied over the standard deployments folder + root = "/deployments/sources" + } else { + root = path.Join(root, fmt.Sprintf("i-source-%03d", i)) + } + + srcName := strings.TrimPrefix(s.Name, "/") + src := path.Join(root, srcName) + src = "file:" + src + + params := make([]string, 0) + if s.InferLanguage() != "" { + params = append(params, "language="+string(s.InferLanguage())) + } + if s.Compression { + params = append(params, "compression=true") + } + + if len(params) > 0 { + src = fmt.Sprintf("%s?%s", src, strings.Join(params, "&")) + } + + paths = append(paths, src) + } + + return paths +} + +// ConfigureVolumesAndMounts -- +func (e *Environment) ConfigureVolumesAndMounts(container bool, vols *[]corev1.Volume, mnts *[]corev1.VolumeMount) { + + if !container { + + // + // Volumes :: Sources + // + + for i, s := range e.Integration.Sources() { + cmName := fmt.Sprintf("%s-source-%03d", e.Integration.Name, i) + refName := fmt.Sprintf("i-source-%03d", i) + resName := strings.TrimPrefix(s.Name, "/") + + if s.ContentRef != "" { + cmName = s.ContentRef + } + + *vols = append(*vols, corev1.Volume{ + Name: refName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: cmName, + }, + Items: []corev1.KeyToPath{ + { + Key: "content", + Path: resName, + }, + }, + }, + }, + }) + + *mnts = append(*mnts, corev1.VolumeMount{ + Name: refName, + MountPath: path.Join("/etc/camel/sources", refName), + }) + } + + for i, r := range e.Integration.Spec.Resources { + if r.Type != v1alpha1.ResourceTypeData { + continue + } + + cmName := fmt.Sprintf("%s-resource-%03d", e.Integration.Name, i) + refName := fmt.Sprintf("i-resource-%03d", i) + resName := strings.TrimPrefix(r.Name, "/") + + *vols = append(*vols, corev1.Volume{ + Name: refName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: cmName, + }, + Items: []corev1.KeyToPath{ + { + Key: "content", + Path: resName, + }, + }, + }, + }, + }) + + *mnts = append(*mnts, corev1.VolumeMount{ + Name: refName, + MountPath: path.Join("/etc/camel/resources", refName), + }) + } + } + + // + // Volumes :: Properties + // + + *vols = append(*vols, corev1.Volume{ + Name: "integration-properties", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: e.Integration.Name + "-properties", + }, + Items: []corev1.KeyToPath{ + { + Key: "application.properties", + Path: "application.properties", + }, + }, + }, + }, + }) + + *mnts = append(*mnts, corev1.VolumeMount{ + Name: "integration-properties", + MountPath: "/etc/camel/conf", + }) + + // + // Volumes :: Additional ConfigMaps + // + + VisitConfigurations("configmap", e.IntegrationContext, e.Integration, func(cmName string) { + refName := kubernetes.SanitizeLabel(cmName) + fileName := "integration-cm-" + strings.ToLower(cmName) + + *vols = append(*vols, corev1.Volume{ + Name: refName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: cmName, + }, + }, + }, + }) + + *mnts = append(*mnts, corev1.VolumeMount{ + Name: refName, + MountPath: path.Join("/etc/camel/conf.d", fileName), + }) + }) + + // + // Volumes :: Additional Secrets + // + + VisitConfigurations("secret", e.IntegrationContext, e.Integration, func(secretName string) { + refName := kubernetes.SanitizeLabel(secretName) + fileName := "integration-secret-" + strings.ToLower(secretName) + + *vols = append(*vols, corev1.Volume{ + Name: refName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secretName, + }, + }, + }) + + *mnts = append(*mnts, corev1.VolumeMount{ + Name: refName, + MountPath: path.Join("/etc/camel/conf.d", fileName), + }) + }) +} diff --git a/pkg/trait/types.go b/pkg/trait/types.go deleted file mode 100644 index d91e72c..0000000 --- a/pkg/trait/types.go +++ /dev/null @@ -1,234 +0,0 @@ -/* -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 trait - -import ( - "context" - - corev1 "k8s.io/api/core/v1" - - "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" - "github.com/apache/camel-k/pkg/builder" - "github.com/apache/camel-k/pkg/client" - "github.com/apache/camel-k/pkg/metadata" - "github.com/apache/camel-k/pkg/platform" - "github.com/apache/camel-k/pkg/util/camel" - "github.com/apache/camel-k/pkg/util/kubernetes" - "github.com/apache/camel-k/pkg/util/log" - "github.com/apache/camel-k/pkg/util/source" -) - -// Identifiable represent an identifiable type -type Identifiable interface { - ID() ID -} - -// ID uniquely identifies a trait -type ID string - -// Trait is the interface of all traits -type Trait interface { - Identifiable - client.Injectable - - // InjectContext to inject a context - InjectContext(context.Context) - - // Configure the trait - Configure(environment *Environment) (bool, error) - - // Apply executes a customization of the Environment - Apply(environment *Environment) error -} - -/* Base trait */ - -func newBaseTrait(id string) BaseTrait { - return BaseTrait{ - id: ID(id), - L: log.Log.WithName("traits").WithValues("trait", id), - } -} - -// BaseTrait is the root trait with noop implementations for hooks -type BaseTrait struct { - id ID - Enabled *bool `property:"enabled"` - client client.Client - ctx context.Context - L log.Logger -} - -// ID returns the identifier of the trait -func (trait *BaseTrait) ID() ID { - return trait.id -} - -// InjectClient implements client.ClientInject and allows to inject a client into the trait -func (trait *BaseTrait) InjectClient(c client.Client) { - trait.client = c -} - -// InjectContext allows to inject a context into the trait -func (trait *BaseTrait) InjectContext(ctx context.Context) { - trait.ctx = ctx -} - -/* Environment */ - -// A Environment provides the context where the trait is executed -type Environment struct { - CamelCatalog *camel.RuntimeCatalog - RuntimeVersion string - Catalog *Catalog - C context.Context - Client client.Client - Platform *v1alpha1.IntegrationPlatform - IntegrationContext *v1alpha1.IntegrationContext - Integration *v1alpha1.Integration - Resources *kubernetes.Collection - PostActions []func(*Environment) error - PostProcessors []func(*Environment) error - Steps []builder.Step - BuildDir string - ExecutedTraits []Trait - EnvVars []corev1.EnvVar -} - -// ControllerStrategy is used to determine the kind of controller that needs to be created for the integration -type ControllerStrategy string - -// List of controller strategies -const ( - ControllerStrategyDeployment = "deployment" - ControllerStrategyKnativeService = "knative-service" -) - -// GetTrait -- -func (e *Environment) GetTrait(id ID) Trait { - for _, t := range e.ExecutedTraits { - if t.ID() == id { - return t - } - } - - return nil -} - -// IntegrationInPhase -- -func (e *Environment) IntegrationInPhase(phase v1alpha1.IntegrationPhase) bool { - return e.Integration != nil && e.Integration.Status.Phase == phase -} - -// IntegrationContextInPhase -- -func (e *Environment) IntegrationContextInPhase(phase v1alpha1.IntegrationContextPhase) bool { - return e.IntegrationContext != nil && e.IntegrationContext.Status.Phase == phase -} - -// InPhase -- -func (e *Environment) InPhase(c v1alpha1.IntegrationContextPhase, i v1alpha1.IntegrationPhase) bool { - return e.IntegrationContextInPhase(c) && e.IntegrationInPhase(i) -} - -// DetermineProfile determines the TraitProfile of the environment. -// First looking at the Integration.Spec for a Profile, -// next looking at the IntegrationContext.Spec -// and lastly the Platform Profile -func (e *Environment) DetermineProfile() v1alpha1.TraitProfile { - if e.Integration != nil && e.Integration.Spec.Profile != "" { - return e.Integration.Spec.Profile - } - - if e.IntegrationContext != nil && e.IntegrationContext.Spec.Profile != "" { - return e.IntegrationContext.Spec.Profile - } - - return platform.GetProfile(e.Platform) -} - -// ResolveSources -- -func (e *Environment) ResolveSources(context context.Context, client client.Client) ([]v1alpha1.SourceSpec, error) { - return source.Resolve(e.Integration.Sources(), func(name string) (*corev1.ConfigMap, error) { - // the config map could be part of the resources created - // by traits - cm := e.Resources.GetConfigMap(func(m *corev1.ConfigMap) bool { - return m.Name == name - }) - - if cm != nil { - return cm, nil - } - - return kubernetes.GetConfigMap(context, client, name, e.Integration.Namespace) - }) -} - -// DetermineControllerStrategy determines the type of controller that should be used for the integration -func (e *Environment) DetermineControllerStrategy(ctx context.Context, c client.Client) (ControllerStrategy, error) { - if e.DetermineProfile() != v1alpha1.TraitProfileKnative { - return ControllerStrategyDeployment, nil - } - - var sources []v1alpha1.SourceSpec - var err error - if sources, err = e.ResolveSources(ctx, c); err != nil { - return "", err - } - - // In Knative profile: use knative service only if needed - meta := metadata.ExtractAll(e.CamelCatalog, sources) - if !meta.RequiresHTTPService { - return ControllerStrategyDeployment, nil - } - - return ControllerStrategyKnativeService, nil -} - -// DetermineCamelVersion -- -func (e *Environment) DetermineCamelVersion() string { - var version string - - if e.Integration != nil { - version = e.Integration.Status.CamelVersion - } - if e.IntegrationContext != nil && version == "" { - version = e.IntegrationContext.Status.CamelVersion - } - if version == "" { - version = e.Platform.Spec.Build.CamelVersion - } - - return version -} - -// DetermineRuntimeVersion -- -func (e *Environment) DetermineRuntimeVersion() string { - var version string - - if e.Integration != nil { - version = e.Integration.Status.RuntimeVersion - } - if e.IntegrationContext != nil && version == "" { - version = e.IntegrationContext.Status.RuntimeVersion - } - if version == "" { - version = e.Platform.Spec.Build.RuntimeVersion - } - - return version -} diff --git a/pkg/util/kubernetes/collection.go b/pkg/util/kubernetes/collection.go index e023229..6eff785 100644 --- a/pkg/util/kubernetes/collection.go +++ b/pkg/util/kubernetes/collection.go @@ -101,6 +101,11 @@ func (c *Collection) GetDeployment(filter func(*appsv1.Deployment) bool) *appsv1 return retValue } +// HasDeployment returns true if a deployment matching the given condition is present +func (c *Collection) HasDeployment(filter func(*appsv1.Deployment) bool) bool { + return c.GetDeployment(filter) != nil +} + // RemoveDeployment removes and returns a Deployment that matches the given function func (c *Collection) RemoveDeployment(filter func(*appsv1.Deployment) bool) *appsv1.Deployment { res := c.Remove(func(res runtime.Object) bool { @@ -169,6 +174,17 @@ func (c *Collection) GetService(filter func(*corev1.Service) bool) *corev1.Servi return retValue } +// GetKnativeService returns a knative Service that matches the given function +func (c *Collection) GetKnativeService(filter func(*serving.Service) bool) *serving.Service { + var retValue *serving.Service + c.VisitKnativeService(func(re *serving.Service) { + if filter(re) { + retValue = re + } + }) + return retValue +} + // VisitRoute executes the visitor function on all Route resources func (c *Collection) VisitRoute(visitor func(*routev1.Route)) { c.Visit(func(res runtime.Object) { diff --git a/pkg/util/kubernetes/resolver.go b/pkg/util/kubernetes/resolver.go new file mode 100644 index 0000000..5330e58 --- /dev/null +++ b/pkg/util/kubernetes/resolver.go @@ -0,0 +1,120 @@ +/* +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 kubernetes + +import ( + "context" + "fmt" + + "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" + "github.com/apache/camel-k/pkg/client" + corev1 "k8s.io/api/core/v1" +) + +// ResolveSources -- +func ResolveSources(elements []v1alpha1.SourceSpec, mapLookup func(string) (*corev1.ConfigMap, error)) ([]v1alpha1.SourceSpec, error) { + for i := 0; i < len(elements); i++ { + r := &elements[i] + + if err := Resolve(&r.DataSpec, mapLookup); err != nil { + return nil, err + } + } + + return elements, nil +} + +// ResolveResource -- +func ResolveResource(elements []v1alpha1.ResourceSpec, mapLookup func(string) (*corev1.ConfigMap, error)) ([]v1alpha1.ResourceSpec, error) { + for i := 0; i < len(elements); i++ { + r := &elements[i] + + if err := Resolve(&r.DataSpec, mapLookup); err != nil { + return nil, err + } + } + + return elements, nil +} + +// Resolve -- +func Resolve(data *v1alpha1.DataSpec, mapLookup func(string) (*corev1.ConfigMap, error)) error { + // if it is a reference, get the content from the + // referenced ConfigMap + if data.ContentRef != "" { + //look up the ConfigMap from the kubernetes cluster + cm, err := mapLookup(data.ContentRef) + if err != nil { + return err + } + + if cm == nil { + return fmt.Errorf("unable to find a ConfigMap with name: %s ", data.ContentRef) + } + + // + // Replace ref source content with real content + // + data.Content = cm.Data["content"] + data.ContentRef = "" + } + + return nil +} + +// ResolveIntegrationSources -- +func ResolveIntegrationSources(context context.Context, client client.Client, integration *v1alpha1.Integration, resources *Collection) ([]v1alpha1.SourceSpec, error) { + if integration == nil { + return nil, nil + } + + return ResolveSources(integration.Sources(), func(name string) (*corev1.ConfigMap, error) { + // the config map could be part of the resources created + // by traits + cm := resources.GetConfigMap(func(m *corev1.ConfigMap) bool { + return m.Name == name + }) + + if cm != nil { + return cm, nil + } + + return GetConfigMap(context, client, name, integration.Namespace) + }) +} + +// ResolveIntegrationResources -- +func ResolveIntegrationResources(context context.Context, client client.Client, integration *v1alpha1.Integration, resources *Collection) ([]v1alpha1.ResourceSpec, error) { + if integration == nil { + return nil, nil + } + + return ResolveResource(integration.Spec.Resources, func(name string) (*corev1.ConfigMap, error) { + // the config map could be part of the resources created + // by traits + cm := resources.GetConfigMap(func(m *corev1.ConfigMap) bool { + return m.Name == name + }) + + if cm != nil { + return cm, nil + } + + return GetConfigMap(context, client, name, integration.Namespace) + }) +} diff --git a/pkg/util/kubernetes/util.go b/pkg/util/kubernetes/util.go index 2114dba..1de3bd3 100644 --- a/pkg/util/kubernetes/util.go +++ b/pkg/util/kubernetes/util.go @@ -94,6 +94,31 @@ func GetConfigMap(context context.Context, client client.Client, name string, na return &answer, nil } +// GetSecret -- +func GetSecret(context context.Context, client client.Client, name string, namespace string) (*corev1.Secret, error) { + key := k8sclient.ObjectKey{ + Name: name, + Namespace: namespace, + } + + answer := corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } + + if err := client.Get(context, key, &answer); err != nil { + return nil, err + } + + return &answer, nil +} + // GetIntegrationContext -- func GetIntegrationContext(context context.Context, client client.Client, name string, namespace string) (*v1alpha1.IntegrationContext, error) { key := k8sclient.ObjectKey{ diff --git a/pkg/util/source/util.go b/pkg/util/source/util.go index 93f805e..63c5f51 100644 --- a/pkg/util/source/util.go +++ b/pkg/util/source/util.go @@ -16,44 +16,3 @@ limitations under the License. */ package source - -import ( - "fmt" - - "github.com/apache/camel-k/pkg/apis/camel/v1alpha1" - - corev1 "k8s.io/api/core/v1" -) - -// Resolve -- -func Resolve(sources []v1alpha1.SourceSpec, mapLookup func(string) (*corev1.ConfigMap, error)) ([]v1alpha1.SourceSpec, error) { - for i := 0; i < len(sources); i++ { - // copy the source to avoid modifications to the - // original source - s := sources[i].DeepCopy() - - // if it is a reference, get the content from the - // referenced ConfigMap - if s.ContentRef != "" { - //look up the ConfigMap from the kubernetes cluster - cm, err := mapLookup(s.ContentRef) - if err != nil { - return []v1alpha1.SourceSpec{}, err - } - - if cm == nil { - return []v1alpha1.SourceSpec{}, fmt.Errorf("unable to find a ConfigMap with name: %s ", s.ContentRef) - } - - // - // Replace ref source content with real content - // - s.Content = cm.Data["content"] - s.ContentRef = "" - } - - sources[i] = *s - } - - return sources, nil -} diff --git a/pkg/util/test/assertions.go b/pkg/util/test/assertions.go new file mode 100644 index 0000000..52c8a9f --- /dev/null +++ b/pkg/util/test/assertions.go @@ -0,0 +1,46 @@ +/* +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 test + +import ( + "testing" + + "github.com/apache/camel-k/pkg/util/envvar" + "github.com/stretchr/testify/assert" + + corev1 "k8s.io/api/core/v1" +) + +// EnvVarHasValue -- +func EnvVarHasValue(t *testing.T, env []corev1.EnvVar, name string, val string) { + ev := envvar.Get(env, name) + assert.NotNil(t, ev) + assert.Equal(t, val, ev.Value) +} + +// HasVolume -- +func HasVolume(t *testing.T, volumes []corev1.Volume, name string) { + assert.Condition(t, func() bool { + for _, v := range volumes { + if v.Name == name { + return true + } + } + return false + }) +} diff --git a/pkg/util/util.go b/pkg/util/util.go index b7790b0..519b9f6 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -18,12 +18,15 @@ limitations under the License. package util import ( + "encoding/base64" "os" "os/signal" "path" "regexp" "syscall" + "github.com/magiconair/properties" + "github.com/scylladb/go-set/strset" corev1 "k8s.io/api/core/v1" @@ -153,3 +156,45 @@ func FindAllDistinctStringSubmatch(data string, regexps ...*regexp.Regexp) []str } return submatchs.List() } + +// ExtractApplicationProperties -- +func ExtractApplicationProperties(data map[string]string, consumer func(string, string)) error { + pstr, ok := data["application.properties"] + if !ok { + return nil + } + + p, err := properties.LoadString(pstr) + if err != nil { + return err + } + + for _, k := range p.Keys() { + consumer(k, p.MustGet(k)) + } + + return nil +} + +// ExtractEncodedApplicationProperties -- +func ExtractEncodedApplicationProperties(data map[string][]byte, consumer func(string, string)) error { + encoded, ok := data["application.properties"] + if !ok { + return nil + } + decoded, err := base64.StdEncoding.DecodeString(string(encoded)) + if err != nil { + return err + } + + p, err := properties.Load(decoded, properties.UTF8) + if err != nil { + return err + } + + for _, k := range p.Keys() { + consumer(k, p.MustGet(k)) + } + + return nil +} diff --git a/vendor/github.com/magiconair/properties/LICENSE b/vendor/github.com/magiconair/properties/LICENSE new file mode 100644 index 0000000..b387087 --- /dev/null +++ b/vendor/github.com/magiconair/properties/LICENSE @@ -0,0 +1,25 @@ +goproperties - properties file decoder for Go + +Copyright (c) 2013-2018 - Frank Schroeder + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/magiconair/properties/assert/assert.go b/vendor/github.com/magiconair/properties/assert/assert.go new file mode 100644 index 0000000..d0f2704 --- /dev/null +++ b/vendor/github.com/magiconair/properties/assert/assert.go @@ -0,0 +1,90 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package assert provides helper functions for testing. +package assert + +import ( + "fmt" + "path/filepath" + "reflect" + "regexp" + "runtime" + "strings" + "testing" +) + +// skip defines the default call depth +const skip = 2 + +// Equal asserts that got and want are equal as defined by +// reflect.DeepEqual. The test fails with msg if they are not equal. +func Equal(t *testing.T, got, want interface{}, msg ...string) { + if x := equal(2, got, want, msg...); x != "" { + fmt.Println(x) + t.Fail() + } +} + +func equal(skip int, got, want interface{}, msg ...string) string { + if !reflect.DeepEqual(got, want) { + return fail(skip, "got %v want %v %s", got, want, strings.Join(msg, " ")) + } + return "" +} + +// Panic asserts that function fn() panics. +// It assumes that recover() either returns a string or +// an error and fails if the message does not match +// the regular expression in 'matches'. +func Panic(t *testing.T, fn func(), matches string) { + if x := doesPanic(2, fn, matches); x != "" { + fmt.Println(x) + t.Fail() + } +} + +func doesPanic(skip int, fn func(), expr string) (err string) { + defer func() { + r := recover() + if r == nil { + err = fail(skip, "did not panic") + return + } + var v string + switch r.(type) { + case error: + v = r.(error).Error() + case string: + v = r.(string) + } + err = matches(skip, v, expr) + }() + fn() + return "" +} + +// Matches asserts that a value matches a given regular expression. +func Matches(t *testing.T, value, expr string) { + if x := matches(2, value, expr); x != "" { + fmt.Println(x) + t.Fail() + } +} + +func matches(skip int, value, expr string) string { + ok, err := regexp.MatchString(expr, value) + if err != nil { + return fail(skip, "invalid pattern %q. %s", expr, err) + } + if !ok { + return fail(skip, "got %s which does not match %s", value, expr) + } + return "" +} + +func fail(skip int, format string, args ...interface{}) string { + _, file, line, _ := runtime.Caller(skip) + return fmt.Sprintf("\t%s:%d: %s\n", filepath.Base(file), line, fmt.Sprintf(format, args...)) +} diff --git a/vendor/github.com/magiconair/properties/decode.go b/vendor/github.com/magiconair/properties/decode.go new file mode 100644 index 0000000..3ebf804 --- /dev/null +++ b/vendor/github.com/magiconair/properties/decode.go @@ -0,0 +1,289 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package properties + +import ( + "fmt" + "reflect" + "strconv" + "strings" + "time" +) + +// Decode assigns property values to exported fields of a struct. +// +// Decode traverses v recursively and returns an error if a value cannot be +// converted to the field type or a required value is missing for a field. +// +// The following type dependent decodings are used: +// +// String, boolean, numeric fields have the value of the property key assigned. +// The property key name is the name of the field. A different key and a default +// value can be set in the field's tag. Fields without default value are +// required. If the value cannot be converted to the field type an error is +// returned. +// +// time.Duration fields have the result of time.ParseDuration() assigned. +// +// time.Time fields have the vaule of time.Parse() assigned. The default layout +// is time.RFC3339 but can be set in the field's tag. +// +// Arrays and slices of string, boolean, numeric, time.Duration and time.Time +// fields have the value interpreted as a comma separated list of values. The +// individual values are trimmed of whitespace and empty values are ignored. A +// default value can be provided as a semicolon separated list in the field's +// tag. +// +// Struct fields are decoded recursively using the field name plus "." as +// prefix. The prefix (without dot) can be overridden in the field's tag. +// Default values are not supported in the field's tag. Specify them on the +// fields of the inner struct instead. +// +// Map fields must have a key of type string and are decoded recursively by +// using the field's name plus ".' as prefix and the next element of the key +// name as map key. The prefix (without dot) can be overridden in the field's +// tag. Default values are not supported. +// +// Examples: +// +// // Field is ignored. +// Field int `properties:"-"` +// +// // Field is assigned value of 'Field'. +// Field int +// +// // Field is assigned value of 'myName'. +// Field int `properties:"myName"` +// +// // Field is assigned value of key 'myName' and has a default +// // value 15 if the key does not exist. +// Field int `properties:"myName,default=15"` +// +// // Field is assigned value of key 'Field' and has a default +// // value 15 if the key does not exist. +// Field int `properties:",default=15"` +// +// // Field is assigned value of key 'date' and the date +// // is in format 2006-01-02 +// Field time.Time `properties:"date,layout=2006-01-02"` +// +// // Field is assigned the non-empty and whitespace trimmed +// // values of key 'Field' split by commas. +// Field []string +// +// // Field is assigned the non-empty and whitespace trimmed +// // values of key 'Field' split by commas and has a default +// // value ["a", "b", "c"] if the key does not exist. +// Field []string `properties:",default=a;b;c"` +// +// // Field is decoded recursively with "Field." as key prefix. +// Field SomeStruct +// +// // Field is decoded recursively with "myName." as key prefix. +// Field SomeStruct `properties:"myName"` +// +// // Field is decoded recursively with "Field." as key prefix +// // and the next dotted element of the key as map key. +// Field map[string]string +// +// // Field is decoded recursively with "myName." as key prefix +// // and the next dotted element of the key as map key. +// Field map[string]string `properties:"myName"` +func (p *Properties) Decode(x interface{}) error { + t, v := reflect.TypeOf(x), reflect.ValueOf(x) + if t.Kind() != reflect.Ptr || v.Elem().Type().Kind() != reflect.Struct { + return fmt.Errorf("not a pointer to struct: %s", t) + } + if err := dec(p, "", nil, nil, v); err != nil { + return err + } + return nil +} + +func dec(p *Properties, key string, def *string, opts map[string]string, v reflect.Value) error { + t := v.Type() + + // value returns the property value for key or the default if provided. + value := func() (string, error) { + if val, ok := p.Get(key); ok { + return val, nil + } + if def != nil { + return *def, nil + } + return "", fmt.Errorf("missing required key %s", key) + } + + // conv converts a string to a value of the given type. + conv := func(s string, t reflect.Type) (val reflect.Value, err error) { + var v interface{} + + switch { + case isDuration(t): + v, err = time.ParseDuration(s) + + case isTime(t): + layout := opts["layout"] + if layout == "" { + layout = time.RFC3339 + } + v, err = time.Parse(layout, s) + + case isBool(t): + v, err = boolVal(s), nil + + case isString(t): + v, err = s, nil + + case isFloat(t): + v, err = strconv.ParseFloat(s, 64) + + case isInt(t): + v, err = strconv.ParseInt(s, 10, 64) + + case isUint(t): + v, err = strconv.ParseUint(s, 10, 64) + + default: + return reflect.Zero(t), fmt.Errorf("unsupported type %s", t) + } + if err != nil { + return reflect.Zero(t), err + } + return reflect.ValueOf(v).Convert(t), nil + } + + // keydef returns the property key and the default value based on the + // name of the struct field and the options in the tag. + keydef := func(f reflect.StructField) (string, *string, map[string]string) { + _key, _opts := parseTag(f.Tag.Get("properties")) + + var _def *string + if d, ok := _opts["default"]; ok { + _def = &d + } + if _key != "" { + return _key, _def, _opts + } + return f.Name, _def, _opts + } + + switch { + case isDuration(t) || isTime(t) || isBool(t) || isString(t) || isFloat(t) || isInt(t) || isUint(t): + s, err := value() + if err != nil { + return err + } + val, err := conv(s, t) + if err != nil { + return err + } + v.Set(val) + + case isPtr(t): + return dec(p, key, def, opts, v.Elem()) + + case isStruct(t): + for i := 0; i < v.NumField(); i++ { + fv := v.Field(i) + fk, def, opts := keydef(t.Field(i)) + if !fv.CanSet() { + return fmt.Errorf("cannot set %s", t.Field(i).Name) + } + if fk == "-" { + continue + } + if key != "" { + fk = key + "." + fk + } + if err := dec(p, fk, def, opts, fv); err != nil { + return err + } + } + return nil + + case isArray(t): + val, err := value() + if err != nil { + return err + } + vals := split(val, ";") + a := reflect.MakeSlice(t, 0, len(vals)) + for _, s := range vals { + val, err := conv(s, t.Elem()) + if err != nil { + return err + } + a = reflect.Append(a, val) + } + v.Set(a) + + case isMap(t): + valT := t.Elem() + m := reflect.MakeMap(t) + for postfix := range p.FilterStripPrefix(key + ".").m { + pp := strings.SplitN(postfix, ".", 2) + mk, mv := pp[0], reflect.New(valT) + if err := dec(p, key+"."+mk, nil, nil, mv); err != nil { + return err + } + m.SetMapIndex(reflect.ValueOf(mk), mv.Elem()) + } + v.Set(m) + + default: + return fmt.Errorf("unsupported type %s", t) + } + return nil +} + +// split splits a string on sep, trims whitespace of elements +// and omits empty elements +func split(s string, sep string) []string { + var a []string + for _, v := range strings.Split(s, sep) { + if v = strings.TrimSpace(v); v != "" { + a = append(a, v) + } + } + return a +} + +// parseTag parses a "key,k=v,k=v,..." +func parseTag(tag string) (key string, opts map[string]string) { + opts = map[string]string{} + for i, s := range strings.Split(tag, ",") { + if i == 0 { + key = s + continue + } + + pp := strings.SplitN(s, "=", 2) + if len(pp) == 1 { + opts[pp[0]] = "" + } else { + opts[pp[0]] = pp[1] + } + } + return key, opts +} + +func isArray(t reflect.Type) bool { return t.Kind() == reflect.Array || t.Kind() == reflect.Slice } +func isBool(t reflect.Type) bool { return t.Kind() == reflect.Bool } +func isDuration(t reflect.Type) bool { return t == reflect.TypeOf(time.Second) } +func isMap(t reflect.Type) bool { return t.Kind() == reflect.Map } +func isPtr(t reflect.Type) bool { return t.Kind() == reflect.Ptr } +func isString(t reflect.Type) bool { return t.Kind() == reflect.String } +func isStruct(t reflect.Type) bool { return t.Kind() == reflect.Struct } +func isTime(t reflect.Type) bool { return t == reflect.TypeOf(time.Time{}) } +func isFloat(t reflect.Type) bool { + return t.Kind() == reflect.Float32 || t.Kind() == reflect.Float64 +} +func isInt(t reflect.Type) bool { + return t.Kind() == reflect.Int || t.Kind() == reflect.Int8 || t.Kind() == reflect.Int16 || t.Kind() == reflect.Int32 || t.Kind() == reflect.Int64 +} +func isUint(t reflect.Type) bool { + return t.Kind() == reflect.Uint || t.Kind() == reflect.Uint8 || t.Kind() == reflect.Uint16 || t.Kind() == reflect.Uint32 || t.Kind() == reflect.Uint64 +} diff --git a/vendor/github.com/magiconair/properties/doc.go b/vendor/github.com/magiconair/properties/doc.go new file mode 100644 index 0000000..f8822da --- /dev/null +++ b/vendor/github.com/magiconair/properties/doc.go @@ -0,0 +1,156 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package properties provides functions for reading and writing +// ISO-8859-1 and UTF-8 encoded .properties files and has +// support for recursive property expansion. +// +// Java properties files are ISO-8859-1 encoded and use Unicode +// literals for characters outside the ISO character set. Unicode +// literals can be used in UTF-8 encoded properties files but +// aren't necessary. +// +// To load a single properties file use MustLoadFile(): +// +// p := properties.MustLoadFile(filename, properties.UTF8) +// +// To load multiple properties files use MustLoadFiles() +// which loads the files in the given order and merges the +// result. Missing properties files can be ignored if the +// 'ignoreMissing' flag is set to true. +// +// Filenames can contain environment variables which are expanded +// before loading. +// +// f1 := "/etc/myapp/myapp.conf" +// f2 := "/home/${USER}/myapp.conf" +// p := MustLoadFiles([]string{f1, f2}, properties.UTF8, true) +// +// All of the different key/value delimiters ' ', ':' and '=' are +// supported as well as the comment characters '!' and '#' and +// multi-line values. +// +// ! this is a comment +// # and so is this +// +// # the following expressions are equal +// key value +// key=value +// key:value +// key = value +// key : value +// key = val\ +// ue +// +// Properties stores all comments preceding a key and provides +// GetComments() and SetComments() methods to retrieve and +// update them. The convenience functions GetComment() and +// SetComment() allow access to the last comment. The +// WriteComment() method writes properties files including +// the comments and with the keys in the original order. +// This can be used for sanitizing properties files. +// +// Property expansion is recursive and circular references +// and malformed expressions are not allowed and cause an +// error. Expansion of environment variables is supported. +// +// # standard property +// key = value +// +// # property expansion: key2 = value +// key2 = ${key} +// +// # recursive expansion: key3 = value +// key3 = ${key2} +// +// # circular reference (error) +// key = ${key} +// +// # malformed expression (error) +// key = ${ke +// +// # refers to the users' home dir +// home = ${HOME} +// +// # local key takes precedence over env var: u = foo +// USER = foo +// u = ${USER} +// +// The default property expansion format is ${key} but can be +// changed by setting different pre- and postfix values on the +// Properties object. +// +// p := properties.NewProperties() +// p.Prefix = "#[" +// p.Postfix = "]#" +// +// Properties provides convenience functions for getting typed +// values with default values if the key does not exist or the +// type conversion failed. +// +// # Returns true if the value is either "1", "on", "yes" or "true" +// # Returns false for every other value and the default value if +// # the key does not exist. +// v = p.GetBool("key", false) +// +// # Returns the value if the key exists and the format conversion +// # was successful. Otherwise, the default value is returned. +// v = p.GetInt64("key", 999) +// v = p.GetUint64("key", 999) +// v = p.GetFloat64("key", 123.0) +// v = p.GetString("key", "def") +// v = p.GetDuration("key", 999) +// +// As an alternative properties may be applied with the standard +// library's flag implementation at any time. +// +// # Standard configuration +// v = flag.Int("key", 999, "help message") +// flag.Parse() +// +// # Merge p into the flag set +// p.MustFlag(flag.CommandLine) +// +// Properties provides several MustXXX() convenience functions +// which will terminate the app if an error occurs. The behavior +// of the failure is configurable and the default is to call +// log.Fatal(err). To have the MustXXX() functions panic instead +// of logging the error set a different ErrorHandler before +// you use the Properties package. +// +// properties.ErrorHandler = properties.PanicHandler +// +// # Will panic instead of logging an error +// p := properties.MustLoadFile("config.properties") +// +// You can also provide your own ErrorHandler function. The only requirement +// is that the error handler function must exit after handling the error. +// +// properties.ErrorHandler = func(err error) { +// fmt.Println(err) +// os.Exit(1) +// } +// +// # Will write to stdout and then exit +// p := properties.MustLoadFile("config.properties") +// +// Properties can also be loaded into a struct via the `Decode` +// method, e.g. +// +// type S struct { +// A string `properties:"a,default=foo"` +// D time.Duration `properties:"timeout,default=5s"` +// E time.Time `properties:"expires,layout=2006-01-02,default=2015-01-01"` +// } +// +// See `Decode()` method for the full documentation. +// +// The following documents provide a description of the properties +// file format. +// +// http://en.wikipedia.org/wiki/.properties +// +// http://docs.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29 +// +package properties diff --git a/vendor/github.com/magiconair/properties/integrate.go b/vendor/github.com/magiconair/properties/integrate.go new file mode 100644 index 0000000..74d38dc --- /dev/null +++ b/vendor/github.com/magiconair/properties/integrate.go @@ -0,0 +1,34 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package properties + +import "flag" + +// MustFlag sets flags that are skipped by dst.Parse when p contains +// the respective key for flag.Flag.Name. +// +// It's use is recommended with command line arguments as in: +// flag.Parse() +// p.MustFlag(flag.CommandLine) +func (p *Properties) MustFlag(dst *flag.FlagSet) { + m := make(map[string]*flag.Flag) + dst.VisitAll(func(f *flag.Flag) { + m[f.Name] = f + }) + dst.Visit(func(f *flag.Flag) { + delete(m, f.Name) // overridden + }) + + for name, f := range m { + v, ok := p.Get(name) + if !ok { + continue + } + + if err := f.Value.Set(v); err != nil { + ErrorHandler(err) + } + } +} diff --git a/vendor/github.com/magiconair/properties/lex.go b/vendor/github.com/magiconair/properties/lex.go new file mode 100644 index 0000000..367166d --- /dev/null +++ b/vendor/github.com/magiconair/properties/lex.go @@ -0,0 +1,407 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Parts of the lexer are from the template/text/parser package +// For these parts the following applies: +// +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file of the go 1.2 +// distribution. + +package properties + +import ( + "fmt" + "strconv" + "strings" + "unicode/utf8" +) + +// item represents a token or text string returned from the scanner. +type item struct { + typ itemType // The type of this item. + pos int // The starting position, in bytes, of this item in the input string. + val string // The value of this item. +} + +func (i item) String() string { + switch { + case i.typ == itemEOF: + return "EOF" + case i.typ == itemError: + return i.val + case len(i.val) > 10: + return fmt.Sprintf("%.10q...", i.val) + } + return fmt.Sprintf("%q", i.val) +} + +// itemType identifies the type of lex items. +type itemType int + +const ( + itemError itemType = iota // error occurred; value is text of error + itemEOF + itemKey // a key + itemValue // a value + itemComment // a comment +) + +// defines a constant for EOF +const eof = -1 + +// permitted whitespace characters space, FF and TAB +const whitespace = " \f\t" + +// stateFn represents the state of the scanner as a function that returns the next state. +type stateFn func(*lexer) stateFn + +// lexer holds the state of the scanner. +type lexer struct { + input string // the string being scanned + state stateFn // the next lexing function to enter + pos int // current position in the input + start int // start position of this item + width int // width of last rune read from input + lastPos int // position of most recent item returned by nextItem + runes []rune // scanned runes for this item + items chan item // channel of scanned items +} + +// next returns the next rune in the input. +func (l *lexer) next() rune { + if l.pos >= len(l.input) { + l.width = 0 + return eof + } + r, w := utf8.DecodeRuneInString(l.input[l.pos:]) + l.width = w + l.pos += l.width + return r +} + +// peek returns but does not consume the next rune in the input. +func (l *lexer) peek() rune { + r := l.next() + l.backup() + return r +} + +// backup steps back one rune. Can only be called once per call of next. +func (l *lexer) backup() { + l.pos -= l.width +} + +// emit passes an item back to the client. +func (l *lexer) emit(t itemType) { + i := item{t, l.start, string(l.runes)} + l.items <- i + l.start = l.pos + l.runes = l.runes[:0] +} + +// ignore skips over the pending input before this point. +func (l *lexer) ignore() { + l.start = l.pos +} + +// appends the rune to the current value +func (l *lexer) appendRune(r rune) { + l.runes = append(l.runes, r) +} + +// accept consumes the next rune if it's from the valid set. +func (l *lexer) accept(valid string) bool { + if strings.ContainsRune(valid, l.next()) { + return true + } + l.backup() + return false +} + +// acceptRun consumes a run of runes from the valid set. +func (l *lexer) acceptRun(valid string) { + for strings.ContainsRune(valid, l.next()) { + } + l.backup() +} + +// acceptRunUntil consumes a run of runes up to a terminator. +func (l *lexer) acceptRunUntil(term rune) { + for term != l.next() { + } + l.backup() +} + +// hasText returns true if the current parsed text is not empty. +func (l *lexer) isNotEmpty() bool { + return l.pos > l.start +} + +// lineNumber reports which line we're on, based on the position of +// the previous item returned by nextItem. Doing it this way +// means we don't have to worry about peek double counting. +func (l *lexer) lineNumber() int { + return 1 + strings.Count(l.input[:l.lastPos], "\n") +} + +// errorf returns an error token and terminates the scan by passing +// back a nil pointer that will be the next state, terminating l.nextItem. +func (l *lexer) errorf(format string, args ...interface{}) stateFn { + l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)} + return nil +} + +// nextItem returns the next item from the input. +func (l *lexer) nextItem() item { + i := <-l.items + l.lastPos = i.pos + return i +} + +// lex creates a new scanner for the input string. +func lex(input string) *lexer { + l := &lexer{ + input: input, + items: make(chan item), + runes: make([]rune, 0, 32), + } + go l.run() + return l +} + +// run runs the state machine for the lexer. +func (l *lexer) run() { + for l.state = lexBeforeKey(l); l.state != nil; { + l.state = l.state(l) + } +} + +// state functions + +// lexBeforeKey scans until a key begins. +func lexBeforeKey(l *lexer) stateFn { + switch r := l.next(); { + case isEOF(r): + l.emit(itemEOF) + return nil + + case isEOL(r): + l.ignore() + return lexBeforeKey + + case isComment(r): + return lexComment + + case isWhitespace(r): + l.ignore() + return lexBeforeKey + + default: + l.backup() + return lexKey + } +} + +// lexComment scans a comment line. The comment character has already been scanned. +func lexComment(l *lexer) stateFn { + l.acceptRun(whitespace) + l.ignore() + for { + switch r := l.next(); { + case isEOF(r): + l.ignore() + l.emit(itemEOF) + return nil + case isEOL(r): + l.emit(itemComment) + return lexBeforeKey + default: + l.appendRune(r) + } + } +} + +// lexKey scans the key up to a delimiter +func lexKey(l *lexer) stateFn { + var r rune + +Loop: + for { + switch r = l.next(); { + + case isEscape(r): + err := l.scanEscapeSequence() + if err != nil { + return l.errorf(err.Error()) + } + + case isEndOfKey(r): + l.backup() + break Loop + + case isEOF(r): + break Loop + + default: + l.appendRune(r) + } + } + + if len(l.runes) > 0 { + l.emit(itemKey) + } + + if isEOF(r) { + l.emit(itemEOF) + return nil + } + + return lexBeforeValue +} + +// lexBeforeValue scans the delimiter between key and value. +// Leading and trailing whitespace is ignored. +// We expect to be just after the key. +func lexBeforeValue(l *lexer) stateFn { + l.acceptRun(whitespace) + l.accept(":=") + l.acceptRun(whitespace) + l.ignore() + return lexValue +} + +// lexValue scans text until the end of the line. We expect to be just after the delimiter. +func lexValue(l *lexer) stateFn { + for { + switch r := l.next(); { + case isEscape(r): + if isEOL(l.peek()) { + l.next() + l.acceptRun(whitespace) + } else { + err := l.scanEscapeSequence() + if err != nil { + return l.errorf(err.Error()) + } + } + + case isEOL(r): + l.emit(itemValue) + l.ignore() + return lexBeforeKey + + case isEOF(r): + l.emit(itemValue) + l.emit(itemEOF) + return nil + + default: + l.appendRune(r) + } + } +} + +// scanEscapeSequence scans either one of the escaped characters +// or a unicode literal. We expect to be after the escape character. +func (l *lexer) scanEscapeSequence() error { + switch r := l.next(); { + + case isEscapedCharacter(r): + l.appendRune(decodeEscapedCharacter(r)) + return nil + + case atUnicodeLiteral(r): + return l.scanUnicodeLiteral() + + case isEOF(r): + return fmt.Errorf("premature EOF") + + // silently drop the escape character and append the rune as is + default: + l.appendRune(r) + return nil + } +} + +// scans a unicode literal in the form \uXXXX. We expect to be after the \u. +func (l *lexer) scanUnicodeLiteral() error { + // scan the digits + d := make([]rune, 4) + for i := 0; i < 4; i++ { + d[i] = l.next() + if d[i] == eof || !strings.ContainsRune("0123456789abcdefABCDEF", d[i]) { + return fmt.Errorf("invalid unicode literal") + } + } + + // decode the digits into a rune + r, err := strconv.ParseInt(string(d), 16, 0) + if err != nil { + return err + } + + l.appendRune(rune(r)) + return nil +} + +// decodeEscapedCharacter returns the unescaped rune. We expect to be after the escape character. +func decodeEscapedCharacter(r rune) rune { + switch r { + case 'f': + return '\f' + case 'n': + return '\n' + case 'r': + return '\r' + case 't': + return '\t' + default: + return r + } +} + +// atUnicodeLiteral reports whether we are at a unicode literal. +// The escape character has already been consumed. +func atUnicodeLiteral(r rune) bool { + return r == 'u' +} + +// isComment reports whether we are at the start of a comment. +func isComment(r rune) bool { + return r == '#' || r == '!' +} + +// isEndOfKey reports whether the rune terminates the current key. +func isEndOfKey(r rune) bool { + return strings.ContainsRune(" \f\t\r\n:=", r) +} + +// isEOF reports whether we are at EOF. +func isEOF(r rune) bool { + return r == eof +} + +// isEOL reports whether we are at a new line character. +func isEOL(r rune) bool { + return r == '\n' || r == '\r' +} + +// isEscape reports whether the rune is the escape character which +// prefixes unicode literals and other escaped characters. +func isEscape(r rune) bool { + return r == '\\' +} + +// isEscapedCharacter reports whether we are at one of the characters that need escaping. +// The escape character has already been consumed. +func isEscapedCharacter(r rune) bool { + return strings.ContainsRune(" :=fnrt", r) +} + +// isWhitespace reports whether the rune is a whitespace character. +func isWhitespace(r rune) bool { + return strings.ContainsRune(whitespace, r) +} diff --git a/vendor/github.com/magiconair/properties/load.go b/vendor/github.com/magiconair/properties/load.go new file mode 100644 index 0000000..c8e1b58 --- /dev/null +++ b/vendor/github.com/magiconair/properties/load.go @@ -0,0 +1,292 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package properties + +import ( + "fmt" + "io/ioutil" + "net/http" + "os" + "strings" +) + +// Encoding specifies encoding of the input data. +type Encoding uint + +const ( + // utf8Default is a private placeholder for the zero value of Encoding to + // ensure that it has the correct meaning. UTF8 is the default encoding but + // was assigned a non-zero value which cannot be changed without breaking + // existing code. Clients should continue to use the public constants. + utf8Default Encoding = iota + + // UTF8 interprets the input data as UTF-8. + UTF8 + + // ISO_8859_1 interprets the input data as ISO-8859-1. + ISO_8859_1 +) + +type Loader struct { + // Encoding determines how the data from files and byte buffers + // is interpreted. For URLs the Content-Type header is used + // to determine the encoding of the data. + Encoding Encoding + + // DisableExpansion configures the property expansion of the + // returned property object. When set to true, the property values + // will not be expanded and the Property object will not be checked + // for invalid expansion expressions. + DisableExpansion bool + + // IgnoreMissing configures whether missing files or URLs which return + // 404 are reported as errors. When set to true, missing files and 404 + // status codes are not reported as errors. + IgnoreMissing bool +} + +// Load reads a buffer into a Properties struct. +func (l *Loader) LoadBytes(buf []byte) (*Properties, error) { + return l.loadBytes(buf, l.Encoding) +} + +// LoadAll reads the content of multiple URLs or files in the given order into +// a Properties struct. If IgnoreMissing is true then a 404 status code or +// missing file will not be reported as error. Encoding sets the encoding for +// files. For the URLs see LoadURL for the Content-Type header and the +// encoding. +func (l *Loader) LoadAll(names []string) (*Properties, error) { + all := NewProperties() + for _, name := range names { + n, err := expandName(name) + if err != nil { + return nil, err + } + + var p *Properties + switch { + case strings.HasPrefix(n, "http://"): + p, err = l.LoadURL(n) + case strings.HasPrefix(n, "https://"): + p, err = l.LoadURL(n) + default: + p, err = l.LoadFile(n) + } + if err != nil { + return nil, err + } + all.Merge(p) + } + + all.DisableExpansion = l.DisableExpansion + if all.DisableExpansion { + return all, nil + } + return all, all.check() +} + +// LoadFile reads a file into a Properties struct. +// If IgnoreMissing is true then a missing file will not be +// reported as error. +func (l *Loader) LoadFile(filename string) (*Properties, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + if l.IgnoreMissing && os.IsNotExist(err) { + LogPrintf("properties: %s not found. skipping", filename) + return NewProperties(), nil + } + return nil, err + } + return l.loadBytes(data, l.Encoding) +} + +// LoadURL reads the content of the URL into a Properties struct. +// +// The encoding is determined via the Content-Type header which +// should be set to 'text/plain'. If the 'charset' parameter is +// missing, 'iso-8859-1' or 'latin1' the encoding is set to +// ISO-8859-1. If the 'charset' parameter is set to 'utf-8' the +// encoding is set to UTF-8. A missing content type header is +// interpreted as 'text/plain; charset=utf-8'. +func (l *Loader) LoadURL(url string) (*Properties, error) { + resp, err := http.Get(url) + if err != nil { + return nil, fmt.Errorf("properties: error fetching %q. %s", url, err) + } + + if resp.StatusCode == 404 && l.IgnoreMissing { + LogPrintf("properties: %s returned %d. skipping", url, resp.StatusCode) + return NewProperties(), nil + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("properties: %s returned %d", url, resp.StatusCode) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("properties: %s error reading response. %s", url, err) + } + defer resp.Body.Close() + + ct := resp.Header.Get("Content-Type") + var enc Encoding + switch strings.ToLower(ct) { + case "text/plain", "text/plain; charset=iso-8859-1", "text/plain; charset=latin1": + enc = ISO_8859_1 + case "", "text/plain; charset=utf-8": + enc = UTF8 + default: + return nil, fmt.Errorf("properties: invalid content type %s", ct) + } + + return l.loadBytes(body, enc) +} + +func (l *Loader) loadBytes(buf []byte, enc Encoding) (*Properties, error) { + p, err := parse(convert(buf, enc)) + if err != nil { + return nil, err + } + p.DisableExpansion = l.DisableExpansion + if p.DisableExpansion { + return p, nil + } + return p, p.check() +} + +// Load reads a buffer into a Properties struct. +func Load(buf []byte, enc Encoding) (*Properties, error) { + l := &Loader{Encoding: enc} + return l.LoadBytes(buf) +} + +// LoadString reads an UTF8 string into a properties struct. +func LoadString(s string) (*Properties, error) { + l := &Loader{Encoding: UTF8} + return l.LoadBytes([]byte(s)) +} + +// LoadMap creates a new Properties struct from a string map. +func LoadMap(m map[string]string) *Properties { + p := NewProperties() + for k, v := range m { + p.Set(k, v) + } + return p +} + +// LoadFile reads a file into a Properties struct. +func LoadFile(filename string, enc Encoding) (*Properties, error) { + l := &Loader{Encoding: enc} + return l.LoadAll([]string{filename}) +} + +// LoadFiles reads multiple files in the given order into +// a Properties struct. If 'ignoreMissing' is true then +// non-existent files will not be reported as error. +func LoadFiles(filenames []string, enc Encoding, ignoreMissing bool) (*Properties, error) { + l := &Loader{Encoding: enc, IgnoreMissing: ignoreMissing} + return l.LoadAll(filenames) +} + +// LoadURL reads the content of the URL into a Properties struct. +// See Loader#LoadURL for details. +func LoadURL(url string) (*Properties, error) { + l := &Loader{Encoding: UTF8} + return l.LoadAll([]string{url}) +} + +// LoadURLs reads the content of multiple URLs in the given order into a +// Properties struct. If IgnoreMissing is true then a 404 status code will +// not be reported as error. See Loader#LoadURL for the Content-Type header +// and the encoding. +func LoadURLs(urls []string, ignoreMissing bool) (*Properties, error) { + l := &Loader{Encoding: UTF8, IgnoreMissing: ignoreMissing} + return l.LoadAll(urls) +} + +// LoadAll reads the content of multiple URLs or files in the given order into a +// Properties struct. If 'ignoreMissing' is true then a 404 status code or missing file will +// not be reported as error. Encoding sets the encoding for files. For the URLs please see +// LoadURL for the Content-Type header and the encoding. +func LoadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, error) { + l := &Loader{Encoding: enc, IgnoreMissing: ignoreMissing} + return l.LoadAll(names) +} + +// MustLoadString reads an UTF8 string into a Properties struct and +// panics on error. +func MustLoadString(s string) *Properties { + return must(LoadString(s)) +} + +// MustLoadFile reads a file into a Properties struct and +// panics on error. +func MustLoadFile(filename string, enc Encoding) *Properties { + return must(LoadFile(filename, enc)) +} + +// MustLoadFiles reads multiple files in the given order into +// a Properties struct and panics on error. If 'ignoreMissing' +// is true then non-existent files will not be reported as error. +func MustLoadFiles(filenames []string, enc Encoding, ignoreMissing bool) *Properties { + return must(LoadFiles(filenames, enc, ignoreMissing)) +} + +// MustLoadURL reads the content of a URL into a Properties struct and +// panics on error. +func MustLoadURL(url string) *Properties { + return must(LoadURL(url)) +} + +// MustLoadURLs reads the content of multiple URLs in the given order into a +// Properties struct and panics on error. If 'ignoreMissing' is true then a 404 +// status code will not be reported as error. +func MustLoadURLs(urls []string, ignoreMissing bool) *Properties { + return must(LoadURLs(urls, ignoreMissing)) +} + +// MustLoadAll reads the content of multiple URLs or files in the given order into a +// Properties struct. If 'ignoreMissing' is true then a 404 status code or missing file will +// not be reported as error. Encoding sets the encoding for files. For the URLs please see +// LoadURL for the Content-Type header and the encoding. It panics on error. +func MustLoadAll(names []string, enc Encoding, ignoreMissing bool) *Properties { + return must(LoadAll(names, enc, ignoreMissing)) +} + +func must(p *Properties, err error) *Properties { + if err != nil { + ErrorHandler(err) + } + return p +} + +// expandName expands ${ENV_VAR} expressions in a name. +// If the environment variable does not exist then it will be replaced +// with an empty string. Malformed expressions like "${ENV_VAR" will +// be reported as error. +func expandName(name string) (string, error) { + return expand(name, []string{}, "${", "}", make(map[string]string)) +} + +// Interprets a byte buffer either as an ISO-8859-1 or UTF-8 encoded string. +// For ISO-8859-1 we can convert each byte straight into a rune since the +// first 256 unicode code points cover ISO-8859-1. +func convert(buf []byte, enc Encoding) string { + switch enc { + case utf8Default, UTF8: + return string(buf) + case ISO_8859_1: + runes := make([]rune, len(buf)) + for i, b := range buf { + runes[i] = rune(b) + } + return string(runes) + default: + ErrorHandler(fmt.Errorf("unsupported encoding %v", enc)) + } + panic("ErrorHandler should exit") +} diff --git a/vendor/github.com/magiconair/properties/parser.go b/vendor/github.com/magiconair/properties/parser.go new file mode 100644 index 0000000..cdc4a80 --- /dev/null +++ b/vendor/github.com/magiconair/properties/parser.go @@ -0,0 +1,95 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package properties + +import ( + "fmt" + "runtime" +) + +type parser struct { + lex *lexer +} + +func parse(input string) (properties *Properties, err error) { + p := &parser{lex: lex(input)} + defer p.recover(&err) + + properties = NewProperties() + key := "" + comments := []string{} + + for { + token := p.expectOneOf(itemComment, itemKey, itemEOF) + switch token.typ { + case itemEOF: + goto done + case itemComment: + comments = append(comments, token.val) + continue + case itemKey: + key = token.val + if _, ok := properties.m[key]; !ok { + properties.k = append(properties.k, key) + } + } + + token = p.expectOneOf(itemValue, itemEOF) + if len(comments) > 0 { + properties.c[key] = comments + comments = []string{} + } + switch token.typ { + case itemEOF: + properties.m[key] = "" + goto done + case itemValue: + properties.m[key] = token.val + } + } + +done: + return properties, nil +} + +func (p *parser) errorf(format string, args ...interface{}) { + format = fmt.Sprintf("properties: Line %d: %s", p.lex.lineNumber(), format) + panic(fmt.Errorf(format, args...)) +} + +func (p *parser) expect(expected itemType) (token item) { + token = p.lex.nextItem() + if token.typ != expected { + p.unexpected(token) + } + return token +} + +func (p *parser) expectOneOf(expected ...itemType) (token item) { + token = p.lex.nextItem() + for _, v := range expected { + if token.typ == v { + return token + } + } + p.unexpected(token) + panic("unexpected token") +} + +func (p *parser) unexpected(token item) { + p.errorf(token.String()) +} + +// recover is the handler that turns panics into returns from the top level of Parse. +func (p *parser) recover(errp *error) { + e := recover() + if e != nil { + if _, ok := e.(runtime.Error); ok { + panic(e) + } + *errp = e.(error) + } + return +} diff --git a/vendor/github.com/magiconair/properties/properties.go b/vendor/github.com/magiconair/properties/properties.go new file mode 100644 index 0000000..cb3d1a3 --- /dev/null +++ b/vendor/github.com/magiconair/properties/properties.go @@ -0,0 +1,833 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package properties + +// BUG(frank): Set() does not check for invalid unicode literals since this is currently handled by the lexer. +// BUG(frank): Write() does not allow to configure the newline character. Therefore, on Windows LF is used. + +import ( + "fmt" + "io" + "log" + "os" + "regexp" + "strconv" + "strings" + "time" + "unicode/utf8" +) + +const maxExpansionDepth = 64 + +// ErrorHandlerFunc defines the type of function which handles failures +// of the MustXXX() functions. An error handler function must exit +// the application after handling the error. +type ErrorHandlerFunc func(error) + +// ErrorHandler is the function which handles failures of the MustXXX() +// functions. The default is LogFatalHandler. +var ErrorHandler ErrorHandlerFunc = LogFatalHandler + +// LogHandlerFunc defines the function prototype for logging errors. +type LogHandlerFunc func(fmt string, args ...interface{}) + +// LogPrintf defines a log handler which uses log.Printf. +var LogPrintf LogHandlerFunc = log.Printf + +// LogFatalHandler handles the error by logging a fatal error and exiting. +func LogFatalHandler(err error) { + log.Fatal(err) +} + +// PanicHandler handles the error by panicking. +func PanicHandler(err error) { + panic(err) +} + +// ----------------------------------------------------------------------------- + +// A Properties contains the key/value pairs from the properties input. +// All values are stored in unexpanded form and are expanded at runtime +type Properties struct { + // Pre-/Postfix for property expansion. + Prefix string + Postfix string + + // DisableExpansion controls the expansion of properties on Get() + // and the check for circular references on Set(). When set to + // true Properties behaves like a simple key/value store and does + // not check for circular references on Get() or on Set(). + DisableExpansion bool + + // Stores the key/value pairs + m map[string]string + + // Stores the comments per key. + c map[string][]string + + // Stores the keys in order of appearance. + k []string +} + +// NewProperties creates a new Properties struct with the default +// configuration for "${key}" expressions. +func NewProperties() *Properties { + return &Properties{ + Prefix: "${", + Postfix: "}", + m: map[string]string{}, + c: map[string][]string{}, + k: []string{}, + } +} + +// Load reads a buffer into the given Properties struct. +func (p *Properties) Load(buf []byte, enc Encoding) error { + l := &Loader{Encoding: enc, DisableExpansion: p.DisableExpansion} + newProperties, err := l.LoadBytes(buf) + if err != nil { + return err + } + p.Merge(newProperties) + return nil +} + +// Get returns the expanded value for the given key if exists. +// Otherwise, ok is false. +func (p *Properties) Get(key string) (value string, ok bool) { + v, ok := p.m[key] + if p.DisableExpansion { + return v, ok + } + if !ok { + return "", false + } + + expanded, err := p.expand(key, v) + + // we guarantee that the expanded value is free of + // circular references and malformed expressions + // so we panic if we still get an error here. + if err != nil { + ErrorHandler(fmt.Errorf("%s in %q", err, key+" = "+v)) + } + + return expanded, true +} + +// MustGet returns the expanded value for the given key if exists. +// Otherwise, it panics. +func (p *Properties) MustGet(key string) string { + if v, ok := p.Get(key); ok { + return v + } + ErrorHandler(invalidKeyError(key)) + panic("ErrorHandler should exit") +} + +// ---------------------------------------------------------------------------- + +// ClearComments removes the comments for all keys. +func (p *Properties) ClearComments() { + p.c = map[string][]string{} +} + +// ---------------------------------------------------------------------------- + +// GetComment returns the last comment before the given key or an empty string. +func (p *Properties) GetComment(key string) string { + comments, ok := p.c[key] + if !ok || len(comments) == 0 { + return "" + } + return comments[len(comments)-1] +} + +// ---------------------------------------------------------------------------- + +// GetComments returns all comments that appeared before the given key or nil. +func (p *Properties) GetComments(key string) []string { + if comments, ok := p.c[key]; ok { + return comments + } + return nil +} + +// ---------------------------------------------------------------------------- + +// SetComment sets the comment for the key. +func (p *Properties) SetComment(key, comment string) { + p.c[key] = []string{comment} +} + +// ---------------------------------------------------------------------------- + +// SetComments sets the comments for the key. If the comments are nil then +// all comments for this key are deleted. +func (p *Properties) SetComments(key string, comments []string) { + if comments == nil { + delete(p.c, key) + return + } + p.c[key] = comments +} + +// ---------------------------------------------------------------------------- + +// GetBool checks if the expanded value is one of '1', 'yes', +// 'true' or 'on' if the key exists. The comparison is case-insensitive. +// If the key does not exist the default value is returned. +func (p *Properties) GetBool(key string, def bool) bool { + v, err := p.getBool(key) + if err != nil { + return def + } + return v +} + +// MustGetBool checks if the expanded value is one of '1', 'yes', +// 'true' or 'on' if the key exists. The comparison is case-insensitive. +// If the key does not exist the function panics. +func (p *Properties) MustGetBool(key string) bool { + v, err := p.getBool(key) + if err != nil { + ErrorHandler(err) + } + return v +} + +func (p *Properties) getBool(key string) (value bool, err error) { + if v, ok := p.Get(key); ok { + return boolVal(v), nil + } + return false, invalidKeyError(key) +} + +func boolVal(v string) bool { + v = strings.ToLower(v) + return v == "1" || v == "true" || v == "yes" || v == "on" +} + +// ---------------------------------------------------------------------------- + +// GetDuration parses the expanded value as an time.Duration (in ns) if the +// key exists. If key does not exist or the value cannot be parsed the default +// value is returned. In almost all cases you want to use GetParsedDuration(). +func (p *Properties) GetDuration(key string, def time.Duration) time.Duration { + v, err := p.getInt64(key) + if err != nil { + return def + } + return time.Duration(v) +} + +// MustGetDuration parses the expanded value as an time.Duration (in ns) if +// the key exists. If key does not exist or the value cannot be parsed the +// function panics. In almost all cases you want to use MustGetParsedDuration(). +func (p *Properties) MustGetDuration(key string) time.Duration { + v, err := p.getInt64(key) + if err != nil { + ErrorHandler(err) + } + return time.Duration(v) +} + +// ---------------------------------------------------------------------------- + +// GetParsedDuration parses the expanded value with time.ParseDuration() if the key exists. +// If key does not exist or the value cannot be parsed the default +// value is returned. +func (p *Properties) GetParsedDuration(key string, def time.Duration) time.Duration { + s, ok := p.Get(key) + if !ok { + return def + } + v, err := time.ParseDuration(s) + if err != nil { + return def + } + return v +} + +// MustGetParsedDuration parses the expanded value with time.ParseDuration() if the key exists. +// If key does not exist or the value cannot be parsed the function panics. +func (p *Properties) MustGetParsedDuration(key string) time.Duration { + s, ok := p.Get(key) + if !ok { + ErrorHandler(invalidKeyError(key)) + } + v, err := time.ParseDuration(s) + if err != nil { + ErrorHandler(err) + } + return v +} + +// ---------------------------------------------------------------------------- + +// GetFloat64 parses the expanded value as a float64 if the key exists. +// If key does not exist or the value cannot be parsed the default +// value is returned. +func (p *Properties) GetFloat64(key string, def float64) float64 { + v, err := p.getFloat64(key) + if err != nil { + return def + } + return v +} + +// MustGetFloat64 parses the expanded value as a float64 if the key exists. +// If key does not exist or the value cannot be parsed the function panics. +func (p *Properties) MustGetFloat64(key string) float64 { + v, err := p.getFloat64(key) + if err != nil { + ErrorHandler(err) + } + return v +} + +func (p *Properties) getFloat64(key string) (value float64, err error) { + if v, ok := p.Get(key); ok { + value, err = strconv.ParseFloat(v, 64) + if err != nil { + return 0, err + } + return value, nil + } + return 0, invalidKeyError(key) +} + +// ---------------------------------------------------------------------------- + +// GetInt parses the expanded value as an int if the key exists. +// If key does not exist or the value cannot be parsed the default +// value is returned. If the value does not fit into an int the +// function panics with an out of range error. +func (p *Properties) GetInt(key string, def int) int { + v, err := p.getInt64(key) + if err != nil { + return def + } + return intRangeCheck(key, v) +} + +// MustGetInt parses the expanded value as an int if the key exists. +// If key does not exist or the value cannot be parsed the function panics. +// If the value does not fit into an int the function panics with +// an out of range error. +func (p *Properties) MustGetInt(key string) int { + v, err := p.getInt64(key) + if err != nil { + ErrorHandler(err) + } + return intRangeCheck(key, v) +} + +// ---------------------------------------------------------------------------- + +// GetInt64 parses the expanded value as an int64 if the key exists. +// If key does not exist or the value cannot be parsed the default +// value is returned. +func (p *Properties) GetInt64(key string, def int64) int64 { + v, err := p.getInt64(key) + if err != nil { + return def + } + return v +} + +// MustGetInt64 parses the expanded value as an int if the key exists. +// If key does not exist or the value cannot be parsed the function panics. +func (p *Properties) MustGetInt64(key string) int64 { + v, err := p.getInt64(key) + if err != nil { + ErrorHandler(err) + } + return v +} + +func (p *Properties) getInt64(key string) (value int64, err error) { + if v, ok := p.Get(key); ok { + value, err = strconv.ParseInt(v, 10, 64) + if err != nil { + return 0, err + } + return value, nil + } + return 0, invalidKeyError(key) +} + +// ---------------------------------------------------------------------------- + +// GetUint parses the expanded value as an uint if the key exists. +// If key does not exist or the value cannot be parsed the default +// value is returned. If the value does not fit into an int the +// function panics with an out of range error. +func (p *Properties) GetUint(key string, def uint) uint { + v, err := p.getUint64(key) + if err != nil { + return def + } + return uintRangeCheck(key, v) +} + +// MustGetUint parses the expanded value as an int if the key exists. +// If key does not exist or the value cannot be parsed the function panics. +// If the value does not fit into an int the function panics with +// an out of range error. +func (p *Properties) MustGetUint(key string) uint { + v, err := p.getUint64(key) + if err != nil { + ErrorHandler(err) + } + return uintRangeCheck(key, v) +} + +// ---------------------------------------------------------------------------- + +// GetUint64 parses the expanded value as an uint64 if the key exists. +// If key does not exist or the value cannot be parsed the default +// value is returned. +func (p *Properties) GetUint64(key string, def uint64) uint64 { + v, err := p.getUint64(key) + if err != nil { + return def + } + return v +} + +// MustGetUint64 parses the expanded value as an int if the key exists. +// If key does not exist or the value cannot be parsed the function panics. +func (p *Properties) MustGetUint64(key string) uint64 { + v, err := p.getUint64(key) + if err != nil { + ErrorHandler(err) + } + return v +} + +func (p *Properties) getUint64(key string) (value uint64, err error) { + if v, ok := p.Get(key); ok { + value, err = strconv.ParseUint(v, 10, 64) + if err != nil { + return 0, err + } + return value, nil + } + return 0, invalidKeyError(key) +} + +// ---------------------------------------------------------------------------- + +// GetString returns the expanded value for the given key if exists or +// the default value otherwise. +func (p *Properties) GetString(key, def string) string { + if v, ok := p.Get(key); ok { + return v + } + return def +} + +// MustGetString returns the expanded value for the given key if exists or +// panics otherwise. +func (p *Properties) MustGetString(key string) string { + if v, ok := p.Get(key); ok { + return v + } + ErrorHandler(invalidKeyError(key)) + panic("ErrorHandler should exit") +} + +// ---------------------------------------------------------------------------- + +// Filter returns a new properties object which contains all properties +// for which the key matches the pattern. +func (p *Properties) Filter(pattern string) (*Properties, error) { + re, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + + return p.FilterRegexp(re), nil +} + +// FilterRegexp returns a new properties object which contains all properties +// for which the key matches the regular expression. +func (p *Properties) FilterRegexp(re *regexp.Regexp) *Properties { + pp := NewProperties() + for _, k := range p.k { + if re.MatchString(k) { + // TODO(fs): we are ignoring the error which flags a circular reference. + // TODO(fs): since we are just copying a subset of keys this cannot happen (fingers crossed) + pp.Set(k, p.m[k]) + } + } + return pp +} + +// FilterPrefix returns a new properties object with a subset of all keys +// with the given prefix. +func (p *Properties) FilterPrefix(prefix string) *Properties { + pp := NewProperties() + for _, k := range p.k { + if strings.HasPrefix(k, prefix) { + // TODO(fs): we are ignoring the error which flags a circular reference. + // TODO(fs): since we are just copying a subset of keys this cannot happen (fingers crossed) + pp.Set(k, p.m[k]) + } + } + return pp +} + +// FilterStripPrefix returns a new properties object with a subset of all keys +// with the given prefix and the prefix removed from the keys. +func (p *Properties) FilterStripPrefix(prefix string) *Properties { + pp := NewProperties() + n := len(prefix) + for _, k := range p.k { + if len(k) > len(prefix) && strings.HasPrefix(k, prefix) { + // TODO(fs): we are ignoring the error which flags a circular reference. + // TODO(fs): since we are modifying keys I am not entirely sure whether we can create a circular reference + // TODO(fs): this function should probably return an error but the signature is fixed + pp.Set(k[n:], p.m[k]) + } + } + return pp +} + +// Len returns the number of keys. +func (p *Properties) Len() int { + return len(p.m) +} + +// Keys returns all keys in the same order as in the input. +func (p *Properties) Keys() []string { + keys := make([]string, len(p.k)) + copy(keys, p.k) + return keys +} + +// Set sets the property key to the corresponding value. +// If a value for key existed before then ok is true and prev +// contains the previous value. If the value contains a +// circular reference or a malformed expression then +// an error is returned. +// An empty key is silently ignored. +func (p *Properties) Set(key, value string) (prev string, ok bool, err error) { + if key == "" { + return "", false, nil + } + + // if expansion is disabled we allow circular references + if p.DisableExpansion { + prev, ok = p.Get(key) + p.m[key] = value + if !ok { + p.k = append(p.k, key) + } + return prev, ok, nil + } + + // to check for a circular reference we temporarily need + // to set the new value. If there is an error then revert + // to the previous state. Only if all tests are successful + // then we add the key to the p.k list. + prev, ok = p.Get(key) + p.m[key] = value + + // now check for a circular reference + _, err = p.expand(key, value) + if err != nil { + + // revert to the previous state + if ok { + p.m[key] = prev + } else { + delete(p.m, key) + } + + return "", false, err + } + + if !ok { + p.k = append(p.k, key) + } + + return prev, ok, nil +} + +// SetValue sets property key to the default string value +// as defined by fmt.Sprintf("%v"). +func (p *Properties) SetValue(key string, value interface{}) error { + _, _, err := p.Set(key, fmt.Sprintf("%v", value)) + return err +} + +// MustSet sets the property key to the corresponding value. +// If a value for key existed before then ok is true and prev +// contains the previous value. An empty key is silently ignored. +func (p *Properties) MustSet(key, value string) (prev string, ok bool) { + prev, ok, err := p.Set(key, value) + if err != nil { + ErrorHandler(err) + } + return prev, ok +} + +// String returns a string of all expanded 'key = value' pairs. +func (p *Properties) String() string { + var s string + for _, key := range p.k { + value, _ := p.Get(key) + s = fmt.Sprintf("%s%s = %s\n", s, key, value) + } + return s +} + +// Write writes all unexpanded 'key = value' pairs to the given writer. +// Write returns the number of bytes written and any write error encountered. +func (p *Properties) Write(w io.Writer, enc Encoding) (n int, err error) { + return p.WriteComment(w, "", enc) +} + +// WriteComment writes all unexpanced 'key = value' pairs to the given writer. +// If prefix is not empty then comments are written with a blank line and the +// given prefix. The prefix should be either "# " or "! " to be compatible with +// the properties file format. Otherwise, the properties parser will not be +// able to read the file back in. It returns the number of bytes written and +// any write error encountered. +func (p *Properties) WriteComment(w io.Writer, prefix string, enc Encoding) (n int, err error) { + var x int + + for _, key := range p.k { + value := p.m[key] + + if prefix != "" { + if comments, ok := p.c[key]; ok { + // don't print comments if they are all empty + allEmpty := true + for _, c := range comments { + if c != "" { + allEmpty = false + break + } + } + + if !allEmpty { + // add a blank line between entries but not at the top + if len(comments) > 0 && n > 0 { + x, err = fmt.Fprintln(w) + if err != nil { + return + } + n += x + } + + for _, c := range comments { + x, err = fmt.Fprintf(w, "%s%s\n", prefix, encode(c, "", enc)) + if err != nil { + return + } + n += x + } + } + } + } + + x, err = fmt.Fprintf(w, "%s = %s\n", encode(key, " :", enc), encode(value, "", enc)) + if err != nil { + return + } + n += x + } + return +} + +// Map returns a copy of the properties as a map. +func (p *Properties) Map() map[string]string { + m := make(map[string]string) + for k, v := range p.m { + m[k] = v + } + return m +} + +// FilterFunc returns a copy of the properties which includes the values which passed all filters. +func (p *Properties) FilterFunc(filters ...func(k, v string) bool) *Properties { + pp := NewProperties() +outer: + for k, v := range p.m { + for _, f := range filters { + if !f(k, v) { + continue outer + } + pp.Set(k, v) + } + } + return pp +} + +// ---------------------------------------------------------------------------- + +// Delete removes the key and its comments. +func (p *Properties) Delete(key string) { + delete(p.m, key) + delete(p.c, key) + newKeys := []string{} + for _, k := range p.k { + if k != key { + newKeys = append(newKeys, k) + } + } + p.k = newKeys +} + +// Merge merges properties, comments and keys from other *Properties into p +func (p *Properties) Merge(other *Properties) { + for k, v := range other.m { + p.m[k] = v + } + for k, v := range other.c { + p.c[k] = v + } + +outer: + for _, otherKey := range other.k { + for _, key := range p.k { + if otherKey == key { + continue outer + } + } + p.k = append(p.k, otherKey) + } +} + +// ---------------------------------------------------------------------------- + +// check expands all values and returns an error if a circular reference or +// a malformed expression was found. +func (p *Properties) check() error { + for key, value := range p.m { + if _, err := p.expand(key, value); err != nil { + return err + } + } + return nil +} + +func (p *Properties) expand(key, input string) (string, error) { + // no pre/postfix -> nothing to expand + if p.Prefix == "" && p.Postfix == "" { + return input, nil + } + + return expand(input, []string{key}, p.Prefix, p.Postfix, p.m) +} + +// expand recursively expands expressions of '(prefix)key(postfix)' to their corresponding values. +// The function keeps track of the keys that were already expanded and stops if it +// detects a circular reference or a malformed expression of the form '(prefix)key'. +func expand(s string, keys []string, prefix, postfix string, values map[string]string) (string, error) { + if len(keys) > maxExpansionDepth { + return "", fmt.Errorf("expansion too deep") + } + + for { + start := strings.Index(s, prefix) + if start == -1 { + return s, nil + } + + keyStart := start + len(prefix) + keyLen := strings.Index(s[keyStart:], postfix) + if keyLen == -1 { + return "", fmt.Errorf("malformed expression") + } + + end := keyStart + keyLen + len(postfix) - 1 + key := s[keyStart : keyStart+keyLen] + + // fmt.Printf("s:%q pp:%q start:%d end:%d keyStart:%d keyLen:%d key:%q\n", s, prefix + "..." + postfix, start, end, keyStart, keyLen, key) + + for _, k := range keys { + if key == k { + return "", fmt.Errorf("circular reference") + } + } + + val, ok := values[key] + if !ok { + val = os.Getenv(key) + } + new_val, err := expand(val, append(keys, key), prefix, postfix, values) + if err != nil { + return "", err + } + s = s[:start] + new_val + s[end+1:] + } + return s, nil +} + +// encode encodes a UTF-8 string to ISO-8859-1 and escapes some characters. +func encode(s string, special string, enc Encoding) string { + switch enc { + case UTF8: + return encodeUtf8(s, special) + case ISO_8859_1: + return encodeIso(s, special) + default: + panic(fmt.Sprintf("unsupported encoding %v", enc)) + } +} + +func encodeUtf8(s string, special string) string { + v := "" + for pos := 0; pos < len(s); { + r, w := utf8.DecodeRuneInString(s[pos:]) + pos += w + v += escape(r, special) + } + return v +} + +func encodeIso(s string, special string) string { + var r rune + var w int + var v string + for pos := 0; pos < len(s); { + switch r, w = utf8.DecodeRuneInString(s[pos:]); { + case r < 1<<8: // single byte rune -> escape special chars only + v += escape(r, special) + case r < 1<<16: // two byte rune -> unicode literal + v += fmt.Sprintf("\\u%04x", r) + default: // more than two bytes per rune -> can't encode + v += "?" + } + pos += w + } + return v +} + +func escape(r rune, special string) string { + switch r { + case '\f': + return "\\f" + case '\n': + return "\\n" + case '\r': + return "\\r" + case '\t': + return "\\t" + default: + if strings.ContainsRune(special, r) { + return "\\" + string(r) + } + return string(r) + } +} + +func invalidKeyError(key string) error { + return fmt.Errorf("unknown property: %s", key) +} diff --git a/vendor/github.com/magiconair/properties/rangecheck.go b/vendor/github.com/magiconair/properties/rangecheck.go new file mode 100644 index 0000000..b013a2e --- /dev/null +++ b/vendor/github.com/magiconair/properties/rangecheck.go @@ -0,0 +1,31 @@ +// Copyright 2018 Frank Schroeder. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package properties + +import ( + "fmt" + "math" +) + +// make this a var to overwrite it in a test +var is32Bit = ^uint(0) == math.MaxUint32 + +// intRangeCheck checks if the value fits into the int type and +// panics if it does not. +func intRangeCheck(key string, v int64) int { + if is32Bit && (v < math.MinInt32 || v > math.MaxInt32) { + panic(fmt.Sprintf("Value %d for key %s out of range", v, key)) + } + return int(v) +} + +// uintRangeCheck checks if the value fits into the uint type and +// panics if it does not. +func uintRangeCheck(key string, v uint64) uint { + if is32Bit && v > math.MaxUint32 { + panic(fmt.Sprintf("Value %d for key %s out of range", v, key)) + } + return uint(v) +}