mirror of https://github.com/knative/client.git
feat: Add service import command (#1065)
* 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ß <rhuss@redhat.com> * 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ß <rhuss@redhat.com> * fix: Remove deprecated flag Co-authored-by: Roland Huß <rhuss@redhat.com>
This commit is contained in:
parent
45ffadecec
commit
b72e4be300
|
|
@ -45,6 +45,10 @@
|
||||||
| 🎁
|
| 🎁
|
||||||
| Add WithLabel list filter to serving client lib
|
| Add WithLabel list filter to serving client lib
|
||||||
| https://github.com/knative/client/pull/1054[#1054]
|
| 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)
|
## v0.18.1 (2020-10-13)
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ kn service
|
||||||
* [kn service delete](kn_service_delete.md) - Delete services
|
* [kn service delete](kn_service_delete.md) - Delete services
|
||||||
* [kn service describe](kn_service_describe.md) - Show details of a service
|
* [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 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 list](kn_service_list.md) - List services
|
||||||
* [kn service update](kn_service_update.md) - Update a service
|
* [kn service update](kn_service_update.md) - Update a service
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,280 @@
|
||||||
|
// 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 (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gotest.tools/assert"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
knclient "knative.dev/client/pkg/serving/v1"
|
||||||
|
"knative.dev/client/pkg/util"
|
||||||
|
"knative.dev/client/pkg/util/mock"
|
||||||
|
"knative.dev/client/pkg/wait"
|
||||||
|
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServiceImportFilenameError(t *testing.T) {
|
||||||
|
client := knclient.NewMockKnServiceClient(t)
|
||||||
|
r := client.Recorder()
|
||||||
|
|
||||||
|
_, err := executeServiceCommand(client, "import")
|
||||||
|
|
||||||
|
assert.Assert(t, err != nil)
|
||||||
|
assert.Assert(t, util.ContainsAll(err.Error(), "'kn service import'", "requires", "file", "single", "argument"))
|
||||||
|
assert.Error(t, err, "'kn service import' requires filename of import file as single argument")
|
||||||
|
r.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceImportExistError(t *testing.T) {
|
||||||
|
file, err := generateFile([]byte(exportYAML))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
defer os.RemoveAll(filepath.Dir(file))
|
||||||
|
|
||||||
|
client := knclient.NewMockKnServiceClient(t)
|
||||||
|
r := client.Recorder()
|
||||||
|
|
||||||
|
r.GetService("foo", nil, nil)
|
||||||
|
_, err = executeServiceCommand(client, "import", file)
|
||||||
|
|
||||||
|
assert.Assert(t, err != nil)
|
||||||
|
assert.Assert(t, util.ContainsAll(err.Error(), "'foo'", "default", "service", "already", "exists"))
|
||||||
|
r.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceImport(t *testing.T) {
|
||||||
|
file, err := generateFile([]byte(exportYAML))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
defer os.RemoveAll(filepath.Dir(file))
|
||||||
|
|
||||||
|
client := knclient.NewMockKnServiceClient(t)
|
||||||
|
r := client.Recorder()
|
||||||
|
|
||||||
|
r.GetService("foo", nil, errors.NewNotFound(servingv1.Resource("service"), "foo"))
|
||||||
|
r.CreateService(mock.Any(), nil)
|
||||||
|
r.GetConfiguration("foo", nil, nil)
|
||||||
|
r.WaitForService("foo", mock.Any(), wait.NoopMessageCallback(), nil, time.Second)
|
||||||
|
r.GetService("foo", getServiceWithUrl("foo", "http://foo.example.com"), nil)
|
||||||
|
|
||||||
|
out, err := executeServiceCommand(client, "import", file)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Assert(t, util.ContainsAll(out, "Service", "'foo'", "default", "imported"))
|
||||||
|
r.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceImportNoWait(t *testing.T) {
|
||||||
|
file, err := generateFile([]byte(exportYAML))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
defer os.RemoveAll(filepath.Dir(file))
|
||||||
|
|
||||||
|
client := knclient.NewMockKnServiceClient(t)
|
||||||
|
r := client.Recorder()
|
||||||
|
|
||||||
|
r.GetService("foo", nil, errors.NewNotFound(servingv1.Resource("service"), "foo"))
|
||||||
|
r.CreateService(mock.Any(), nil)
|
||||||
|
r.GetConfiguration("foo", nil, nil)
|
||||||
|
|
||||||
|
out, err := executeServiceCommand(client, "import", file, "--no-wait")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Assert(t, util.ContainsAll(out, "Service", "imported", "foo"))
|
||||||
|
r.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceImportWitRevisions(t *testing.T) {
|
||||||
|
file, err := generateFile([]byte(exportWithRevisionsYAML))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
defer os.RemoveAll(filepath.Dir(file))
|
||||||
|
|
||||||
|
client := knclient.NewMockKnServiceClient(t)
|
||||||
|
r := client.Recorder()
|
||||||
|
|
||||||
|
r.GetService("foo", nil, errors.NewNotFound(servingv1.Resource("service"), "foo"))
|
||||||
|
r.CreateService(mock.Any(), nil)
|
||||||
|
r.GetConfiguration("foo", getConfiguration("foo"), nil)
|
||||||
|
// 2 previous Revisions to re-create + 1 latest from CreateService
|
||||||
|
r.CreateRevision(mock.Any(), nil)
|
||||||
|
r.CreateRevision(mock.Any(), nil)
|
||||||
|
r.WaitForService("foo", mock.Any(), wait.NoopMessageCallback(), nil, time.Second)
|
||||||
|
r.GetService("foo", getServiceWithUrl("foo", "http://foo.example.com"), nil)
|
||||||
|
|
||||||
|
out, err := executeServiceCommand(client, "import", file)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Assert(t, util.ContainsAll(out, "Service", "imported", "foo"))
|
||||||
|
r.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateFile(fileContent []byte) (string, error) {
|
||||||
|
tempDir, err := ioutil.TempDir("", "kn-file")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
tempFile := filepath.Join(tempDir, "import.yaml")
|
||||||
|
if err = ioutil.WriteFile(tempFile, fileContent, os.FileMode(0666)); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return tempFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConfiguration(name string) *servingv1.Configuration {
|
||||||
|
return &servingv1.Configuration{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var exportYAML = `
|
||||||
|
apiVersion: client.knative.dev/v1alpha1
|
||||||
|
kind: Export
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
spec:
|
||||||
|
revisions: null
|
||||||
|
service:
|
||||||
|
apiVersion: serving.knative.dev/v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
spec:
|
||||||
|
containerConcurrency: 0
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- name: TARGET
|
||||||
|
value: v1
|
||||||
|
image: gcr.io/foo/bar:baz
|
||||||
|
name: user-container
|
||||||
|
readinessProbe:
|
||||||
|
successThreshold: 1
|
||||||
|
tcpSocket:
|
||||||
|
port: 0
|
||||||
|
resources: {}
|
||||||
|
enableServiceLinks: false
|
||||||
|
timeoutSeconds: 300
|
||||||
|
status: {}
|
||||||
|
`
|
||||||
|
|
||||||
|
var exportWithRevisionsYAML = `
|
||||||
|
apiVersion: client.knative.dev/v1alpha1
|
||||||
|
kind: Export
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
spec:
|
||||||
|
revisions:
|
||||||
|
- apiVersion: serving.knative.dev/v1
|
||||||
|
kind: Revision
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
client.knative.dev/user-image: gcr.io/foo/bar:baz
|
||||||
|
serving.knative.dev/routes: foo
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
serving.knative.dev/configuration: foo
|
||||||
|
serving.knative.dev/configurationGeneration: "1"
|
||||||
|
serving.knative.dev/routingState: active
|
||||||
|
serving.knative.dev/service: foo
|
||||||
|
name: foo-rev-1
|
||||||
|
spec:
|
||||||
|
containerConcurrency: 0
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- name: TARGET
|
||||||
|
value: v1
|
||||||
|
image: gcr.io/foo/bar:baz
|
||||||
|
name: user-container
|
||||||
|
readinessProbe:
|
||||||
|
successThreshold: 1
|
||||||
|
tcpSocket:
|
||||||
|
port: 0
|
||||||
|
resources: {}
|
||||||
|
enableServiceLinks: false
|
||||||
|
timeoutSeconds: 300
|
||||||
|
status: {}
|
||||||
|
- apiVersion: serving.knative.dev/v1
|
||||||
|
kind: Revision
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
client.knative.dev/user-image: gcr.io/foo/bar:baz
|
||||||
|
serving.knative.dev/routes: foo
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
serving.knative.dev/configuration: foo
|
||||||
|
serving.knative.dev/configurationGeneration: "2"
|
||||||
|
serving.knative.dev/routingState: active
|
||||||
|
serving.knative.dev/service: foo
|
||||||
|
name: foo-rev-2
|
||||||
|
spec:
|
||||||
|
containerConcurrency: 0
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- name: TARGET
|
||||||
|
value: v2
|
||||||
|
image: gcr.io/foo/bar:baz
|
||||||
|
name: user-container
|
||||||
|
readinessProbe:
|
||||||
|
successThreshold: 1
|
||||||
|
tcpSocket:
|
||||||
|
port: 0
|
||||||
|
resources: {}
|
||||||
|
enableServiceLinks: false
|
||||||
|
timeoutSeconds: 300
|
||||||
|
status: {}
|
||||||
|
service:
|
||||||
|
apiVersion: serving.knative.dev/v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: foo
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
client.knative.dev/user-image: gcr.io/foo/bar:baz
|
||||||
|
name: foo-rev-3
|
||||||
|
spec:
|
||||||
|
containerConcurrency: 0
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- name: TARGET
|
||||||
|
value: v3
|
||||||
|
image: gcr.io/foo/bar:baz
|
||||||
|
name: user-container
|
||||||
|
readinessProbe:
|
||||||
|
successThreshold: 1
|
||||||
|
tcpSocket:
|
||||||
|
port: 0
|
||||||
|
resources: {}
|
||||||
|
enableServiceLinks: false
|
||||||
|
timeoutSeconds: 300
|
||||||
|
traffic:
|
||||||
|
- latestRevision: false
|
||||||
|
percent: 25
|
||||||
|
revisionName: foo-rev-1
|
||||||
|
- latestRevision: false
|
||||||
|
percent: 25
|
||||||
|
revisionName: foo-rev-2
|
||||||
|
- latestRevision: false
|
||||||
|
percent: 50
|
||||||
|
revisionName: foo-rev-3
|
||||||
|
status: {}
|
||||||
|
`
|
||||||
|
|
@ -44,6 +44,7 @@ func NewServiceCommand(p *commands.KnParams) *cobra.Command {
|
||||||
serviceCmd.AddCommand(NewServiceUpdateCommand(p))
|
serviceCmd.AddCommand(NewServiceUpdateCommand(p))
|
||||||
serviceCmd.AddCommand(NewServiceApplyCommand(p))
|
serviceCmd.AddCommand(NewServiceApplyCommand(p))
|
||||||
serviceCmd.AddCommand(NewServiceExportCommand(p))
|
serviceCmd.AddCommand(NewServiceExportCommand(p))
|
||||||
|
serviceCmd.AddCommand(NewServiceImportCommand(p))
|
||||||
return serviceCmd
|
return serviceCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,12 @@ type KnServingClient interface {
|
||||||
// current template.
|
// current template.
|
||||||
GetBaseRevision(service *servingv1.Service) (*servingv1.Revision, error)
|
GetBaseRevision(service *servingv1.Service) (*servingv1.Revision, error)
|
||||||
|
|
||||||
|
// Create revision
|
||||||
|
CreateRevision(revision *servingv1.Revision) error
|
||||||
|
|
||||||
|
// Update revision
|
||||||
|
UpdateRevision(revision *servingv1.Revision) error
|
||||||
|
|
||||||
// List revisions
|
// List revisions
|
||||||
ListRevisions(opts ...ListConfig) (*servingv1.RevisionList, error)
|
ListRevisions(opts ...ListConfig) (*servingv1.RevisionList, error)
|
||||||
|
|
||||||
|
|
@ -423,6 +429,24 @@ func getBaseRevision(cl KnServingClient, service *servingv1.Service) (*servingv1
|
||||||
return nil, noBaseRevisionError
|
return nil, noBaseRevisionError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a revision
|
||||||
|
func (cl *knServingClient) CreateRevision(revision *servingv1.Revision) error {
|
||||||
|
rev, err := cl.client.Revisions(cl.namespace).Create(context.TODO(), revision, v1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return clienterrors.GetError(err)
|
||||||
|
}
|
||||||
|
return updateServingGvk(rev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the given service
|
||||||
|
func (cl *knServingClient) UpdateRevision(revision *servingv1.Revision) error {
|
||||||
|
_, err := cl.client.Revisions(cl.namespace).Update(context.TODO(), revision, v1.UpdateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return updateServingGvk(revision)
|
||||||
|
}
|
||||||
|
|
||||||
// Delete a revision by name
|
// Delete a revision by name
|
||||||
func (cl *knServingClient) DeleteRevision(name string, timeout time.Duration) error {
|
func (cl *knServingClient) DeleteRevision(name string, timeout time.Duration) error {
|
||||||
revision, err := cl.client.Revisions(cl.namespace).Get(context.TODO(), name, v1.GetOptions{})
|
revision, err := cl.client.Revisions(cl.namespace).Get(context.TODO(), name, v1.GetOptions{})
|
||||||
|
|
|
||||||
|
|
@ -202,6 +202,28 @@ func (c *MockKnServingClient) GetConfiguration(name string) (*servingv1.Configur
|
||||||
return call.Result[0].(*servingv1.Configuration), mock.ErrorOrNil(call.Result[1])
|
return call.Result[0].(*servingv1.Configuration), mock.ErrorOrNil(call.Result[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateRevision records a call CreateRevision
|
||||||
|
func (sr *ServingRecorder) CreateRevision(revision interface{}, err error) {
|
||||||
|
sr.r.Add("CreateRevision", []interface{}{revision}, []interface{}{err})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRevision creates a new revision
|
||||||
|
func (c *MockKnServingClient) CreateRevision(revision *servingv1.Revision) error {
|
||||||
|
call := c.recorder.r.VerifyCall("CreateRevision", revision)
|
||||||
|
return mock.ErrorOrNil(call.Result[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRevision records a call UpdateRevision
|
||||||
|
func (sr *ServingRecorder) UpdateRevision(revision interface{}, err error) {
|
||||||
|
sr.r.Add("UpdateRevision", []interface{}{revision}, []interface{}{err})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRevision updates given revision
|
||||||
|
func (c *MockKnServingClient) UpdateRevision(revision *servingv1.Revision) error {
|
||||||
|
call := c.recorder.r.VerifyCall("UpdateRevision", revision)
|
||||||
|
return mock.ErrorOrNil(call.Result[0])
|
||||||
|
}
|
||||||
|
|
||||||
// Check that every recorded method has been called
|
// Check that every recorded method has been called
|
||||||
func (sr *ServingRecorder) Validate() {
|
func (sr *ServingRecorder) Validate() {
|
||||||
sr.r.CheckThatAllRecordedMethodsHaveBeenCalled()
|
sr.r.CheckThatAllRecordedMethodsHaveBeenCalled()
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,8 @@ func TestMockKnClient(t *testing.T) {
|
||||||
recorder.WaitForService("hello", time.Duration(10)*time.Second, wait.NoopMessageCallback(), nil, 10*time.Second)
|
recorder.WaitForService("hello", time.Duration(10)*time.Second, wait.NoopMessageCallback(), nil, 10*time.Second)
|
||||||
recorder.GetRevision("hello", nil, nil)
|
recorder.GetRevision("hello", nil, nil)
|
||||||
recorder.ListRevisions(mock.Any(), nil, nil)
|
recorder.ListRevisions(mock.Any(), nil, nil)
|
||||||
|
recorder.CreateRevision(&servingv1.Revision{}, nil)
|
||||||
|
recorder.UpdateRevision(&servingv1.Revision{}, nil)
|
||||||
recorder.DeleteRevision("hello", time.Duration(10)*time.Second, nil)
|
recorder.DeleteRevision("hello", time.Duration(10)*time.Second, nil)
|
||||||
recorder.GetRoute("hello", nil, nil)
|
recorder.GetRoute("hello", nil, nil)
|
||||||
recorder.ListRoutes(mock.Any(), nil, nil)
|
recorder.ListRoutes(mock.Any(), nil, nil)
|
||||||
|
|
@ -58,6 +60,8 @@ func TestMockKnClient(t *testing.T) {
|
||||||
client.WaitForService("hello", time.Duration(10)*time.Second, wait.NoopMessageCallback())
|
client.WaitForService("hello", time.Duration(10)*time.Second, wait.NoopMessageCallback())
|
||||||
client.GetRevision("hello")
|
client.GetRevision("hello")
|
||||||
client.ListRevisions(WithName("blub"))
|
client.ListRevisions(WithName("blub"))
|
||||||
|
client.CreateRevision(&servingv1.Revision{})
|
||||||
|
client.UpdateRevision(&servingv1.Revision{})
|
||||||
client.DeleteRevision("hello", time.Duration(10)*time.Second)
|
client.DeleteRevision("hello", time.Duration(10)*time.Second)
|
||||||
client.GetRoute("hello")
|
client.GetRoute("hello")
|
||||||
client.ListRoutes(WithName("blub"))
|
client.ListRoutes(WithName("blub"))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
// Copyright 2019 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 im
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build e2e
|
||||||
|
// +build !eventing
|
||||||
|
|
||||||
|
package e2e
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/assert"
|
||||||
|
"knative.dev/client/lib/test"
|
||||||
|
"knative.dev/client/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServiceImport(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
it, err := test.NewKnTest()
|
||||||
|
assert.NilError(t, err)
|
||||||
|
defer func() {
|
||||||
|
assert.NilError(t, it.Teardown())
|
||||||
|
}()
|
||||||
|
|
||||||
|
r := test.NewKnRunResultCollector(t, it)
|
||||||
|
defer r.DumpIfFailed()
|
||||||
|
|
||||||
|
tempDir, err := ioutil.TempDir("", "kn-file")
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
t.Log("import service foo with revision")
|
||||||
|
testFile := filepath.Join(tempDir, "foo-with-revisions")
|
||||||
|
|
||||||
|
serviceCreateWithOptions(r, "foo", "--revision-name", "foo-rev-1")
|
||||||
|
test.ServiceUpdate(r, "foo", "--env", "TARGET=v2", "--revision-name", "foo-rev-2")
|
||||||
|
test.ServiceUpdate(r, "foo", "--traffic", "foo-rev-1=50,foo-rev-2=50")
|
||||||
|
serviceExportToFile(r, "foo", testFile, true)
|
||||||
|
test.ServiceDelete(r, "foo")
|
||||||
|
serviceImport(r, testFile)
|
||||||
|
|
||||||
|
t.Log("import existing service foo error")
|
||||||
|
serviceImportExistsError(r, testFile)
|
||||||
|
|
||||||
|
t.Log("import service from missing file error")
|
||||||
|
serviceImportFileError(r, testFile+"-missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceExportToFile(r *test.KnRunResultCollector, serviceName, filename string, withRevisions bool) {
|
||||||
|
command := []string{"service", "export", serviceName, "-o", "yaml", "--mode", "export"}
|
||||||
|
if withRevisions {
|
||||||
|
command = append(command, "--with-revisions")
|
||||||
|
}
|
||||||
|
out := r.KnTest().Kn().Run(command...)
|
||||||
|
r.AssertNoError(out)
|
||||||
|
err := ioutil.WriteFile(filename, []byte(out.Stdout), test.FileModeReadWrite)
|
||||||
|
assert.NilError(r.T(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceImport(r *test.KnRunResultCollector, filename string) {
|
||||||
|
command := []string{"service", "import", filename}
|
||||||
|
|
||||||
|
out := r.KnTest().Kn().Run(command...)
|
||||||
|
r.AssertNoError(out)
|
||||||
|
assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "service", "importing", "namespace", r.KnTest().Kn().Namespace(), "ready"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceImportExistsError(r *test.KnRunResultCollector, filename string) {
|
||||||
|
command := []string{"service", "import", filename}
|
||||||
|
|
||||||
|
out := r.KnTest().Kn().Run(command...)
|
||||||
|
r.AssertError(out)
|
||||||
|
assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stderr, "service", "already", "exists"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceImportFileError(r *test.KnRunResultCollector, filePath string) {
|
||||||
|
out := r.KnTest().Kn().Run("service", "import", filePath)
|
||||||
|
r.AssertError(out)
|
||||||
|
assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stderr, "no", "such", "file", "directory", filePath))
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue