mirror of https://github.com/knative/client.git
* add kn export (#653) * add kn export (#653) * review comments for #669 * add kn export command * add kn export command * add kn export command * add kn export command * add changelog for pr 679 * add changelog * review comments for pr #669 * review comments for pr #669 * review comments for kn export
This commit is contained in:
parent
ba7e14c807
commit
5c794d3b04
|
|
@ -81,6 +81,10 @@
|
||||||
| 🎁
|
| 🎁
|
||||||
| Add `--user` flag for specifying the user id to run the container
|
| Add `--user` flag for specifying the user id to run the container
|
||||||
| https://github.com/knative/client/pull/679[#679]
|
| https://github.com/knative/client/pull/679[#679]
|
||||||
|
|
||||||
|
| 🎁
|
||||||
|
| add kn service export command for exporting a service
|
||||||
|
| https://github.com/knative/client/pull/669[#669]
|
||||||
|===
|
|===
|
||||||
|
|
||||||
## v0.12.0 (2020-01-29)
|
## v0.12.0 (2020-01-29)
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ kn service [flags]
|
||||||
* [kn service create](kn_service_create.md) - Create a service.
|
* [kn service create](kn_service_create.md) - Create a service.
|
||||||
* [kn service delete](kn_service_delete.md) - Delete a service.
|
* [kn service delete](kn_service_delete.md) - Delete a service.
|
||||||
* [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
|
||||||
* [kn service list](kn_service_list.md) - List available services.
|
* [kn service list](kn_service_list.md) - List available 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 export
|
||||||
|
|
||||||
|
export a service
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
export a service
|
||||||
|
|
||||||
|
```
|
||||||
|
kn service export NAME [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
# Export a service in yaml format
|
||||||
|
kn service export foo -n bar -o yaml
|
||||||
|
# Export a service in json format
|
||||||
|
kn service export foo -n bar -o json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
--allow-missing-template-keys If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. (default true)
|
||||||
|
-h, --help help for export
|
||||||
|
-r, --history Export all active revisions
|
||||||
|
-n, --namespace string Specify the namespace to operate in.
|
||||||
|
-o, --output string Output format. One of: json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-file.
|
||||||
|
--template string Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options inherited from parent commands
|
||||||
|
|
||||||
|
```
|
||||||
|
--config string kn config file (default is ~/.config/kn/config.yaml)
|
||||||
|
--kubeconfig string kubectl config file (default is ~/.kube/config)
|
||||||
|
--log-http log http traffic
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [kn service](kn_service.md) - Service command group
|
||||||
|
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
// 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"
|
||||||
|
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
|
|
||||||
|
"knative.dev/client/pkg/kn/commands"
|
||||||
|
clientservingv1 "knative.dev/client/pkg/serving/v1"
|
||||||
|
"knative.dev/serving/pkg/apis/serving"
|
||||||
|
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewServiceExportCommand returns a new command for exporting a service.
|
||||||
|
func NewServiceExportCommand(p *commands.KnParams) *cobra.Command {
|
||||||
|
|
||||||
|
// For machine readable output
|
||||||
|
machineReadablePrintFlags := genericclioptions.NewPrintFlags("")
|
||||||
|
|
||||||
|
command := &cobra.Command{
|
||||||
|
Use: "export NAME",
|
||||||
|
Short: "export a service",
|
||||||
|
Example: `
|
||||||
|
# Export a service in yaml format
|
||||||
|
kn service export foo -n bar -o yaml
|
||||||
|
# Export a service in json format
|
||||||
|
kn service export foo -n bar -o json`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return errors.New("'kn service export' requires name of the service as single argument")
|
||||||
|
}
|
||||||
|
if !machineReadablePrintFlags.OutputFlagSpecified() {
|
||||||
|
return errors.New("'kn service export' requires output format")
|
||||||
|
}
|
||||||
|
serviceName := args[0]
|
||||||
|
|
||||||
|
namespace, err := p.GetNamespace(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := p.NewServingClient(namespace)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
service, err := client.GetService(serviceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
history, err := cmd.Flags().GetBool("history")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
printer, err := machineReadablePrintFlags.ToPrinter()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if history {
|
||||||
|
if svcList, err := exportServicewithActiveRevisions(service, client); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
return printer.PrintObj(svcList, cmd.OutOrStdout())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return printer.PrintObj(exportService(service), cmd.OutOrStdout())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
flags := command.Flags()
|
||||||
|
commands.AddNamespaceFlags(flags, false)
|
||||||
|
flags.BoolP("history", "r", false, "Export all active revisions")
|
||||||
|
machineReadablePrintFlags.AddFlags(command)
|
||||||
|
return command
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportService(latestSvc *servingv1.Service) *servingv1.Service {
|
||||||
|
|
||||||
|
exportedSvc := servingv1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: latestSvc.ObjectMeta.Name,
|
||||||
|
Labels: latestSvc.ObjectMeta.Labels,
|
||||||
|
},
|
||||||
|
TypeMeta: latestSvc.TypeMeta,
|
||||||
|
}
|
||||||
|
|
||||||
|
exportedSvc.Spec.Template = servingv1.RevisionTemplateSpec{
|
||||||
|
Spec: latestSvc.Spec.ConfigurationSpec.Template.Spec,
|
||||||
|
ObjectMeta: latestSvc.Spec.ConfigurationSpec.Template.ObjectMeta,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &exportedSvc
|
||||||
|
}
|
||||||
|
|
||||||
|
func constructServicefromRevision(latestSvc *servingv1.Service, revision servingv1.Revision) servingv1.Service {
|
||||||
|
|
||||||
|
exportedSvc := servingv1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: latestSvc.ObjectMeta.Name,
|
||||||
|
Labels: latestSvc.ObjectMeta.Labels,
|
||||||
|
},
|
||||||
|
TypeMeta: latestSvc.TypeMeta,
|
||||||
|
}
|
||||||
|
|
||||||
|
exportedSvc.Spec.Template = servingv1.RevisionTemplateSpec{
|
||||||
|
Spec: revision.Spec,
|
||||||
|
ObjectMeta: latestSvc.Spec.ConfigurationSpec.Template.ObjectMeta,
|
||||||
|
}
|
||||||
|
|
||||||
|
exportedSvc.Spec.ConfigurationSpec.Template.ObjectMeta.Name = revision.ObjectMeta.Name
|
||||||
|
|
||||||
|
return exportedSvc
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportServicewithActiveRevisions(latestSvc *servingv1.Service, client clientservingv1.KnServingClient) (*servingv1.ServiceList, error) {
|
||||||
|
var exportedSvcItems []servingv1.Service
|
||||||
|
|
||||||
|
//get revisions to export from traffic
|
||||||
|
revsMap := getRevisionstoExport(latestSvc)
|
||||||
|
|
||||||
|
var params []clientservingv1.ListConfig
|
||||||
|
params = append(params, clientservingv1.WithService(latestSvc.ObjectMeta.Name))
|
||||||
|
|
||||||
|
// Query for list with filters
|
||||||
|
revisionList, err := client.ListRevisions(params...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(revisionList.Items) == 0 {
|
||||||
|
return nil, fmt.Errorf("No revisions found for the service %s", latestSvc.ObjectMeta.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort revisions to main the order of generations
|
||||||
|
sortRevisions(revisionList)
|
||||||
|
|
||||||
|
for _, revision := range revisionList.Items {
|
||||||
|
//construct service only for active revisions
|
||||||
|
if revsMap[revision.ObjectMeta.Name] {
|
||||||
|
exportedSvcItems = append(exportedSvcItems, constructServicefromRevision(latestSvc, revision))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//set traffic in the latest revision
|
||||||
|
exportedSvcItems[len(exportedSvcItems)-1] = setTrafficSplit(latestSvc, exportedSvcItems[len(exportedSvcItems)-1])
|
||||||
|
|
||||||
|
typeMeta := metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "List",
|
||||||
|
}
|
||||||
|
exportedSvcList := &servingv1.ServiceList{
|
||||||
|
TypeMeta: typeMeta,
|
||||||
|
Items: exportedSvcItems,
|
||||||
|
}
|
||||||
|
|
||||||
|
return exportedSvcList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTrafficSplit(latestSvc *servingv1.Service, exportedSvc servingv1.Service) servingv1.Service {
|
||||||
|
|
||||||
|
exportedSvc.Spec.RouteSpec = latestSvc.Spec.RouteSpec
|
||||||
|
|
||||||
|
return exportedSvc
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRevisionstoExport(latestSvc *servingv1.Service) map[string]bool {
|
||||||
|
trafficList := latestSvc.Spec.RouteSpec.Traffic
|
||||||
|
revsMap := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, traffic := range trafficList {
|
||||||
|
if traffic.RevisionName == "" {
|
||||||
|
revsMap[latestSvc.Spec.ConfigurationSpec.Template.ObjectMeta.Name] = true
|
||||||
|
} else {
|
||||||
|
revsMap[traffic.RevisionName] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return revsMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// sortRevisions sorts revisions by generation and name (in this order)
|
||||||
|
func sortRevisions(revisionList *servingv1.RevisionList) {
|
||||||
|
// sort revisionList by configuration generation key
|
||||||
|
sort.SliceStable(revisionList.Items, revisionListSortFunc(revisionList))
|
||||||
|
}
|
||||||
|
|
||||||
|
// revisionListSortFunc sorts by generation and name
|
||||||
|
func revisionListSortFunc(revisionList *servingv1.RevisionList) func(i int, j int) bool {
|
||||||
|
return func(i, j int) bool {
|
||||||
|
a := revisionList.Items[i]
|
||||||
|
b := revisionList.Items[j]
|
||||||
|
|
||||||
|
// By Generation
|
||||||
|
// Convert configuration generation key from string to int for avoiding string comparison.
|
||||||
|
agen, err := strconv.Atoi(a.Labels[serving.ConfigurationGenerationLabelKey])
|
||||||
|
if err != nil {
|
||||||
|
return a.Name > b.Name
|
||||||
|
}
|
||||||
|
bgen, err := strconv.Atoi(b.Labels[serving.ConfigurationGenerationLabelKey])
|
||||||
|
if err != nil {
|
||||||
|
return a.Name > b.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if agen != bgen {
|
||||||
|
return agen < bgen
|
||||||
|
}
|
||||||
|
return a.Name > b.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,322 @@
|
||||||
|
// 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/assert"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
servinglib "knative.dev/client/pkg/serving"
|
||||||
|
knclient "knative.dev/client/pkg/serving/v1"
|
||||||
|
"knative.dev/client/pkg/util/mock"
|
||||||
|
"knative.dev/pkg/ptr"
|
||||||
|
apiserving "knative.dev/serving/pkg/apis/serving"
|
||||||
|
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServiceExport(t *testing.T) {
|
||||||
|
var svcs []*servingv1.Service
|
||||||
|
typeMeta := metav1.TypeMeta{
|
||||||
|
Kind: "service",
|
||||||
|
APIVersion: "serving.knative.dev/v1",
|
||||||
|
}
|
||||||
|
|
||||||
|
// case 1 - plain svc
|
||||||
|
plainService := getService("foo")
|
||||||
|
svcs = append(svcs, plainService)
|
||||||
|
|
||||||
|
// case 2 - svc with env variables
|
||||||
|
envSvc := getService("foo")
|
||||||
|
envVars := []v1.EnvVar{
|
||||||
|
{Name: "a", Value: "mouse"},
|
||||||
|
{Name: "b", Value: "cookie"},
|
||||||
|
{Name: "empty", Value: ""},
|
||||||
|
}
|
||||||
|
template := &envSvc.Spec.Template
|
||||||
|
template.Spec.Containers[0].Env = envVars
|
||||||
|
template.Spec.Containers[0].Image = "gcr.io/foo/bar:baz"
|
||||||
|
template.Annotations = map[string]string{servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz"}
|
||||||
|
svcs = append(svcs, envSvc)
|
||||||
|
|
||||||
|
//case 3 - svc with labels
|
||||||
|
labelService := getService("foo")
|
||||||
|
expected := map[string]string{
|
||||||
|
"a": "mouse",
|
||||||
|
"b": "cookie",
|
||||||
|
"empty": "",
|
||||||
|
}
|
||||||
|
labelService.Labels = expected
|
||||||
|
labelService.Spec.Template.Annotations = map[string]string{
|
||||||
|
servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz",
|
||||||
|
}
|
||||||
|
template = &labelService.Spec.Template
|
||||||
|
template.ObjectMeta.Labels = expected
|
||||||
|
template.Spec.Containers[0].Image = "gcr.io/foo/bar:baz"
|
||||||
|
svcs = append(svcs, labelService)
|
||||||
|
|
||||||
|
//case 4 - config map
|
||||||
|
CMservice := getService("foo")
|
||||||
|
template = &CMservice.Spec.Template
|
||||||
|
template.Spec.Containers[0].EnvFrom = []v1.EnvFromSource{
|
||||||
|
{
|
||||||
|
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||||
|
LocalObjectReference: v1.LocalObjectReference{
|
||||||
|
Name: "config-map-name",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
template.Spec.Containers[0].Image = "gcr.io/foo/bar:baz"
|
||||||
|
template.Annotations = map[string]string{servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz"}
|
||||||
|
svcs = append(svcs, CMservice)
|
||||||
|
|
||||||
|
//case 5 - volume mount and secrets
|
||||||
|
Volservice := getService("foo")
|
||||||
|
template = &Volservice.Spec.Template
|
||||||
|
template.Spec.Volumes = []v1.Volume{
|
||||||
|
{
|
||||||
|
Name: "volume-name",
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
Secret: &v1.SecretVolumeSource{
|
||||||
|
SecretName: "secret-name",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
template.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: "volume-name",
|
||||||
|
MountPath: "/mount/path",
|
||||||
|
ReadOnly: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
svcs = append(svcs, Volservice)
|
||||||
|
|
||||||
|
template.Spec.Containers[0].Image = "gcr.io/foo/bar:baz"
|
||||||
|
template.Annotations = map[string]string{servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz"}
|
||||||
|
|
||||||
|
for _, svc := range svcs {
|
||||||
|
svc.TypeMeta = typeMeta
|
||||||
|
callServiceExportTest(t, svc)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func callServiceExportTest(t *testing.T, expectedService *servingv1.Service) {
|
||||||
|
// New mock client
|
||||||
|
client := knclient.NewMockKnServiceClient(t)
|
||||||
|
|
||||||
|
// Recording:
|
||||||
|
r := client.Recorder()
|
||||||
|
|
||||||
|
r.GetService(expectedService.ObjectMeta.Name, expectedService, nil)
|
||||||
|
|
||||||
|
output, err := executeServiceCommand(client, "export", expectedService.ObjectMeta.Name, "-o", "yaml")
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
expectedService.ObjectMeta.Namespace = ""
|
||||||
|
|
||||||
|
expSvcYaml, err := yaml.Marshal(expectedService)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, string(expSvcYaml), output)
|
||||||
|
|
||||||
|
// Validate that all recorded API methods have been called
|
||||||
|
r.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceExportwithMultipleRevisions(t *testing.T) {
|
||||||
|
//case 1 = 2 revisions with traffic split
|
||||||
|
trafficSplitService := createServiceTwoRevsionsWithTraffic("foo", true)
|
||||||
|
|
||||||
|
multiRevs := createTestRevisionList("rev", "foo")
|
||||||
|
|
||||||
|
callServiceExportHistoryTest(t, trafficSplitService, multiRevs)
|
||||||
|
|
||||||
|
//case 2 - same revisions no traffic split
|
||||||
|
noTrafficSplitService := createServiceTwoRevsionsWithTraffic("foo", false)
|
||||||
|
|
||||||
|
callServiceExportHistoryTest(t, noTrafficSplitService, multiRevs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func callServiceExportHistoryTest(t *testing.T, expectedService *servingv1.Service, revs *servingv1.RevisionList) {
|
||||||
|
// New mock client
|
||||||
|
client := knclient.NewMockKnServiceClient(t)
|
||||||
|
|
||||||
|
// Recording:
|
||||||
|
r := client.Recorder()
|
||||||
|
|
||||||
|
r.GetService(expectedService.ObjectMeta.Name, expectedService, nil)
|
||||||
|
|
||||||
|
r.ListRevisions(mock.Any(), revs, nil)
|
||||||
|
|
||||||
|
output, err := executeServiceCommand(client, "export", expectedService.ObjectMeta.Name, "-r", "-o", "json")
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
actSvcList := servingv1.ServiceList{}
|
||||||
|
|
||||||
|
json.Unmarshal([]byte(output), &actSvcList)
|
||||||
|
|
||||||
|
for i, actSvc := range actSvcList.Items {
|
||||||
|
var checkTraffic bool
|
||||||
|
if i == (len(actSvcList.Items) - 1) {
|
||||||
|
checkTraffic = true
|
||||||
|
}
|
||||||
|
validateServiceWithRevisionHistory(t, expectedService, revs, actSvc, checkTraffic)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that all recorded API methods have been called
|
||||||
|
r.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateServiceWithRevisionHistory(t *testing.T, expectedsvc *servingv1.Service, expectedRevList *servingv1.RevisionList, actualSvc servingv1.Service, checkTraffic bool) {
|
||||||
|
var expectedRev servingv1.Revision
|
||||||
|
var routeSpec servingv1.RouteSpec
|
||||||
|
for _, rev := range expectedRevList.Items {
|
||||||
|
if actualSvc.Spec.ConfigurationSpec.Template.ObjectMeta.Name == rev.ObjectMeta.Name {
|
||||||
|
expectedRev = rev
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expectedsvc.Spec.ConfigurationSpec.Template.ObjectMeta.Name = expectedRev.ObjectMeta.Name
|
||||||
|
expectedsvc.Spec.Template.Spec = expectedRev.Spec
|
||||||
|
|
||||||
|
stripExpectedSvcVariables(expectedsvc)
|
||||||
|
|
||||||
|
if !checkTraffic {
|
||||||
|
routeSpec = expectedsvc.Spec.RouteSpec
|
||||||
|
expectedsvc.Spec.RouteSpec = servingv1.RouteSpec{}
|
||||||
|
}
|
||||||
|
assert.DeepEqual(t, expectedsvc, &actualSvc)
|
||||||
|
|
||||||
|
expectedsvc.Spec.RouteSpec = routeSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceExportError(t *testing.T) {
|
||||||
|
// New mock client
|
||||||
|
client := knclient.NewMockKnServiceClient(t)
|
||||||
|
|
||||||
|
expectedService := getService("foo")
|
||||||
|
|
||||||
|
_, err := executeServiceCommand(client, "export", expectedService.ObjectMeta.Name)
|
||||||
|
|
||||||
|
assert.Error(t, err, "'kn service export' requires output format")
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestRevisionList(revision string, service string) *servingv1.RevisionList {
|
||||||
|
labels1 := make(map[string]string)
|
||||||
|
labels1[apiserving.ConfigurationGenerationLabelKey] = "1"
|
||||||
|
labels1[apiserving.ServiceLabelKey] = service
|
||||||
|
|
||||||
|
labels2 := make(map[string]string)
|
||||||
|
labels2[apiserving.ConfigurationGenerationLabelKey] = "2"
|
||||||
|
labels2[apiserving.ServiceLabelKey] = service
|
||||||
|
|
||||||
|
rev1 := servingv1.Revision{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "Revision",
|
||||||
|
APIVersion: "serving.knative.dev/v1",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: fmt.Sprintf("%s-%s-%d", service, revision, 1),
|
||||||
|
Namespace: "default",
|
||||||
|
Generation: int64(1),
|
||||||
|
Labels: labels1,
|
||||||
|
},
|
||||||
|
Spec: servingv1.RevisionSpec{
|
||||||
|
PodSpec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Image: "gcr.io/test/image:v1",
|
||||||
|
Env: []v1.EnvVar{
|
||||||
|
{Name: "env1", Value: "eval1"},
|
||||||
|
{Name: "env2", Value: "eval2"},
|
||||||
|
},
|
||||||
|
EnvFrom: []v1.EnvFromSource{
|
||||||
|
{ConfigMapRef: &v1.ConfigMapEnvSource{LocalObjectReference: v1.LocalObjectReference{Name: "test1"}}},
|
||||||
|
{ConfigMapRef: &v1.ConfigMapEnvSource{LocalObjectReference: v1.LocalObjectReference{Name: "test2"}}},
|
||||||
|
},
|
||||||
|
Ports: []v1.ContainerPort{
|
||||||
|
{ContainerPort: 8080},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rev2 := rev1
|
||||||
|
|
||||||
|
rev2.Spec.PodSpec.Containers[0].Image = "gcr.io/test/image:v2"
|
||||||
|
rev2.ObjectMeta.Labels = labels2
|
||||||
|
rev2.ObjectMeta.Generation = int64(2)
|
||||||
|
rev2.ObjectMeta.Name = fmt.Sprintf("%s-%s-%d", service, revision, 2)
|
||||||
|
|
||||||
|
typeMeta := metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "List",
|
||||||
|
}
|
||||||
|
|
||||||
|
return &servingv1.RevisionList{
|
||||||
|
TypeMeta: typeMeta,
|
||||||
|
Items: []servingv1.Revision{rev1, rev2},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createServiceTwoRevsionsWithTraffic(svc string, trafficSplit bool) *servingv1.Service {
|
||||||
|
expectedService := createTestService(svc, []string{svc + "-rev-1", svc + "-rev-2"}, goodConditions())
|
||||||
|
expectedService.Status.Traffic[0].LatestRevision = ptr.Bool(true)
|
||||||
|
expectedService.Status.Traffic[0].Tag = "latest"
|
||||||
|
expectedService.Status.Traffic[1].Tag = "current"
|
||||||
|
|
||||||
|
if trafficSplit {
|
||||||
|
trafficList := []servingv1.TrafficTarget{
|
||||||
|
{
|
||||||
|
RevisionName: "foo-rev-1",
|
||||||
|
Percent: ptr.Int64(int64(50)),
|
||||||
|
}, {
|
||||||
|
RevisionName: "foo-rev-2",
|
||||||
|
Percent: ptr.Int64(int64(50)),
|
||||||
|
}}
|
||||||
|
expectedService.Spec.RouteSpec = servingv1.RouteSpec{Traffic: trafficList}
|
||||||
|
} else {
|
||||||
|
trafficList := []servingv1.TrafficTarget{
|
||||||
|
{
|
||||||
|
RevisionName: "foo-rev-2",
|
||||||
|
Percent: ptr.Int64(int64(50)),
|
||||||
|
}}
|
||||||
|
expectedService.Spec.RouteSpec = servingv1.RouteSpec{Traffic: trafficList}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &expectedService
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripExpectedSvcVariables(expectedsvc *servingv1.Service) {
|
||||||
|
expectedsvc.ObjectMeta.Namespace = ""
|
||||||
|
expectedsvc.Spec.Template.Spec.Containers[0].Resources = v1.ResourceRequirements{}
|
||||||
|
expectedsvc.Status = servingv1.ServiceStatus{}
|
||||||
|
expectedsvc.ObjectMeta.Annotations = nil
|
||||||
|
expectedsvc.ObjectMeta.CreationTimestamp = metav1.Time{}
|
||||||
|
}
|
||||||
|
|
@ -41,6 +41,7 @@ func NewServiceCommand(p *commands.KnParams) *cobra.Command {
|
||||||
serviceCmd.AddCommand(NewServiceCreateCommand(p))
|
serviceCmd.AddCommand(NewServiceCreateCommand(p))
|
||||||
serviceCmd.AddCommand(NewServiceDeleteCommand(p))
|
serviceCmd.AddCommand(NewServiceDeleteCommand(p))
|
||||||
serviceCmd.AddCommand(NewServiceUpdateCommand(p))
|
serviceCmd.AddCommand(NewServiceUpdateCommand(p))
|
||||||
|
serviceCmd.AddCommand(NewServiceExportCommand(p))
|
||||||
return serviceCmd
|
return serviceCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue