Skip to content

Commit

Permalink
feat: Add service import command (#1065)
Browse files Browse the repository at this point in the history
* feat: Add service import command

* fix: Fix e2e test

* fix: Add retry when retrieving Configuration

* fix: Reflect review feedback

* fix: Fix error message

Co-authored-by: Roland Huß <[email protected]>

* fix: Add missing mock tests

* fix: Polish unit test assertions

* fix: Mark import as experimental

* chore: Add changelog entry

* Update CHANGELOG.adoc

Co-authored-by: Roland Huß <[email protected]>

* fix: Remove deprecated flag

Co-authored-by: Roland Huß <[email protected]>
  • Loading branch information
dsimansk and rhuss authored Nov 9, 2020
1 parent 45ffade commit b72e4be
Show file tree
Hide file tree
Showing 10 changed files with 618 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
| 🎁
| Add WithLabel list filter to serving client lib
| https://github.com/knative/client/pull/1054[#1054]

| 🎁
| Add `kn service import` command (experimental)
| https://github.com/knative/client/pull/1065[#1065]
|===

## v0.18.1 (2020-10-13)
Expand Down
1 change: 1 addition & 0 deletions docs/cmd/kn_service.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ kn service
* [kn service delete](kn_service_delete.md) - Delete services
* [kn service describe](kn_service_describe.md) - Show details of a service
* [kn service export](kn_service_export.md) - Export a service and its revisions
* [kn service import](kn_service_import.md) - Import a service and its revisions (experimental)
* [kn service list](kn_service_list.md) - List services
* [kn service update](kn_service_update.md) - Update a service

45 changes: 45 additions & 0 deletions docs/cmd/kn_service_import.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
## kn service import

Import a service and its revisions (experimental)

### Synopsis

Import a service and its revisions (experimental)

```
kn service import FILENAME
```

### Examples

```
# Import a service from YAML file
kn service import /path/to/file.yaml
# Import a service from JSON file
kn service import /path/to/file.json
```

### Options

```
-h, --help help for import
-n, --namespace string Specify the namespace to operate in.
--no-wait Do not wait for 'service import' operation to be completed.
--wait Wait for 'service import' operation to be completed. (default true)
--wait-timeout int Seconds to wait before giving up on waiting for service to be ready. (default 600)
```

### Options inherited from parent commands

```
--config string kn configuration file (default: ~/.config/kn/config.yaml)
--kubeconfig string kubectl configuration file (default: ~/.kube/config)
--log-http log http traffic
```

### SEE ALSO

* [kn service](kn_service.md) - Manage Knative services

143 changes: 143 additions & 0 deletions pkg/kn/commands/service/import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright © 2020 The Knative Authors
//
// Licensed 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 service

import (
"errors"
"fmt"
"io"
"os"

"github.com/spf13/cobra"

apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/util/retry"

clientv1alpha1 "knative.dev/client/pkg/apis/client/v1alpha1"
"knative.dev/client/pkg/kn/commands"
clientservingv1 "knative.dev/client/pkg/serving/v1"
"knative.dev/pkg/kmeta"
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
)

// NewServiceImportCommand returns a new command for importing a service.
func NewServiceImportCommand(p *commands.KnParams) *cobra.Command {
var waitFlags commands.WaitFlags

command := &cobra.Command{
Use: "import FILENAME",
Short: "Import a service and its revisions (experimental)",
Example: `
# Import a service from YAML file
kn service import /path/to/file.yaml
# Import a service from JSON file
kn service import /path/to/file.json`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("'kn service import' requires filename of import file as single argument")
}
filename := args[0]

namespace, err := p.GetNamespace(cmd)
if err != nil {
return err
}

client, err := p.NewServingClient(namespace)
if err != nil {
return err
}

return importWithOwnerRef(client, filename, cmd.OutOrStdout(), waitFlags)
},
}
flags := command.Flags()
commands.AddNamespaceFlags(flags, false)
waitFlags.AddConditionWaitFlags(command, commands.WaitDefaultTimeout, "import", "service", "ready")

return command
}

func importWithOwnerRef(client clientservingv1.KnServingClient, filename string, out io.Writer, waitFlags commands.WaitFlags) error {
var export clientv1alpha1.Export
file, err := os.Open(filename)
if err != nil {
return err
}
decoder := yaml.NewYAMLOrJSONDecoder(file, 512)
err = decoder.Decode(&export)
if err != nil {
return err
}
if export.Spec.Service.Name == "" {
return fmt.Errorf("provided import file doesn't contain service name, please note that only kn's custom export format is supported")
}

serviceName := export.Spec.Service.Name

// Return error if service already exists
svcExists, err := serviceExists(client, serviceName)
if err != nil {
return err
}
if svcExists {
return fmt.Errorf("cannot import service '%s' in namespace '%s' because the service already exists",
serviceName, client.Namespace())
}

err = client.CreateService(&export.Spec.Service)
if err != nil {
return err
}

// Retrieve current Configuration to be use in OwnerReference
currentConf, err := getConfigurationWithRetry(client, serviceName)
if err != nil {
return err
}

// Create revision with current Configuration's OwnerReference
if len(export.Spec.Revisions) > 0 {
for _, r := range export.Spec.Revisions {
tmp := r.DeepCopy()
// OwnerRef ensures that Revisions are recognized by controller
tmp.OwnerReferences = []metav1.OwnerReference{*kmeta.NewControllerRef(currentConf)}
if err = client.CreateRevision(tmp); err != nil {
return err
}
}
}

err = waitIfRequested(client, serviceName, waitFlags, "Importing", "imported", out)
if err != nil {
return err
}
return err
}

func getConfigurationWithRetry(client clientservingv1.KnServingClient, name string) (*servingv1.Configuration, error) {
var conf *servingv1.Configuration
var err error
err = retry.OnError(retry.DefaultBackoff, func(err error) bool {
return apierrors.IsNotFound(err)
}, func() error {
conf, err = client.GetConfiguration(name)
return err
})
return conf, err
}
Loading

0 comments on commit b72e4be

Please sign in to comment.