You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by nf...@apache.org on 2020/02/22 13:33:22 UTC
[camel-k] branch master updated (9a7c4f7 -> abacac8)
This is an automated email from the ASF dual-hosted git repository.
nferraro pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/camel-k.git.
from 9a7c4f7 chore(ci): upgrade to actions/checkout@v2
new 5be5ac7 Fix #1286: overwrite resources when --force option is passed to kamel install
new 6c15e5d Fix #1286: fix tests
new 794bd2d Fix #1286: fix linter
new abacac8 Fix #1286: fix tekton test
The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
e2e/tekton_test.go | 2 +-
e2e/test_support.go | 8 ++--
pkg/cmd/install.go | 13 ++++--
pkg/controller/integrationplatform/create.go | 4 +-
pkg/install/builder.go | 4 +-
pkg/install/common.go | 64 ++++++++++++++--------------
pkg/install/operator.go | 32 +++++++-------
pkg/install/secret.go | 4 +-
8 files changed, 68 insertions(+), 63 deletions(-)
[camel-k] 01/04: Fix #1286: overwrite resources when --force option
is passed to kamel install
Posted by nf...@apache.org.
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
commit 5be5ac7c556d5c9187b7c0227dd5e1f1a0a0fb62
Author: Nicola Ferraro <ni...@gmail.com>
AuthorDate: Fri Feb 21 09:35:27 2020 +0100
Fix #1286: overwrite resources when --force option is passed to kamel install
---
pkg/cmd/install.go | 13 ++++--
pkg/controller/integrationplatform/create.go | 4 +-
pkg/install/builder.go | 4 +-
pkg/install/common.go | 62 ++++++++++++++--------------
pkg/install/operator.go | 32 +++++++-------
pkg/install/secret.go | 4 +-
6 files changed, 61 insertions(+), 58 deletions(-)
diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go
index 8b973b8..b0441dc 100644
--- a/pkg/cmd/install.go
+++ b/pkg/cmd/install.go
@@ -60,6 +60,9 @@ func newCmdInstall(rootCmdOptions *RootCmdOptions) (*cobra.Command, *installCmdO
return err
}
if err := options.install(cmd, args); err != nil {
+ if k8serrors.IsAlreadyExists(err) {
+ return errors.Wrap(err, "Camel K seems already installed (use the --force option to overwrite existing resources)")
+ }
return err
}
return nil
@@ -73,6 +76,7 @@ func newCmdInstall(rootCmdOptions *RootCmdOptions) (*cobra.Command, *installCmdO
cmd.Flags().Bool("skip-cluster-setup", false, "Skip the cluster-setup phase")
cmd.Flags().Bool("example", false, "Install example integration")
cmd.Flags().Bool("global", false, "Configure the operator to watch all namespaces. No integration platform is created.")
+ cmd.Flags().Bool("force", false, "Force replacement of configuration resources when already present.")
cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml")
cmd.Flags().String("organization", "", "A organization on the Docker registry that can be used to publish images")
@@ -137,6 +141,7 @@ type installCmdOptions struct {
Global bool `mapstructure:"global"`
KanikoBuildCache bool `mapstructure:"kaniko-build-cache"`
Save bool `mapstructure:"save"`
+ Force bool `mapstructure:"force"`
Olm bool `mapstructure:"olm"`
ClusterType string `mapstructure:"cluster-type"`
OutputFormat string `mapstructure:"output"`
@@ -238,7 +243,7 @@ func (o *installCmdOptions) install(cobraCmd *cobra.Command, _ []string) error {
Global: o.Global,
ClusterType: o.ClusterType,
}
- err = install.OperatorOrCollect(o.Context, c, cfg, collection)
+ err = install.OperatorOrCollect(o.Context, c, cfg, collection, o.Force)
if err != nil {
return err
}
@@ -250,7 +255,7 @@ func (o *installCmdOptions) install(cobraCmd *cobra.Command, _ []string) error {
if o.registryAuth.IsSet() {
regData := o.registryAuth
regData.Registry = o.registry.Address
- generatedSecretName, err = install.RegistrySecretOrCollect(o.Context, c, namespace, regData, collection)
+ generatedSecretName, err = install.RegistrySecretOrCollect(o.Context, c, namespace, regData, collection, o.Force)
if err != nil {
return err
}
@@ -346,14 +351,14 @@ func (o *installCmdOptions) install(cobraCmd *cobra.Command, _ []string) error {
// to be created in other namespaces.
// In OLM mode, the operator is installed in an external namespace, so it's ok to install the platform locally.
if !o.Global || installViaOLM {
- err = install.RuntimeObjectOrCollect(o.Context, c, namespace, collection, platform)
+ err = install.RuntimeObjectOrCollect(o.Context, c, namespace, collection, o.Force, platform)
if err != nil {
return err
}
}
if o.ExampleSetup {
- err = install.ExampleOrCollect(o.Context, c, namespace, collection)
+ err = install.ExampleOrCollect(o.Context, c, namespace, collection, o.Force)
if err != nil {
return err
}
diff --git a/pkg/controller/integrationplatform/create.go b/pkg/controller/integrationplatform/create.go
index 7d387a9..e8efeba 100644
--- a/pkg/controller/integrationplatform/create.go
+++ b/pkg/controller/integrationplatform/create.go
@@ -49,7 +49,7 @@ func (action *createAction) Handle(ctx context.Context, platform *v1.Integration
for _, k := range deploy.Resources("/") {
if strings.HasPrefix(k, "camel-catalog-") {
action.L.Infof("Installing camel catalog: %s", k)
- err := install.Resources(ctx, action.client, platform.Namespace, install.IdentityResourceCustomizer, k)
+ err := install.Resources(ctx, action.client, platform.Namespace, true, install.IdentityResourceCustomizer, k)
if err != nil {
return nil, err
}
@@ -73,7 +73,7 @@ func (action *createAction) Handle(ctx context.Context, platform *v1.Integration
if len(res) > 0 {
action.L.Info("Installing custom platform resources")
- err := install.Resources(ctx, action.client, platform.Namespace, install.IdentityResourceCustomizer, res...)
+ err := install.Resources(ctx, action.client, platform.Namespace, true, install.IdentityResourceCustomizer, res...)
if err != nil {
return nil, err
}
diff --git a/pkg/install/builder.go b/pkg/install/builder.go
index cd906c0..d9f0d17 100644
--- a/pkg/install/builder.go
+++ b/pkg/install/builder.go
@@ -43,7 +43,7 @@ func BuilderServiceAccountRoles(ctx context.Context, c client.Client, namespace
}
func installBuilderServiceAccountRolesOpenshift(ctx context.Context, c client.Client, namespace string) error {
- return ResourcesOrCollect(ctx, c, namespace, nil, IdentityResourceCustomizer,
+ return ResourcesOrCollect(ctx, c, namespace, nil, true, IdentityResourceCustomizer,
"builder-service-account.yaml",
"builder-role-openshift.yaml",
"builder-role-binding.yaml",
@@ -51,7 +51,7 @@ func installBuilderServiceAccountRolesOpenshift(ctx context.Context, c client.Cl
}
func installBuilderServiceAccountRolesKubernetes(ctx context.Context, c client.Client, namespace string) error {
- return ResourcesOrCollect(ctx, c, namespace, nil, IdentityResourceCustomizer,
+ return ResourcesOrCollect(ctx, c, namespace, nil, true, IdentityResourceCustomizer,
"builder-service-account.yaml",
"builder-role-kubernetes.yaml",
"builder-role-binding.yaml",
diff --git a/pkg/install/common.go b/pkg/install/common.go
index 79414c2..e1328a1 100644
--- a/pkg/install/common.go
+++ b/pkg/install/common.go
@@ -42,14 +42,14 @@ var IdentityResourceCustomizer = func(object runtime.Object) runtime.Object {
}
// Resources installs named resources from the project resource directory
-func Resources(ctx context.Context, c client.Client, namespace string, customizer ResourceCustomizer, names ...string) error {
- return ResourcesOrCollect(ctx, c, namespace, nil, customizer, names...)
+func Resources(ctx context.Context, c client.Client, namespace string, force bool, customizer ResourceCustomizer, names ...string) error {
+ return ResourcesOrCollect(ctx, c, namespace, nil, force, customizer, names...)
}
// ResourcesOrCollect --
-func ResourcesOrCollect(ctx context.Context, c client.Client, namespace string, collection *kubernetes.Collection, customizer ResourceCustomizer, names ...string) error {
+func ResourcesOrCollect(ctx context.Context, c client.Client, namespace string, collection *kubernetes.Collection, force bool, customizer ResourceCustomizer, names ...string) error {
for _, name := range names {
- if err := ResourceOrCollect(ctx, c, namespace, collection, customizer, name); err != nil {
+ if err := ResourceOrCollect(ctx, c, namespace, collection, force, customizer, name); err != nil {
return err
}
}
@@ -57,27 +57,27 @@ func ResourcesOrCollect(ctx context.Context, c client.Client, namespace string,
}
// Resource installs a single named resource from the project resource directory
-func Resource(ctx context.Context, c client.Client, namespace string, customizer ResourceCustomizer, name string) error {
- return ResourceOrCollect(ctx, c, namespace, nil, customizer, name)
+func Resource(ctx context.Context, c client.Client, namespace string, force bool, customizer ResourceCustomizer, name string) error {
+ return ResourceOrCollect(ctx, c, namespace, nil, force, customizer, name)
}
// ResourceOrCollect --
-func ResourceOrCollect(ctx context.Context, c client.Client, namespace string, collection *kubernetes.Collection, customizer ResourceCustomizer, name string) error {
+func ResourceOrCollect(ctx context.Context, c client.Client, namespace string, collection *kubernetes.Collection, force bool, customizer ResourceCustomizer, name string) error {
obj, err := kubernetes.LoadResourceFromYaml(c.GetScheme(), deploy.ResourceAsString(name))
if err != nil {
return err
}
- return RuntimeObjectOrCollect(ctx, c, namespace, collection, customizer(obj))
+ return RuntimeObjectOrCollect(ctx, c, namespace, collection, force, customizer(obj))
}
// RuntimeObject installs a single runtime object
-func RuntimeObject(ctx context.Context, c client.Client, namespace string, obj runtime.Object) error {
- return RuntimeObjectOrCollect(ctx, c, namespace, nil, obj)
+func RuntimeObject(ctx context.Context, c client.Client, namespace string, force bool, obj runtime.Object) error {
+ return RuntimeObjectOrCollect(ctx, c, namespace, nil, force, obj)
}
// RuntimeObjectOrCollect --
-func RuntimeObjectOrCollect(ctx context.Context, c client.Client, namespace string, collection *kubernetes.Collection, obj runtime.Object) error {
+func RuntimeObjectOrCollect(ctx context.Context, c client.Client, namespace string, collection *kubernetes.Collection, force bool, obj runtime.Object) error {
if collection != nil {
// Adding to the collection before setting the namespace
collection.Add(obj)
@@ -88,31 +88,29 @@ func RuntimeObjectOrCollect(ctx context.Context, c client.Client, namespace stri
metaObject.SetNamespace(namespace)
}
- err := c.Create(ctx, obj)
- if err != nil && errors.IsAlreadyExists(err) {
- // Don't recreate Service object
- if obj.GetObjectKind().GroupVersionKind().Kind == "Service" {
- return nil
- }
- // Don't recreate integration kits, platforms, etc
- if obj.GetObjectKind().GroupVersionKind().Kind == v1.IntegrationKitKind {
- return nil
- }
- if obj.GetObjectKind().GroupVersionKind().Kind == v1.IntegrationPlatformKind {
- return nil
- }
- if obj.GetObjectKind().GroupVersionKind().Kind == v1.CamelCatalogKind {
- return nil
+ if obj.GetObjectKind().GroupVersionKind().Kind == "PersistentVolumeClaim" {
+ if err := c.Create(ctx, obj); err != nil && !errors.IsAlreadyExists(err) {
+ return err
}
- if obj.GetObjectKind().GroupVersionKind().Kind == v1.BuildKind {
- return nil
+ }
+
+ if force {
+ if err := kubernetes.ReplaceResource(ctx, c, obj); err != nil {
+ return err
}
- if obj.GetObjectKind().GroupVersionKind().Kind == "PersistentVolumeClaim" {
- return nil
+ // For some resources, also reset the status
+ if obj.GetObjectKind().GroupVersionKind().Kind == v1.IntegrationKitKind ||
+ obj.GetObjectKind().GroupVersionKind().Kind == v1.BuildKind ||
+ obj.GetObjectKind().GroupVersionKind().Kind == v1.IntegrationPlatformKind {
+ if err := c.Status().Update(ctx, obj); err != nil {
+ return err
+ }
}
- return c.Update(ctx, obj)
+ return nil
}
- return err
+
+ // Just try to create them
+ return c.Create(ctx, obj)
}
func isOpenShift(c kube.Interface, clusterType string) (bool, error) {
diff --git a/pkg/install/operator.go b/pkg/install/operator.go
index 02a01ca..e61f13f 100644
--- a/pkg/install/operator.go
+++ b/pkg/install/operator.go
@@ -45,12 +45,12 @@ type OperatorConfiguration struct {
}
// Operator installs the operator resources in the given namespace
-func Operator(ctx context.Context, c client.Client, cfg OperatorConfiguration) error {
- return OperatorOrCollect(ctx, c, cfg, nil)
+func Operator(ctx context.Context, c client.Client, cfg OperatorConfiguration, force bool) error {
+ return OperatorOrCollect(ctx, c, cfg, nil, force)
}
// OperatorOrCollect installs the operator resources or adds them to the collector if present
-func OperatorOrCollect(ctx context.Context, c client.Client, cfg OperatorConfiguration, collection *kubernetes.Collection) error {
+func OperatorOrCollect(ctx context.Context, c client.Client, cfg OperatorConfiguration, collection *kubernetes.Collection, force bool) error {
customizer := func(o runtime.Object) runtime.Object {
if cfg.CustomImage != "" {
if d, ok := o.(*appsv1.Deployment); ok {
@@ -108,11 +108,11 @@ func OperatorOrCollect(ctx context.Context, c client.Client, cfg OperatorConfigu
return err
}
if isOpenshift {
- if err := installOpenshift(ctx, c, cfg.Namespace, customizer, collection); err != nil {
+ if err := installOpenshift(ctx, c, cfg.Namespace, customizer, collection, force); err != nil {
return err
}
} else {
- if err := installKubernetes(ctx, c, cfg.Namespace, customizer, collection); err != nil {
+ if err := installKubernetes(ctx, c, cfg.Namespace, customizer, collection, force); err != nil {
return err
}
}
@@ -122,13 +122,13 @@ func OperatorOrCollect(ctx context.Context, c client.Client, cfg OperatorConfigu
return err
}
if isKnative {
- return installKnative(ctx, c, cfg.Namespace, customizer, collection)
+ return installKnative(ctx, c, cfg.Namespace, customizer, collection, force)
}
return nil
}
-func installOpenshift(ctx context.Context, c client.Client, namespace string, customizer ResourceCustomizer, collection *kubernetes.Collection) error {
- return ResourcesOrCollect(ctx, c, namespace, collection, customizer,
+func installOpenshift(ctx context.Context, c client.Client, namespace string, customizer ResourceCustomizer, collection *kubernetes.Collection, force bool) error {
+ return ResourcesOrCollect(ctx, c, namespace, collection, force, customizer,
"operator-service-account.yaml",
"operator-role-openshift.yaml",
"operator-role-binding.yaml",
@@ -136,8 +136,8 @@ func installOpenshift(ctx context.Context, c client.Client, namespace string, cu
)
}
-func installKubernetes(ctx context.Context, c client.Client, namespace string, customizer ResourceCustomizer, collection *kubernetes.Collection) error {
- return ResourcesOrCollect(ctx, c, namespace, collection, customizer,
+func installKubernetes(ctx context.Context, c client.Client, namespace string, customizer ResourceCustomizer, collection *kubernetes.Collection, force bool) error {
+ return ResourcesOrCollect(ctx, c, namespace, collection, force, customizer,
"operator-service-account.yaml",
"operator-role-kubernetes.yaml",
"operator-role-binding.yaml",
@@ -145,8 +145,8 @@ func installKubernetes(ctx context.Context, c client.Client, namespace string, c
)
}
-func installKnative(ctx context.Context, c client.Client, namespace string, customizer ResourceCustomizer, collection *kubernetes.Collection) error {
- return ResourcesOrCollect(ctx, c, namespace, collection, customizer,
+func installKnative(ctx context.Context, c client.Client, namespace string, customizer ResourceCustomizer, collection *kubernetes.Collection, force bool) error {
+ return ResourcesOrCollect(ctx, c, namespace, collection, force, customizer,
"operator-role-knative.yaml",
"operator-role-binding-knative.yaml",
)
@@ -195,13 +195,13 @@ func PlatformOrCollect(ctx context.Context, c client.Client, clusterType string,
}
// Example --
-func Example(ctx context.Context, c client.Client, namespace string) error {
- return ExampleOrCollect(ctx, c, namespace, nil)
+func Example(ctx context.Context, c client.Client, namespace string, force bool) error {
+ return ExampleOrCollect(ctx, c, namespace, nil, force)
}
// ExampleOrCollect --
-func ExampleOrCollect(ctx context.Context, c client.Client, namespace string, collection *kubernetes.Collection) error {
- return ResourcesOrCollect(ctx, c, namespace, collection, IdentityResourceCustomizer,
+func ExampleOrCollect(ctx context.Context, c client.Client, namespace string, collection *kubernetes.Collection, force bool) error {
+ return ResourcesOrCollect(ctx, c, namespace, collection, force, IdentityResourceCustomizer,
"cr-example.yaml",
)
}
diff --git a/pkg/install/secret.go b/pkg/install/secret.go
index 026b520..70a7536 100644
--- a/pkg/install/secret.go
+++ b/pkg/install/secret.go
@@ -30,7 +30,7 @@ import (
const registrySecretName = "camel-k-registry-secret"
// RegistrySecretOrCollect generates a secret from auth settings and creates it on the cluster (or appends it to the collection)
-func RegistrySecretOrCollect(ctx context.Context, c client.Client, namespace string, auth registry.Auth, collection *kubernetes.Collection) (string, error) {
+func RegistrySecretOrCollect(ctx context.Context, c client.Client, namespace string, auth registry.Auth, collection *kubernetes.Collection, force bool) (string, error) {
secretData, err := auth.GenerateDockerConfig()
if err != nil {
return "", err
@@ -51,7 +51,7 @@ func RegistrySecretOrCollect(ctx context.Context, c client.Client, namespace str
},
}
- if err := RuntimeObjectOrCollect(ctx, c, namespace, collection, ®istrySecret); err != nil {
+ if err := RuntimeObjectOrCollect(ctx, c, namespace, collection, force, ®istrySecret); err != nil {
return "", err
}
return registrySecretName, nil
[camel-k] 04/04: Fix #1286: fix tekton test
Posted by nf...@apache.org.
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
commit abacac8fbe30daccd6cc37cfee78e6aca39111d6
Author: Nicola Ferraro <ni...@gmail.com>
AuthorDate: Sat Feb 22 10:03:11 2020 +0100
Fix #1286: fix tekton test
---
e2e/tekton_test.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/e2e/tekton_test.go b/e2e/tekton_test.go
index 02b2412..ae3863f 100644
--- a/e2e/tekton_test.go
+++ b/e2e/tekton_test.go
@@ -36,7 +36,7 @@ func TestTektonLikeBehavior(t *testing.T) {
Expect(createOperatorRoleBinding(ns)).Should(BeNil())
Eventually(operatorPod(ns)).Should(BeNil())
- Expect(createKamelPod(ns, "tekton-task", "install", "--skip-cluster-setup")).Should(BeNil())
+ Expect(createKamelPod(ns, "tekton-task", "install", "--skip-cluster-setup", "--force")).Should(BeNil())
Eventually(operatorPod(ns)).ShouldNot(BeNil())
})
[camel-k] 02/04: Fix #1286: fix tests
Posted by nf...@apache.org.
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
commit 6c15e5deb29576eea1b4eb17b9d9c5f28b145e6c
Author: Nicola Ferraro <ni...@gmail.com>
AuthorDate: Fri Feb 21 10:22:55 2020 +0100
Fix #1286: fix tests
---
e2e/test_support.go | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/e2e/test_support.go b/e2e/test_support.go
index b033eeb..f47891c 100644
--- a/e2e/test_support.go
+++ b/e2e/test_support.go
@@ -681,7 +681,7 @@ func serviceaccount(ns, name string) func() *corev1.ServiceAccount {
*/
func createOperatorServiceAccount(ns string) error {
- return install.Resource(testContext, testClient, ns, install.IdentityResourceCustomizer, "operator-service-account.yaml")
+ return install.Resource(testContext, testClient, ns, true, install.IdentityResourceCustomizer, "operator-service-account.yaml")
}
func createOperatorRole(ns string) (err error) {
@@ -690,13 +690,13 @@ func createOperatorRole(ns string) (err error) {
panic(err)
}
if oc {
- return install.Resource(testContext, testClient, ns, install.IdentityResourceCustomizer, "operator-role-openshift.yaml")
+ return install.Resource(testContext, testClient, ns, true, install.IdentityResourceCustomizer, "operator-role-openshift.yaml")
}
- return install.Resource(testContext, testClient, ns, install.IdentityResourceCustomizer, "operator-role-kubernetes.yaml")
+ return install.Resource(testContext, testClient, ns, true, install.IdentityResourceCustomizer, "operator-role-kubernetes.yaml")
}
func createOperatorRoleBinding(ns string) error {
- return install.Resource(testContext, testClient, ns, install.IdentityResourceCustomizer, "operator-role-binding.yaml")
+ return install.Resource(testContext, testClient, ns, true, install.IdentityResourceCustomizer, "operator-role-binding.yaml")
}
func createKamelPod(ns string, name string, command ...string) error {
[camel-k] 03/04: Fix #1286: fix linter
Posted by nf...@apache.org.
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
commit 794bd2d619797115ad9656f6cb0499911a236f1f
Author: Nicola Ferraro <ni...@gmail.com>
AuthorDate: Fri Feb 21 10:48:01 2020 +0100
Fix #1286: fix linter
---
pkg/install/common.go | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/pkg/install/common.go b/pkg/install/common.go
index e1328a1..c5170a0 100644
--- a/pkg/install/common.go
+++ b/pkg/install/common.go
@@ -47,7 +47,8 @@ func Resources(ctx context.Context, c client.Client, namespace string, force boo
}
// ResourcesOrCollect --
-func ResourcesOrCollect(ctx context.Context, c client.Client, namespace string, collection *kubernetes.Collection, force bool, customizer ResourceCustomizer, names ...string) error {
+func ResourcesOrCollect(ctx context.Context, c client.Client, namespace string, collection *kubernetes.Collection,
+ force bool, customizer ResourceCustomizer, names ...string) error {
for _, name := range names {
if err := ResourceOrCollect(ctx, c, namespace, collection, force, customizer, name); err != nil {
return err
@@ -62,7 +63,8 @@ func Resource(ctx context.Context, c client.Client, namespace string, force bool
}
// ResourceOrCollect --
-func ResourceOrCollect(ctx context.Context, c client.Client, namespace string, collection *kubernetes.Collection, force bool, customizer ResourceCustomizer, name string) error {
+func ResourceOrCollect(ctx context.Context, c client.Client, namespace string, collection *kubernetes.Collection,
+ force bool, customizer ResourceCustomizer, name string) error {
obj, err := kubernetes.LoadResourceFromYaml(c.GetScheme(), deploy.ResourceAsString(name))
if err != nil {
return err