mirror of https://github.com/knative/client.git
refactor kn service export to export revisions (#819)
* refactor kn service export to export revisions * refactor kn service export to export revisions * refactor kn service export to export revisions * refactor kn service export to export revisions * kn service export refactor * kn service export refactor * kn service export refactor * kn service export refactor * review changes for #819
This commit is contained in:
parent
c41e9fd923
commit
644ecb68e5
|
|
@ -14,10 +14,14 @@ kn service export NAME [flags]
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Export a service in yaml format
|
# Export a service in YAML format
|
||||||
kn service export foo -n bar -o yaml
|
kn service export foo -n bar -o yaml
|
||||||
# Export a service in json format
|
# Export a service in JSON format
|
||||||
kn service export foo -n bar -o json
|
kn service export foo -n bar -o json
|
||||||
|
# Export a service with revisions
|
||||||
|
kn service export foo --with-revisions --mode=resources -n bar -o json
|
||||||
|
# Export services in kubectl friendly format, as a list kind, one service item for each revision
|
||||||
|
kn service export foo --with-revisions --mode=kubernetes -n bar -o json
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
@ -25,6 +29,7 @@ kn service export NAME [flags]
|
||||||
```
|
```
|
||||||
--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)
|
--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
|
-h, --help help for export
|
||||||
|
--mode string Format for exporting all routed revisions. One of kubernetes|resources (experimental)
|
||||||
-n, --namespace string Specify the namespace to operate in.
|
-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.
|
-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].
|
--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].
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import (
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
|
"k8s.io/cli-runtime/pkg/printers"
|
||||||
|
|
||||||
"knative.dev/client/pkg/kn/commands"
|
"knative.dev/client/pkg/kn/commands"
|
||||||
clientservingv1 "knative.dev/client/pkg/serving/v1"
|
clientservingv1 "knative.dev/client/pkg/serving/v1"
|
||||||
|
|
@ -32,6 +33,15 @@ import (
|
||||||
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
|
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var IGNORED_SERVICE_ANNOTATIONS = []string{
|
||||||
|
"serving.knative.dev/creator",
|
||||||
|
"serving.knative.dev/lastModifier",
|
||||||
|
"kubectl.kubernetes.io/last-applied-configuration",
|
||||||
|
}
|
||||||
|
var IGNORED_REVISION_ANNOTATIONS = []string{
|
||||||
|
"serving.knative.dev/lastPinned",
|
||||||
|
}
|
||||||
|
|
||||||
// NewServiceExportCommand returns a new command for exporting a service.
|
// NewServiceExportCommand returns a new command for exporting a service.
|
||||||
func NewServiceExportCommand(p *commands.KnParams) *cobra.Command {
|
func NewServiceExportCommand(p *commands.KnParams) *cobra.Command {
|
||||||
|
|
||||||
|
|
@ -42,10 +52,14 @@ func NewServiceExportCommand(p *commands.KnParams) *cobra.Command {
|
||||||
Use: "export NAME",
|
Use: "export NAME",
|
||||||
Short: "Export a service.",
|
Short: "Export a service.",
|
||||||
Example: `
|
Example: `
|
||||||
# Export a service in yaml format
|
# Export a service in YAML format
|
||||||
kn service export foo -n bar -o yaml
|
kn service export foo -n bar -o yaml
|
||||||
# Export a service in json format
|
# Export a service in JSON format
|
||||||
kn service export foo -n bar -o json`,
|
kn service export foo -n bar -o json
|
||||||
|
# Export a service with revisions
|
||||||
|
kn service export foo --with-revisions --mode=resources -n bar -o json
|
||||||
|
# Export services in kubectl friendly format, as a list kind, one service item for each revision
|
||||||
|
kn service export foo --with-revisions --mode=kubernetes -n bar -o json`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return errors.New("'kn service export' requires name of the service as single argument")
|
return errors.New("'kn service export' requires name of the service as single argument")
|
||||||
|
|
@ -69,105 +83,138 @@ func NewServiceExportCommand(p *commands.KnParams) *cobra.Command {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
withRevisions, err := cmd.Flags().GetBool("with-revisions")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
printer, err := machineReadablePrintFlags.ToPrinter()
|
printer, err := machineReadablePrintFlags.ToPrinter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return exportService(cmd, service, client, printer)
|
||||||
if withRevisions {
|
|
||||||
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()
|
flags := command.Flags()
|
||||||
commands.AddNamespaceFlags(flags, false)
|
commands.AddNamespaceFlags(flags, false)
|
||||||
flags.Bool("with-revisions", false, "Export all routed revisions (experimental)")
|
flags.Bool("with-revisions", false, "Export all routed revisions (experimental)")
|
||||||
|
flags.String("mode", "", "Format for exporting all routed revisions. One of kubernetes|resources (experimental)")
|
||||||
machineReadablePrintFlags.AddFlags(command)
|
machineReadablePrintFlags.AddFlags(command)
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportService(latestSvc *servingv1.Service) *servingv1.Service {
|
func exportService(cmd *cobra.Command, service *servingv1.Service, client clientservingv1.KnServingClient, printer printers.ResourcePrinter) error {
|
||||||
|
withRevisions, err := cmd.Flags().GetBool("with-revisions")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !withRevisions {
|
||||||
|
return printer.PrintObj(exportLatestService(service, false), cmd.OutOrStdout())
|
||||||
|
}
|
||||||
|
|
||||||
|
mode, err := cmd.Flags().GetString("mode")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch mode {
|
||||||
|
case "kubernetes":
|
||||||
|
svcList, err := exportServiceListWithActiveRevisions(service, client)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return printer.PrintObj(svcList, cmd.OutOrStdout())
|
||||||
|
case "resources":
|
||||||
|
latestSvc, revList, err := exportActiveRevisionList(service, client)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
//print svc
|
||||||
|
if err := printer.PrintObj(latestSvc, cmd.OutOrStdout()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// print revisionList if revisions exist
|
||||||
|
if len(revList.Items) > 0 {
|
||||||
|
return printer.PrintObj(revList, cmd.OutOrStdout())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("'kn service export --with-revisions' requires a mode, please specify one of kubernetes|resources.")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportLatestService(latestSvc *servingv1.Service, withRoutes bool) *servingv1.Service {
|
||||||
exportedSvc := servingv1.Service{
|
exportedSvc := servingv1.Service{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: latestSvc.ObjectMeta.Name,
|
Name: latestSvc.ObjectMeta.Name,
|
||||||
Labels: latestSvc.ObjectMeta.Labels,
|
Labels: latestSvc.ObjectMeta.Labels,
|
||||||
|
Annotations: latestSvc.ObjectMeta.Annotations,
|
||||||
},
|
},
|
||||||
TypeMeta: latestSvc.TypeMeta,
|
TypeMeta: latestSvc.TypeMeta,
|
||||||
}
|
}
|
||||||
|
|
||||||
exportedSvc.Spec.Template = servingv1.RevisionTemplateSpec{
|
exportedSvc.Spec.Template = servingv1.RevisionTemplateSpec{
|
||||||
Spec: latestSvc.Spec.ConfigurationSpec.Template.Spec,
|
Spec: latestSvc.Spec.Template.Spec,
|
||||||
ObjectMeta: latestSvc.Spec.ConfigurationSpec.Template.ObjectMeta,
|
ObjectMeta: latestSvc.Spec.Template.ObjectMeta,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if withRoutes {
|
||||||
|
exportedSvc.Spec.RouteSpec = latestSvc.Spec.RouteSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
stripIgnoredAnnotationsFromService(&exportedSvc)
|
||||||
|
|
||||||
return &exportedSvc
|
return &exportedSvc
|
||||||
}
|
}
|
||||||
|
|
||||||
func constructServicefromRevision(latestSvc *servingv1.Service, revision servingv1.Revision) servingv1.Service {
|
func exportRevision(revision *servingv1.Revision) servingv1.Revision {
|
||||||
|
exportedRevision := servingv1.Revision{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: revision.ObjectMeta.Name,
|
||||||
|
Labels: revision.ObjectMeta.Labels,
|
||||||
|
Annotations: revision.ObjectMeta.Annotations,
|
||||||
|
},
|
||||||
|
TypeMeta: revision.TypeMeta,
|
||||||
|
}
|
||||||
|
|
||||||
|
exportedRevision.Spec = revision.Spec
|
||||||
|
stripIgnoredAnnotationsFromRevision(&exportedRevision)
|
||||||
|
return exportedRevision
|
||||||
|
}
|
||||||
|
|
||||||
|
func constructServiceFromRevision(latestSvc *servingv1.Service, revision *servingv1.Revision) servingv1.Service {
|
||||||
exportedSvc := servingv1.Service{
|
exportedSvc := servingv1.Service{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: latestSvc.ObjectMeta.Name,
|
Name: latestSvc.ObjectMeta.Name,
|
||||||
Labels: latestSvc.ObjectMeta.Labels,
|
Labels: latestSvc.ObjectMeta.Labels,
|
||||||
|
Annotations: latestSvc.ObjectMeta.Annotations,
|
||||||
},
|
},
|
||||||
TypeMeta: latestSvc.TypeMeta,
|
TypeMeta: latestSvc.TypeMeta,
|
||||||
}
|
}
|
||||||
|
|
||||||
exportedSvc.Spec.ConfigurationSpec.Template = servingv1.RevisionTemplateSpec{
|
exportedSvc.Spec.Template = servingv1.RevisionTemplateSpec{
|
||||||
Spec: revision.Spec,
|
Spec: revision.Spec,
|
||||||
ObjectMeta: latestSvc.Spec.ConfigurationSpec.Template.ObjectMeta,
|
ObjectMeta: latestSvc.Spec.Template.ObjectMeta,
|
||||||
}
|
}
|
||||||
|
|
||||||
exportedSvc.Spec.ConfigurationSpec.Template.ObjectMeta.Name = revision.ObjectMeta.Name
|
exportedSvc.Spec.Template.ObjectMeta.Name = revision.ObjectMeta.Name
|
||||||
|
stripIgnoredAnnotationsFromService(&exportedSvc)
|
||||||
return exportedSvc
|
return exportedSvc
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportServiceWithActiveRevisions(latestSvc *servingv1.Service, client clientservingv1.KnServingClient) (*servingv1.ServiceList, error) {
|
func exportServiceListWithActiveRevisions(latestSvc *servingv1.Service, client clientservingv1.KnServingClient) (*servingv1.ServiceList, error) {
|
||||||
var exportedSvcItems []servingv1.Service
|
revisionList, revsMap, err := getRevisionsToExport(latestSvc, client)
|
||||||
|
|
||||||
//get revisions to export from traffic
|
|
||||||
revsMap := getRevisionstoExport(latestSvc)
|
|
||||||
|
|
||||||
// Query for list with filters
|
|
||||||
revisionList, err := client.ListRevisions(clientservingv1.WithService(latestSvc.ObjectMeta.Name))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
var exportedSvcItems []servingv1.Service
|
||||||
sortRevisions(revisionList)
|
|
||||||
|
|
||||||
for _, revision := range revisionList.Items {
|
for _, revision := range revisionList.Items {
|
||||||
//construct service only for active revisions
|
//construct service only for active revisions
|
||||||
if revsMap[revision.ObjectMeta.Name] {
|
if revsMap[revision.ObjectMeta.Name] && revision.ObjectMeta.Name != latestSvc.Spec.Template.ObjectMeta.Name {
|
||||||
exportedSvcItems = append(exportedSvcItems, constructServicefromRevision(latestSvc, revision))
|
exportedSvcItems = append(exportedSvcItems, constructServiceFromRevision(latestSvc, &revision))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(exportedSvcItems) == 0 {
|
//add latest service, add traffic if more than one revision exist
|
||||||
return nil, fmt.Errorf("no revisions found for service %s", latestSvc.ObjectMeta.Name)
|
exportedSvcItems = append(exportedSvcItems, *(exportLatestService(latestSvc, len(revisionList.Items) > 1)))
|
||||||
}
|
|
||||||
|
|
||||||
//set traffic in the latest revision on if there is traffic split
|
|
||||||
if len(latestSvc.Spec.RouteSpec.Traffic) > 1 {
|
|
||||||
exportedSvcItems[len(exportedSvcItems)-1] = setTrafficSplit(latestSvc, exportedSvcItems[len(exportedSvcItems)-1])
|
|
||||||
}
|
|
||||||
|
|
||||||
typeMeta := metav1.TypeMeta{
|
typeMeta := metav1.TypeMeta{
|
||||||
APIVersion: "v1",
|
APIVersion: "v1",
|
||||||
|
|
@ -181,20 +228,56 @@ func exportServiceWithActiveRevisions(latestSvc *servingv1.Service, client clien
|
||||||
return exportedSvcList, nil
|
return exportedSvcList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setTrafficSplit(latestSvc *servingv1.Service, exportedSvc servingv1.Service) servingv1.Service {
|
func exportActiveRevisionList(latestSvc *servingv1.Service, client clientservingv1.KnServingClient) (*servingv1.Service, *servingv1.RevisionList, error) {
|
||||||
|
revisionList, revsMap, err := getRevisionsToExport(latestSvc, client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
exportedSvc.Spec.RouteSpec = latestSvc.Spec.RouteSpec
|
var exportedRevItems []servingv1.Revision
|
||||||
return exportedSvc
|
|
||||||
|
for _, revision := range revisionList.Items {
|
||||||
|
//append only active revisions, no latest revision
|
||||||
|
if revsMap[revision.ObjectMeta.Name] && revision.ObjectMeta.Name != latestSvc.Spec.Template.ObjectMeta.Name {
|
||||||
|
exportedRevItems = append(exportedRevItems, exportRevision(&revision))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typeMeta := metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "List",
|
||||||
|
}
|
||||||
|
exportedRevList := &servingv1.RevisionList{
|
||||||
|
TypeMeta: typeMeta,
|
||||||
|
Items: exportedRevItems,
|
||||||
|
}
|
||||||
|
|
||||||
|
return exportLatestService(latestSvc, len(revisionList.Items) > 1), exportedRevList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRevisionstoExport(latestSvc *servingv1.Service) map[string]bool {
|
func getRevisionsToExport(latestSvc *servingv1.Service, client clientservingv1.KnServingClient) (*servingv1.RevisionList, map[string]bool, error) {
|
||||||
|
//get revisions to export from traffic
|
||||||
|
revsMap := getRoutedRevisions(latestSvc)
|
||||||
|
|
||||||
|
// Query for list with filters
|
||||||
|
revisionList, err := client.ListRevisions(clientservingv1.WithService(latestSvc.ObjectMeta.Name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if len(revisionList.Items) == 0 {
|
||||||
|
return nil, nil, fmt.Errorf("no revisions found for the service %s", latestSvc.ObjectMeta.Name)
|
||||||
|
}
|
||||||
|
// sort revisions to maintain the order of generations
|
||||||
|
sortRevisions(revisionList)
|
||||||
|
return revisionList, revsMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRoutedRevisions(latestSvc *servingv1.Service) map[string]bool {
|
||||||
trafficList := latestSvc.Spec.RouteSpec.Traffic
|
trafficList := latestSvc.Spec.RouteSpec.Traffic
|
||||||
revsMap := make(map[string]bool)
|
revsMap := make(map[string]bool)
|
||||||
|
|
||||||
for _, traffic := range trafficList {
|
for _, traffic := range trafficList {
|
||||||
if traffic.RevisionName == "" {
|
if traffic.RevisionName != "" {
|
||||||
revsMap[latestSvc.Spec.ConfigurationSpec.Template.ObjectMeta.Name] = true
|
|
||||||
} else {
|
|
||||||
revsMap[traffic.RevisionName] = true
|
revsMap[traffic.RevisionName] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -230,3 +313,15 @@ func revisionListSortFunc(revisionList *servingv1.RevisionList) func(i int, j in
|
||||||
return a.Name > b.Name
|
return a.Name > b.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stripIgnoredAnnotationsFromService(svc *servingv1.Service) {
|
||||||
|
for _, annotation := range IGNORED_SERVICE_ANNOTATIONS {
|
||||||
|
delete(svc.ObjectMeta.Annotations, annotation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripIgnoredAnnotationsFromRevision(revision *servingv1.Revision) {
|
||||||
|
for _, annotation := range IGNORED_REVISION_ANNOTATIONS {
|
||||||
|
delete(revision.ObjectMeta.Annotations, annotation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,13 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"strings"
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gotest.tools/assert"
|
"gotest.tools/assert"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
servinglib "knative.dev/client/pkg/serving"
|
|
||||||
knclient "knative.dev/client/pkg/serving/v1"
|
knclient "knative.dev/client/pkg/serving/v1"
|
||||||
"knative.dev/client/pkg/util/mock"
|
"knative.dev/client/pkg/util/mock"
|
||||||
"knative.dev/pkg/ptr"
|
"knative.dev/pkg/ptr"
|
||||||
|
|
@ -35,162 +33,317 @@ import (
|
||||||
|
|
||||||
type expectedServiceOption func(*servingv1.Service)
|
type expectedServiceOption func(*servingv1.Service)
|
||||||
type expectedRevisionOption func(*servingv1.Revision)
|
type expectedRevisionOption func(*servingv1.Revision)
|
||||||
|
type expectedServiceListOption func(*servingv1.ServiceList)
|
||||||
|
type expectedRevisionListOption func(*servingv1.RevisionList)
|
||||||
|
type podSpecOption func(*v1.PodSpec)
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
latestSvc *servingv1.Service
|
||||||
|
expectedSvcList *servingv1.ServiceList
|
||||||
|
revisionList *servingv1.RevisionList
|
||||||
|
expectedRevisionList *servingv1.RevisionList
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceExportError(t *testing.T) {
|
||||||
|
tc := &testCase{latestSvc: getService("foo")}
|
||||||
|
|
||||||
|
_, err := executeServiceExportCommand(t, tc, "export", tc.latestSvc.ObjectMeta.Name)
|
||||||
|
assert.Error(t, err, "'kn service export' requires output format")
|
||||||
|
|
||||||
|
_, err = executeServiceExportCommand(t, tc, "export", tc.latestSvc.ObjectMeta.Name, "--with-revisions", "-o", "json")
|
||||||
|
assert.Error(t, err, "'kn service export --with-revisions' requires a mode, please specify one of kubernetes|resources.")
|
||||||
|
|
||||||
|
_, err = executeServiceExportCommand(t, tc, "export", tc.latestSvc.ObjectMeta.Name, "--with-revisions", "--mode", "k8s", "-o", "yaml")
|
||||||
|
assert.Error(t, err, "'kn service export --with-revisions' requires a mode, please specify one of kubernetes|resources.")
|
||||||
|
}
|
||||||
|
|
||||||
func TestServiceExport(t *testing.T) {
|
func TestServiceExport(t *testing.T) {
|
||||||
|
|
||||||
svcs := []*servingv1.Service{
|
for _, tc := range []testCase{
|
||||||
getServiceWithOptions(getService("foo"), withContainer()),
|
{latestSvc: getServiceWithOptions(getService("foo"), withServicePodSpecOption(withContainer()))},
|
||||||
getServiceWithOptions(getService("foo"), withContainer(), withEnv([]v1.EnvVar{{Name: "a", Value: "mouse"}})),
|
{latestSvc: getServiceWithOptions(getService("foo"), withServicePodSpecOption(withContainer(), withEnv([]v1.EnvVar{{Name: "a", Value: "mouse"}})))},
|
||||||
getServiceWithOptions(getService("foo"), withContainer(), withLabels(map[string]string{"a": "mouse", "b": "cookie", "empty": ""})),
|
{latestSvc: getServiceWithOptions(getService("foo"), withConfigurationLabels(map[string]string{"a": "mouse"}), withConfigurationAnnotations(map[string]string{"a": "mouse"}), withServicePodSpecOption(withContainer()))},
|
||||||
getServiceWithOptions(getService("foo"), withContainer(), withEnvFrom([]string{"cm-name"})),
|
{latestSvc: getServiceWithOptions(getService("foo"), withLabels(map[string]string{"a": "mouse"}), withAnnotations(map[string]string{"a": "mouse"}), withServicePodSpecOption(withContainer()))},
|
||||||
getServiceWithOptions(getService("foo"), withContainer(), withVolumeandSecrets("volName", "secretName")),
|
{latestSvc: getServiceWithOptions(getService("foo"), withServicePodSpecOption(withContainer(), withVolumeandSecrets("secretName")))},
|
||||||
}
|
} {
|
||||||
|
exportServiceTest(t, &tc)
|
||||||
for _, svc := range svcs {
|
|
||||||
callServiceExportTest(t, svc)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func callServiceExportTest(t *testing.T, expectedService *servingv1.Service) {
|
func exportServiceTest(t *testing.T, tc *testCase) {
|
||||||
// New mock client
|
output, err := executeServiceExportCommand(t, tc, "export", tc.latestSvc.ObjectMeta.Name, "-o", "yaml")
|
||||||
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)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
actSvc := servingv1.Service{}
|
actSvc := servingv1.Service{}
|
||||||
err = yaml.Unmarshal([]byte(output), &actSvc)
|
err = yaml.Unmarshal([]byte(output), &actSvc)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
stripExpectedSvcVariables(expectedService)
|
|
||||||
assert.DeepEqual(t, expectedService, &actSvc)
|
stripUnwantedFields(tc.latestSvc)
|
||||||
// Validate that all recorded API methods have been called
|
assert.DeepEqual(t, tc.latestSvc, &actSvc)
|
||||||
r.Validate()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceExportwithMultipleRevisions(t *testing.T) {
|
func TestServiceExportwithMultipleRevisions(t *testing.T) {
|
||||||
//case 1 - 2 revisions with traffic split
|
for _, tc := range []testCase{{
|
||||||
expSvc1 := getServiceWithOptions(getService("foo"), withContainer(), withServiceRevisionName("foo-rev-1"))
|
name: "test 2 revisions with traffic split",
|
||||||
stripExpectedSvcVariables(expSvc1)
|
latestSvc: getServiceWithOptions(
|
||||||
expSvc2 := getServiceWithOptions(getService("foo"), withContainer(), withTrafficSplit([]string{"foo-rev-1", "foo-rev-2"}, []int{50, 50}, []string{"latest", "current"}), withServiceRevisionName("foo-rev-2"))
|
getService("foo"),
|
||||||
stripExpectedSvcVariables(expSvc2)
|
withAnnotations(map[string]string{"serving.knative.dev/creator": "ut", "serving.knative.dev/lastModifier": "ut"}),
|
||||||
latestSvc := getServiceWithOptions(getService("foo"), withContainer(), withTrafficSplit([]string{"foo-rev-1", "foo-rev-2"}, []int{50, 50}, []string{"latest", "current"}))
|
withTrafficSplit([]string{"foo-rev-1", ""}, []int{50, 50}, []bool{false, true}),
|
||||||
|
withServicePodSpecOption(withContainer()),
|
||||||
expSvcList := servingv1.ServiceList{
|
),
|
||||||
TypeMeta: metav1.TypeMeta{
|
expectedSvcList: getServiceListWithOptions(
|
||||||
APIVersion: "v1",
|
withServices(
|
||||||
Kind: "List",
|
getService("foo"),
|
||||||
},
|
withUnwantedFieldsStripped(),
|
||||||
Items: []servingv1.Service{*expSvc1, *expSvc2},
|
withServicePodSpecOption(withContainer()),
|
||||||
|
withServiceRevisionName("foo-rev-1"),
|
||||||
|
),
|
||||||
|
withServices(
|
||||||
|
getService("foo"),
|
||||||
|
withUnwantedFieldsStripped(),
|
||||||
|
withServicePodSpecOption(withContainer()),
|
||||||
|
withTrafficSplit([]string{"foo-rev-1", ""}, []int{50, 50}, []bool{false, true}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
revisionList: getRevisionListWithOptions(
|
||||||
|
withRevisions(
|
||||||
|
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
||||||
|
withRevisionGeneration("1"),
|
||||||
|
withRevisionAnnotations(map[string]string{"serving.knative.dev/lastPinned": "1111132"}),
|
||||||
|
withRevisionName("foo-rev-1"),
|
||||||
|
withRevisionPodSpecOption(withContainer()),
|
||||||
|
),
|
||||||
|
withRevisions(
|
||||||
|
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
||||||
|
withRevisionGeneration("2"),
|
||||||
|
withRevisionName("foo-rev-2"),
|
||||||
|
withRevisionPodSpecOption(withContainer()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectedRevisionList: getRevisionListWithOptions(
|
||||||
|
withRevisions(
|
||||||
|
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
||||||
|
withRevisionName("foo-rev-1"),
|
||||||
|
withRevisionGeneration("1"),
|
||||||
|
withRevisionPodSpecOption(withContainer()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}, {
|
||||||
|
name: "test 2 revisions no traffic split",
|
||||||
|
latestSvc: getServiceWithOptions(
|
||||||
|
getService("foo"),
|
||||||
|
withTrafficSplit([]string{""}, []int{100}, []bool{true}),
|
||||||
|
withServicePodSpecOption(withContainer()),
|
||||||
|
),
|
||||||
|
expectedSvcList: getServiceListWithOptions(
|
||||||
|
withServices(
|
||||||
|
getService("foo"),
|
||||||
|
withUnwantedFieldsStripped(),
|
||||||
|
withServicePodSpecOption(withContainer()),
|
||||||
|
withTrafficSplit([]string{""}, []int{100}, []bool{true}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
revisionList: getRevisionListWithOptions(
|
||||||
|
withRevisions(
|
||||||
|
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
||||||
|
withRevisionGeneration("1"),
|
||||||
|
withRevisionName("foo-rev-1"),
|
||||||
|
withRevisionPodSpecOption(withContainer()),
|
||||||
|
),
|
||||||
|
withRevisions(
|
||||||
|
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
||||||
|
withRevisionGeneration("2"),
|
||||||
|
withRevisionName("foo-rev-2"),
|
||||||
|
withRevisionPodSpecOption(withContainer()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}, {
|
||||||
|
name: "test 3 active revisions with traffic split with no latest revision",
|
||||||
|
latestSvc: getServiceWithOptions(
|
||||||
|
getService("foo"),
|
||||||
|
withTrafficSplit([]string{"foo-rev-1", "foo-rev-2", "foo-rev-3"}, []int{25, 50, 25}, []bool{false, false, false}),
|
||||||
|
withServiceRevisionName("foo-rev-3"),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]v1.EnvVar{{Name: "a", Value: "mouse"}}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectedSvcList: getServiceListWithOptions(
|
||||||
|
withServices(
|
||||||
|
getService("foo"),
|
||||||
|
withUnwantedFieldsStripped(),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]v1.EnvVar{{Name: "a", Value: "cat"}}),
|
||||||
|
),
|
||||||
|
withServiceRevisionName("foo-rev-1"),
|
||||||
|
),
|
||||||
|
withServices(
|
||||||
|
getService("foo"),
|
||||||
|
withUnwantedFieldsStripped(),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]v1.EnvVar{{Name: "a", Value: "dog"}}),
|
||||||
|
),
|
||||||
|
withServiceRevisionName("foo-rev-2"),
|
||||||
|
),
|
||||||
|
withServices(
|
||||||
|
getService("foo"),
|
||||||
|
withUnwantedFieldsStripped(),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]v1.EnvVar{{Name: "a", Value: "mouse"}}),
|
||||||
|
),
|
||||||
|
withServiceRevisionName("foo-rev-3"),
|
||||||
|
withTrafficSplit([]string{"foo-rev-1", "foo-rev-2", "foo-rev-3"}, []int{25, 50, 25}, []bool{false, false, false}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
revisionList: getRevisionListWithOptions(
|
||||||
|
withRevisions(
|
||||||
|
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
||||||
|
withRevisionGeneration("1"),
|
||||||
|
withRevisionName("foo-rev-1"),
|
||||||
|
withRevisionPodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]v1.EnvVar{{Name: "a", Value: "cat"}}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
withRevisions(
|
||||||
|
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
||||||
|
withRevisionGeneration("2"),
|
||||||
|
withRevisionName("foo-rev-2"),
|
||||||
|
withRevisionPodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]v1.EnvVar{{Name: "a", Value: "dog"}}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
withRevisions(
|
||||||
|
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
||||||
|
withRevisionGeneration("3"),
|
||||||
|
withRevisionName("foo-rev-3"),
|
||||||
|
withRevisionPodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]v1.EnvVar{{Name: "a", Value: "mouse"}}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectedRevisionList: getRevisionListWithOptions(
|
||||||
|
withRevisions(
|
||||||
|
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
||||||
|
withRevisionName("foo-rev-1"),
|
||||||
|
withRevisionGeneration("1"),
|
||||||
|
withRevisionPodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]v1.EnvVar{{Name: "a", Value: "cat"}}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
withRevisions(
|
||||||
|
withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}),
|
||||||
|
withRevisionName("foo-rev-2"),
|
||||||
|
withRevisionGeneration("2"),
|
||||||
|
withRevisionPodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]v1.EnvVar{{Name: "a", Value: "dog"}}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
exportWithRevisionsforKubernetesTest(t, &tc)
|
||||||
|
exportWithRevisionsTest(t, &tc)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
multiRevs := getRevisionList("rev", "foo")
|
|
||||||
|
|
||||||
callServiceExportHistoryTest(t, latestSvc, multiRevs, &expSvcList)
|
|
||||||
|
|
||||||
// case 2 - same revisions no traffic split
|
|
||||||
expSvc2 = getServiceWithOptions(getService("foo"), withContainer(), withServiceRevisionName("foo-rev-2"))
|
|
||||||
stripExpectedSvcVariables(expSvc2)
|
|
||||||
expSvcList = servingv1.ServiceList{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
APIVersion: "v1",
|
|
||||||
Kind: "List",
|
|
||||||
},
|
|
||||||
Items: []servingv1.Service{*expSvc2},
|
|
||||||
}
|
|
||||||
latestSvc = getServiceWithOptions(getService("foo"), withContainer(), withTrafficSplit([]string{"foo-rev-2"}, []int{100}, []string{"latest"}))
|
|
||||||
callServiceExportHistoryTest(t, latestSvc, multiRevs, &expSvcList)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func callServiceExportHistoryTest(t *testing.T, latestSvc *servingv1.Service, revs *servingv1.RevisionList, expSvcList *servingv1.ServiceList) {
|
func exportWithRevisionsforKubernetesTest(t *testing.T, tc *testCase) {
|
||||||
// New mock client
|
output, err := executeServiceExportCommand(t, tc, "export", tc.latestSvc.ObjectMeta.Name, "--with-revisions", "--mode", "kubernetes", "-o", "json")
|
||||||
client := knclient.NewMockKnServiceClient(t)
|
|
||||||
// Recording:
|
|
||||||
r := client.Recorder()
|
|
||||||
|
|
||||||
r.GetService(latestSvc.ObjectMeta.Name, latestSvc, nil)
|
|
||||||
r.ListRevisions(mock.Any(), revs, nil)
|
|
||||||
|
|
||||||
output, err := executeServiceCommand(client, "export", latestSvc.ObjectMeta.Name, "--with-revisions", "-o", "json")
|
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
actSvcList := servingv1.ServiceList{}
|
actSvcList := servingv1.ServiceList{}
|
||||||
err = json.Unmarshal([]byte(output), &actSvcList)
|
err = json.Unmarshal([]byte(output), &actSvcList)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.DeepEqual(t, expSvcList, &actSvcList)
|
assert.DeepEqual(t, tc.expectedSvcList, &actSvcList)
|
||||||
// Validate that all recorded API methods have been called
|
|
||||||
r.Validate()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceExportError(t *testing.T) {
|
func exportWithRevisionsTest(t *testing.T, tc *testCase) {
|
||||||
// New mock client
|
output, err := executeServiceExportCommand(t, tc, "export", tc.latestSvc.ObjectMeta.Name, "--with-revisions", "--mode", "resources", "-o", "json")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
stripUnwantedFields(tc.latestSvc)
|
||||||
|
expOut := strings.Builder{}
|
||||||
|
expSvcJSON, err := json.MarshalIndent(tc.latestSvc, "", " ")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
expOut.Write(expSvcJSON)
|
||||||
|
expOut.WriteString("\n")
|
||||||
|
|
||||||
|
if tc.expectedRevisionList != nil {
|
||||||
|
expRevsJSON, err := json.MarshalIndent(tc.expectedRevisionList, "", " ")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
expOut.Write(expRevsJSON)
|
||||||
|
expOut.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expOut.String(), output)
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeServiceExportCommand(t *testing.T, tc *testCase, options ...string) (string, error) {
|
||||||
client := knclient.NewMockKnServiceClient(t)
|
client := knclient.NewMockKnServiceClient(t)
|
||||||
|
r := client.Recorder()
|
||||||
|
|
||||||
expectedService := getService("foo")
|
r.GetService(tc.latestSvc.ObjectMeta.Name, tc.latestSvc, nil)
|
||||||
|
r.ListRevisions(mock.Any(), tc.revisionList, nil)
|
||||||
|
|
||||||
_, err := executeServiceCommand(client, "export", expectedService.ObjectMeta.Name)
|
return executeServiceCommand(client, options...)
|
||||||
|
|
||||||
assert.Error(t, err, "'kn service export' requires output format")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRevisionList(revision string, service string) *servingv1.RevisionList {
|
func stripUnwantedFields(svc *servingv1.Service) {
|
||||||
rev1 := getRevisionWithOptions(
|
svc.ObjectMeta.Namespace = ""
|
||||||
service,
|
svc.Spec.Template.Spec.Containers[0].Resources = v1.ResourceRequirements{}
|
||||||
withRevisionGeneration("1"),
|
svc.Status = servingv1.ServiceStatus{}
|
||||||
withRevisionName(fmt.Sprintf("%s-%s-%d", service, revision, 1)),
|
svc.ObjectMeta.CreationTimestamp = metav1.Time{}
|
||||||
)
|
}
|
||||||
|
|
||||||
rev2 := getRevisionWithOptions(
|
func getServiceListWithOptions(options ...expectedServiceListOption) *servingv1.ServiceList {
|
||||||
service,
|
list := &servingv1.ServiceList{
|
||||||
withRevisionGeneration("2"),
|
|
||||||
withRevisionName(fmt.Sprintf("%s-%s-%d", service, revision, 2)),
|
|
||||||
)
|
|
||||||
|
|
||||||
return &servingv1.RevisionList{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
APIVersion: "v1",
|
APIVersion: "v1",
|
||||||
Kind: "List",
|
Kind: "List",
|
||||||
},
|
},
|
||||||
Items: []servingv1.Revision{rev1, rev2},
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRevisionWithOptions(service string, options ...expectedRevisionOption) servingv1.Revision {
|
|
||||||
rev := servingv1.Revision{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "Revision",
|
|
||||||
APIVersion: "serving.knative.dev/v1",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Namespace: "default",
|
|
||||||
Labels: map[string]string{
|
|
||||||
apiserving.ServiceLabelKey: service,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Spec: servingv1.RevisionSpec{
|
|
||||||
PodSpec: v1.PodSpec{
|
|
||||||
Containers: []v1.Container{
|
|
||||||
{
|
|
||||||
Image: "gcr.io/foo/bar:baz",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, fn := range options {
|
for _, fn := range options {
|
||||||
fn(&rev)
|
fn(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func withServices(svc *servingv1.Service, options ...expectedServiceOption) expectedServiceListOption {
|
||||||
|
return func(list *servingv1.ServiceList) {
|
||||||
|
list.Items = append(list.Items, *(getServiceWithOptions(svc, options...)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRevisionListWithOptions(options ...expectedRevisionListOption) *servingv1.RevisionList {
|
||||||
|
list := &servingv1.RevisionList{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "List",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range options {
|
||||||
|
fn(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func withRevisions(options ...expectedRevisionOption) expectedRevisionListOption {
|
||||||
|
return func(list *servingv1.RevisionList) {
|
||||||
|
list.Items = append(list.Items, getRevisionWithOptions(options...))
|
||||||
}
|
}
|
||||||
return rev
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServiceWithOptions(svc *servingv1.Service, options ...expectedServiceOption) *servingv1.Service {
|
func getServiceWithOptions(svc *servingv1.Service, options ...expectedServiceOption) *servingv1.Service {
|
||||||
|
|
@ -205,98 +358,47 @@ func getServiceWithOptions(svc *servingv1.Service, options ...expectedServiceOpt
|
||||||
|
|
||||||
return svc
|
return svc
|
||||||
}
|
}
|
||||||
|
|
||||||
func withLabels(labels map[string]string) expectedServiceOption {
|
func withLabels(labels map[string]string) expectedServiceOption {
|
||||||
return func(svc *servingv1.Service) {
|
return func(svc *servingv1.Service) {
|
||||||
svc.Spec.ConfigurationSpec.Template.ObjectMeta.Labels = labels
|
svc.ObjectMeta.Labels = labels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func withConfigurationLabels(labels map[string]string) expectedServiceOption {
|
||||||
func withEnvFrom(cmNames []string) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
return func(svc *servingv1.Service) {
|
||||||
var list []v1.EnvFromSource
|
svc.Spec.Template.ObjectMeta.Labels = labels
|
||||||
for _, cmName := range cmNames {
|
|
||||||
list = append(list, v1.EnvFromSource{
|
|
||||||
ConfigMapRef: &v1.ConfigMapEnvSource{
|
|
||||||
LocalObjectReference: v1.LocalObjectReference{
|
|
||||||
Name: cmName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
svc.Spec.ConfigurationSpec.Template.Spec.PodSpec.Containers[0].EnvFrom = list
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func withAnnotations(Annotations map[string]string) expectedServiceOption {
|
||||||
func withEnv(env []v1.EnvVar) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
return func(svc *servingv1.Service) {
|
||||||
svc.Spec.ConfigurationSpec.Template.Spec.PodSpec.Containers[0].Env = env
|
svc.ObjectMeta.Annotations = Annotations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func withConfigurationAnnotations(Annotations map[string]string) expectedServiceOption {
|
||||||
func withContainer() expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
return func(svc *servingv1.Service) {
|
||||||
svc.Spec.ConfigurationSpec.Template.Spec.Containers[0].Image = "gcr.io/foo/bar:baz"
|
svc.Spec.Template.ObjectMeta.Annotations = Annotations
|
||||||
svc.Spec.ConfigurationSpec.Template.Annotations = map[string]string{servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz"}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func withVolumeandSecrets(volName string, secretName string) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
template := &svc.Spec.Template
|
|
||||||
template.Spec.Volumes = []v1.Volume{
|
|
||||||
{
|
|
||||||
Name: volName,
|
|
||||||
VolumeSource: v1.VolumeSource{
|
|
||||||
Secret: &v1.SecretVolumeSource{
|
|
||||||
SecretName: secretName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
template.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{
|
|
||||||
{
|
|
||||||
Name: volName,
|
|
||||||
MountPath: "/mount/path",
|
|
||||||
ReadOnly: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func withRevisionGeneration(gen string) expectedRevisionOption {
|
|
||||||
return func(rev *servingv1.Revision) {
|
|
||||||
i, _ := strconv.Atoi(gen)
|
|
||||||
rev.ObjectMeta.Generation = int64(i)
|
|
||||||
rev.ObjectMeta.Labels[apiserving.ConfigurationGenerationLabelKey] = gen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func withRevisionName(name string) expectedRevisionOption {
|
|
||||||
return func(rev *servingv1.Revision) {
|
|
||||||
rev.ObjectMeta.Name = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func withServiceRevisionName(name string) expectedServiceOption {
|
func withServiceRevisionName(name string) expectedServiceOption {
|
||||||
return func(svc *servingv1.Service) {
|
return func(svc *servingv1.Service) {
|
||||||
svc.Spec.ConfigurationSpec.Template.ObjectMeta.Name = name
|
svc.Spec.Template.ObjectMeta.Name = name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func withUnwantedFieldsStripped() expectedServiceOption {
|
||||||
func withTrafficSplit(revisions []string, percentages []int, tags []string) expectedServiceOption {
|
return func(svc *servingv1.Service) {
|
||||||
|
svc.ObjectMeta.Namespace = ""
|
||||||
|
svc.Spec.Template.Spec.Containers[0].Resources = v1.ResourceRequirements{}
|
||||||
|
svc.Status = servingv1.ServiceStatus{}
|
||||||
|
svc.ObjectMeta.CreationTimestamp = metav1.Time{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func withTrafficSplit(revisions []string, percentages []int, latest []bool) expectedServiceOption {
|
||||||
return func(svc *servingv1.Service) {
|
return func(svc *servingv1.Service) {
|
||||||
var trafficTargets []servingv1.TrafficTarget
|
var trafficTargets []servingv1.TrafficTarget
|
||||||
for i, rev := range revisions {
|
for i, rev := range revisions {
|
||||||
trafficTargets = append(trafficTargets, servingv1.TrafficTarget{
|
trafficTargets = append(trafficTargets, servingv1.TrafficTarget{
|
||||||
Percent: ptr.Int64(int64(percentages[i])),
|
Percent: ptr.Int64(int64(percentages[i])),
|
||||||
})
|
})
|
||||||
if tags[i] != "" {
|
if latest[i] {
|
||||||
trafficTargets[i].Tag = tags[i]
|
|
||||||
}
|
|
||||||
if rev == "latest" {
|
|
||||||
trafficTargets[i].LatestRevision = ptr.Bool(true)
|
trafficTargets[i].LatestRevision = ptr.Bool(true)
|
||||||
} else {
|
} else {
|
||||||
trafficTargets[i].RevisionName = rev
|
trafficTargets[i].RevisionName = rev
|
||||||
|
|
@ -308,3 +410,86 @@ func withTrafficSplit(revisions []string, percentages []int, tags []string) expe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func withServicePodSpecOption(options ...podSpecOption) expectedServiceOption {
|
||||||
|
return func(svc *servingv1.Service) {
|
||||||
|
svc.Spec.Template.Spec.PodSpec = getPodSpecWithOptions(options...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRevisionWithOptions(options ...expectedRevisionOption) servingv1.Revision {
|
||||||
|
rev := servingv1.Revision{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "Revision",
|
||||||
|
APIVersion: "serving.knative.dev/v1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, fn := range options {
|
||||||
|
fn(&rev)
|
||||||
|
}
|
||||||
|
return rev
|
||||||
|
}
|
||||||
|
func withRevisionGeneration(gen string) expectedRevisionOption {
|
||||||
|
return func(rev *servingv1.Revision) {
|
||||||
|
rev.ObjectMeta.Labels[apiserving.ConfigurationGenerationLabelKey] = gen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func withRevisionName(name string) expectedRevisionOption {
|
||||||
|
return func(rev *servingv1.Revision) {
|
||||||
|
rev.ObjectMeta.Name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func withRevisionLabels(labels map[string]string) expectedRevisionOption {
|
||||||
|
return func(rev *servingv1.Revision) {
|
||||||
|
rev.ObjectMeta.Labels = labels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func withRevisionAnnotations(Annotations map[string]string) expectedRevisionOption {
|
||||||
|
return func(rev *servingv1.Revision) {
|
||||||
|
rev.ObjectMeta.Annotations = Annotations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func withRevisionPodSpecOption(options ...podSpecOption) expectedRevisionOption {
|
||||||
|
return func(rev *servingv1.Revision) {
|
||||||
|
rev.Spec.PodSpec = getPodSpecWithOptions(options...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPodSpecWithOptions(options ...podSpecOption) v1.PodSpec {
|
||||||
|
spec := v1.PodSpec{}
|
||||||
|
for _, fn := range options {
|
||||||
|
fn(&spec)
|
||||||
|
}
|
||||||
|
return spec
|
||||||
|
}
|
||||||
|
|
||||||
|
func withEnv(env []v1.EnvVar) podSpecOption {
|
||||||
|
return func(spec *v1.PodSpec) {
|
||||||
|
spec.Containers[0].Env = env
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func withContainer() podSpecOption {
|
||||||
|
return func(spec *v1.PodSpec) {
|
||||||
|
spec.Containers = append(spec.Containers, v1.Container{Image: "gcr.io/foo/bar:baz"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func withVolumeandSecrets(secretName string) podSpecOption {
|
||||||
|
return func(spec *v1.PodSpec) {
|
||||||
|
spec.Volumes = []v1.Volume{
|
||||||
|
{
|
||||||
|
Name: secretName,
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
Secret: &v1.SecretVolumeSource{
|
||||||
|
SecretName: secretName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
spec.Containers[0].VolumeMounts = []v1.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: secretName,
|
||||||
|
MountPath: "/mount/path",
|
||||||
|
ReadOnly: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package e2e
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gotest.tools/assert"
|
"gotest.tools/assert"
|
||||||
|
|
@ -35,8 +36,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type expectedServiceOption func(*servingv1.Service)
|
type expectedServiceOption func(*servingv1.Service)
|
||||||
|
type expectedRevisionOption func(*servingv1.Revision)
|
||||||
|
type expectedServiceListOption func(*servingv1.ServiceList)
|
||||||
|
type expectedRevisionListOption func(*servingv1.RevisionList)
|
||||||
|
type podSpecOption func(*corev1.PodSpec)
|
||||||
|
|
||||||
func TestServiceExportImportApply(t *testing.T) {
|
func TestServiceExport(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
it, err := test.NewKnTest()
|
it, err := test.NewKnTest()
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
@ -50,22 +55,235 @@ func TestServiceExportImportApply(t *testing.T) {
|
||||||
t.Log("create service with byo revision")
|
t.Log("create service with byo revision")
|
||||||
serviceCreateWithOptions(r, "hello", "--revision-name", "rev1")
|
serviceCreateWithOptions(r, "hello", "--revision-name", "rev1")
|
||||||
|
|
||||||
t.Log("export service and compare")
|
t.Log("export service-revision1 and compare")
|
||||||
serviceExport(r, "hello", getSvc(withName("hello"), withRevisionName("hello-rev1"), withAnnotations()), "-o", "json")
|
serviceExport(r, "hello", getServiceWithOptions(
|
||||||
|
withServiceName("hello"),
|
||||||
|
withServiceRevisionName("hello-rev1"),
|
||||||
|
withConfigurationAnnotations(),
|
||||||
|
withServicePodSpecOption(withContainer()),
|
||||||
|
), "-o", "json")
|
||||||
|
|
||||||
t.Log("update service - add env variable")
|
t.Log("update service - add env variable")
|
||||||
serviceUpdateWithOptions(r, "hello", "--env", "key1=val1", "--revision-name", "rev2", "--no-lock-to-digest")
|
serviceUpdateWithOptions(r, "hello", "--env", "a=mouse", "--revision-name", "rev2", "--no-lock-to-digest")
|
||||||
serviceExport(r, "hello", getSvc(withName("hello"), withRevisionName("hello-rev2"), withEnv("key1", "val1")), "-o", "json")
|
serviceExport(r, "hello", getServiceWithOptions(
|
||||||
serviceExportWithRevisions(r, "hello", getSvcListWithOneRevision(), "--with-revisions", "-o", "yaml")
|
withServiceName("hello"),
|
||||||
|
withServiceRevisionName("hello-rev2"),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
||||||
|
),
|
||||||
|
), "-o", "json")
|
||||||
|
|
||||||
|
t.Log("export service-revision2 with kubernetes-resources")
|
||||||
|
serviceExportWithServiceList(r, "hello", getServiceListWithOptions(
|
||||||
|
withServices(
|
||||||
|
withServiceName("hello"),
|
||||||
|
withServiceRevisionName("hello-rev2"),
|
||||||
|
withTrafficSplit([]string{"latest"}, []int{100}, []string{""}),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
), "--with-revisions", "--mode", "kubernetes", "-o", "yaml")
|
||||||
|
|
||||||
|
t.Log("export service-revision2 with revisions-only")
|
||||||
|
serviceExportWithRevisionList(r, "hello", getServiceWithOptions(
|
||||||
|
withServiceName("hello"),
|
||||||
|
withServiceRevisionName("hello-rev2"),
|
||||||
|
withTrafficSplit([]string{"latest"}, []int{100}, []string{""}),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
||||||
|
),
|
||||||
|
), getRevisionListWithOptions(), "--with-revisions", "--mode", "resources", "-o", "yaml")
|
||||||
|
|
||||||
t.Log("update service with tag and split traffic")
|
t.Log("update service with tag and split traffic")
|
||||||
serviceUpdateWithOptions(r, "hello", "--tag", "hello-rev1=candidate", "--traffic", "candidate=2%,@latest=98%")
|
serviceUpdateWithOptions(r, "hello", "--tag", "hello-rev1=candidate", "--traffic", "candidate=2%,@latest=98%")
|
||||||
serviceExportWithRevisions(r, "hello", getSvcListWithTags(), "--with-revisions", "-o", "yaml")
|
|
||||||
|
|
||||||
t.Log("update service - untag, add env variable and traffic split")
|
t.Log("export service-revision2 after tagging kubernetes-resources")
|
||||||
|
serviceExportWithServiceList(r, "hello", getServiceListWithOptions(
|
||||||
|
withServices(
|
||||||
|
withServiceName("hello"),
|
||||||
|
withServiceRevisionName("hello-rev1"),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
withServices(
|
||||||
|
withServiceName("hello"),
|
||||||
|
withServiceRevisionName("hello-rev2"),
|
||||||
|
withTrafficSplit([]string{"latest", "hello-rev1"}, []int{98, 2}, []string{"", "candidate"}),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
), "--with-revisions", "--mode", "kubernetes", "-o", "yaml")
|
||||||
|
|
||||||
|
t.Log("export service-revision2 after tagging with revisions-only")
|
||||||
|
serviceExportWithRevisionList(r, "hello", getServiceWithOptions(
|
||||||
|
withServiceName("hello"),
|
||||||
|
withServiceRevisionName("hello-rev2"),
|
||||||
|
withTrafficSplit([]string{"latest", "hello-rev1"}, []int{98, 2}, []string{"", "candidate"}),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
||||||
|
),
|
||||||
|
), getRevisionListWithOptions(
|
||||||
|
withRevisions(
|
||||||
|
withRevisionName("hello-rev1"),
|
||||||
|
withRevisionAnnotations(
|
||||||
|
map[string]string{
|
||||||
|
"client.knative.dev/user-image": "gcr.io/knative-samples/helloworld-go",
|
||||||
|
"serving.knative.dev/creator": "kubernetes-admin",
|
||||||
|
}),
|
||||||
|
withRevisionLabels(
|
||||||
|
map[string]string{
|
||||||
|
"serving.knative.dev/configuration": "hello",
|
||||||
|
"serving.knative.dev/configurationGeneration": "1",
|
||||||
|
"serving.knative.dev/route": "hello",
|
||||||
|
"serving.knative.dev/service": "hello",
|
||||||
|
}),
|
||||||
|
withRevisionPodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
), "--with-revisions", "--mode", "resources", "-o", "yaml")
|
||||||
|
|
||||||
|
t.Log("update service - untag, add env variable, traffic split and system revision name")
|
||||||
serviceUpdateWithOptions(r, "hello", "--untag", "candidate")
|
serviceUpdateWithOptions(r, "hello", "--untag", "candidate")
|
||||||
serviceUpdateWithOptions(r, "hello", "--env", "key2=val2", "--revision-name", "rev3", "--traffic", "hello-rev1=30,hello-rev2=30,hello-rev3=40")
|
serviceUpdateWithOptions(r, "hello", "--env", "b=cat", "--revision-name", "hello-rev3", "--traffic", "hello-rev1=30,hello-rev2=30,hello-rev3=40")
|
||||||
serviceExportWithRevisions(r, "hello", getSvcListWOTags(), "--with-revisions", "-o", "yaml")
|
|
||||||
|
t.Log("export service-revision3 with kubernetes-resources")
|
||||||
|
serviceExportWithServiceList(r, "hello", getServiceListWithOptions(
|
||||||
|
withServices(
|
||||||
|
withServiceName("hello"),
|
||||||
|
withServiceRevisionName("hello-rev1"),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
withServices(
|
||||||
|
withServiceName("hello"),
|
||||||
|
withServiceRevisionName("hello-rev2"),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
withServices(
|
||||||
|
withServiceName("hello"),
|
||||||
|
withServiceRevisionName("hello-rev3"),
|
||||||
|
withTrafficSplit([]string{"hello-rev1", "hello-rev2", "hello-rev3"}, []int{30, 30, 40}, []string{"", "", ""}),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}, {Name: "b", Value: "cat"}}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
), "--with-revisions", "--mode", "kubernetes", "-o", "yaml")
|
||||||
|
|
||||||
|
t.Log("export service-revision3 with revisions-only")
|
||||||
|
serviceExportWithRevisionList(r, "hello", getServiceWithOptions(
|
||||||
|
withServiceName("hello"),
|
||||||
|
withServiceRevisionName("hello-rev3"),
|
||||||
|
withTrafficSplit([]string{"hello-rev1", "hello-rev2", "hello-rev3"}, []int{30, 30, 40}, []string{"", "", ""}),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}, {Name: "b", Value: "cat"}}),
|
||||||
|
),
|
||||||
|
), getRevisionListWithOptions(
|
||||||
|
withRevisions(
|
||||||
|
withRevisionName("hello-rev1"),
|
||||||
|
withRevisionAnnotations(
|
||||||
|
map[string]string{
|
||||||
|
"client.knative.dev/user-image": "gcr.io/knative-samples/helloworld-go",
|
||||||
|
"serving.knative.dev/creator": "kubernetes-admin",
|
||||||
|
}),
|
||||||
|
withRevisionLabels(
|
||||||
|
map[string]string{
|
||||||
|
"serving.knative.dev/configuration": "hello",
|
||||||
|
"serving.knative.dev/configurationGeneration": "1",
|
||||||
|
"serving.knative.dev/route": "hello",
|
||||||
|
"serving.knative.dev/service": "hello",
|
||||||
|
}),
|
||||||
|
withRevisionPodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
withRevisions(
|
||||||
|
withRevisionName("hello-rev2"),
|
||||||
|
withRevisionAnnotations(
|
||||||
|
map[string]string{
|
||||||
|
"serving.knative.dev/creator": "kubernetes-admin",
|
||||||
|
}),
|
||||||
|
withRevisionLabels(
|
||||||
|
map[string]string{
|
||||||
|
"serving.knative.dev/configuration": "hello",
|
||||||
|
"serving.knative.dev/configurationGeneration": "2",
|
||||||
|
"serving.knative.dev/route": "hello",
|
||||||
|
"serving.knative.dev/service": "hello",
|
||||||
|
}),
|
||||||
|
withRevisionPodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
), "--with-revisions", "--mode", "resources", "-o", "yaml")
|
||||||
|
|
||||||
|
t.Log("send all traffic to revision 2")
|
||||||
|
serviceUpdateWithOptions(r, "hello", "--traffic", "hello-rev2=100")
|
||||||
|
|
||||||
|
t.Log("export kubernetes-resources - all traffic to revision 2")
|
||||||
|
serviceExportWithServiceList(r, "hello", getServiceListWithOptions(
|
||||||
|
withServices(
|
||||||
|
withServiceName("hello"),
|
||||||
|
withServiceRevisionName("hello-rev2"),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
withServices(
|
||||||
|
withServiceName("hello"),
|
||||||
|
withServiceRevisionName("hello-rev3"),
|
||||||
|
withTrafficSplit([]string{"hello-rev2"}, []int{100}, []string{""}),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}, {Name: "b", Value: "cat"}}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
), "--with-revisions", "--mode", "kubernetes", "-o", "yaml")
|
||||||
|
|
||||||
|
t.Log("export revisions-only - all traffic to revision 2")
|
||||||
|
serviceExportWithRevisionList(r, "hello", getServiceWithOptions(
|
||||||
|
withServiceName("hello"),
|
||||||
|
withServiceRevisionName("hello-rev3"),
|
||||||
|
withTrafficSplit([]string{"hello-rev2"}, []int{100}, []string{""}),
|
||||||
|
withServicePodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}, {Name: "b", Value: "cat"}}),
|
||||||
|
),
|
||||||
|
), getRevisionListWithOptions(
|
||||||
|
withRevisions(
|
||||||
|
withRevisionName("hello-rev2"),
|
||||||
|
withRevisionAnnotations(
|
||||||
|
map[string]string{
|
||||||
|
"serving.knative.dev/creator": "kubernetes-admin",
|
||||||
|
}),
|
||||||
|
withRevisionLabels(
|
||||||
|
map[string]string{
|
||||||
|
"serving.knative.dev/configuration": "hello",
|
||||||
|
"serving.knative.dev/configurationGeneration": "2",
|
||||||
|
"serving.knative.dev/route": "hello",
|
||||||
|
"serving.knative.dev/service": "hello",
|
||||||
|
}),
|
||||||
|
withRevisionPodSpecOption(
|
||||||
|
withContainer(),
|
||||||
|
withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
), "--with-revisions", "--mode", "resources", "-o", "yaml")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private methods
|
// Private methods
|
||||||
|
|
@ -78,7 +296,7 @@ func serviceExport(r *test.KnRunResultCollector, serviceName string, expService
|
||||||
r.AssertNoError(out)
|
r.AssertNoError(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func serviceExportWithRevisions(r *test.KnRunResultCollector, serviceName string, expServiceList servingv1.ServiceList, options ...string) {
|
func serviceExportWithServiceList(r *test.KnRunResultCollector, serviceName string, expServiceList servingv1.ServiceList, options ...string) {
|
||||||
command := []string{"service", "export", serviceName}
|
command := []string{"service", "export", serviceName}
|
||||||
command = append(command, options...)
|
command = append(command, options...)
|
||||||
out := r.KnTest().Kn().Run(command...)
|
out := r.KnTest().Kn().Run(command...)
|
||||||
|
|
@ -86,166 +304,131 @@ func serviceExportWithRevisions(r *test.KnRunResultCollector, serviceName string
|
||||||
r.AssertNoError(out)
|
r.AssertNoError(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serviceExportWithRevisionList(r *test.KnRunResultCollector, serviceName string, expService servingv1.Service, expRevisionList servingv1.RevisionList, options ...string) {
|
||||||
|
command := []string{"service", "export", serviceName}
|
||||||
|
command = append(command, options...)
|
||||||
|
out := r.KnTest().Kn().Run(command...)
|
||||||
|
validateExportedServiceandRevisionList(r.T(), r.KnTest(), out.Stdout, expService, expRevisionList)
|
||||||
|
r.AssertNoError(out)
|
||||||
|
}
|
||||||
|
|
||||||
// Private functions
|
// Private functions
|
||||||
|
|
||||||
func validateExportedService(t *testing.T, it *test.KnTest, out string, expService servingv1.Service) {
|
func validateExportedService(t *testing.T, it *test.KnTest, out string, expService servingv1.Service) {
|
||||||
actSvcJSON := servingv1.Service{}
|
actSvc := servingv1.Service{}
|
||||||
err := json.Unmarshal([]byte(out), &actSvcJSON)
|
err := json.Unmarshal([]byte(out), &actSvc)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.DeepEqual(t, &expService, &actSvcJSON)
|
assert.DeepEqual(t, &expService, &actSvc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateExportedServiceList(t *testing.T, it *test.KnTest, out string, expServiceList servingv1.ServiceList) {
|
func validateExportedServiceList(t *testing.T, it *test.KnTest, out string, expServiceList servingv1.ServiceList) {
|
||||||
actYaml := servingv1.ServiceList{}
|
actSvcList := servingv1.ServiceList{}
|
||||||
err := yaml.Unmarshal([]byte(out), &actYaml)
|
err := yaml.Unmarshal([]byte(out), &actSvcList)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.DeepEqual(t, &expServiceList, &actYaml)
|
assert.DeepEqual(t, &expServiceList, &actSvcList)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSvc(options ...expectedServiceOption) servingv1.Service {
|
func validateExportedServiceandRevisionList(t *testing.T, it *test.KnTest, out string, expService servingv1.Service, expRevisionList servingv1.RevisionList) {
|
||||||
svc := servingv1.Service{
|
outArray := strings.Split(out, "apiVersion: v1")
|
||||||
Spec: servingv1.ServiceSpec{
|
|
||||||
ConfigurationSpec: servingv1.ConfigurationSpec{
|
actSvc := servingv1.Service{}
|
||||||
Template: servingv1.RevisionTemplateSpec{
|
err := yaml.Unmarshal([]byte(outArray[0]), &actSvc)
|
||||||
Spec: servingv1.RevisionSpec{
|
assert.NilError(t, err)
|
||||||
ContainerConcurrency: ptr.Int64(int64(0)),
|
assert.DeepEqual(t, &expService, &actSvc)
|
||||||
TimeoutSeconds: ptr.Int64(int64(300)),
|
|
||||||
PodSpec: corev1.PodSpec{
|
if len(outArray) > 1 {
|
||||||
Containers: []corev1.Container{
|
revListBuilder := strings.Builder{}
|
||||||
{
|
revListBuilder.WriteString("apiVersion: v1")
|
||||||
Name: "user-container",
|
revListBuilder.WriteString("\n")
|
||||||
Image: test.KnDefaultTestImage,
|
revListBuilder.WriteString(outArray[1])
|
||||||
Resources: corev1.ResourceRequirements{},
|
actRevList := servingv1.RevisionList{}
|
||||||
ReadinessProbe: &corev1.Probe{
|
err := yaml.Unmarshal([]byte(revListBuilder.String()), &actRevList)
|
||||||
SuccessThreshold: int32(1),
|
assert.NilError(t, err)
|
||||||
Handler: corev1.Handler{
|
assert.DeepEqual(t, &actRevList, &actRevList)
|
||||||
TCPSocket: &corev1.TCPSocketAction{
|
} else if len(expRevisionList.Items) > 0 {
|
||||||
Port: intstr.FromInt(0),
|
t.Errorf("expecting a revision list and got no list")
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
|
||||||
},
|
func getServiceListWithOptions(options ...expectedServiceListOption) servingv1.ServiceList {
|
||||||
},
|
list := servingv1.ServiceList{
|
||||||
},
|
TypeMeta: metav1.TypeMeta{
|
||||||
},
|
APIVersion: "v1",
|
||||||
},
|
Kind: "List",
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range options {
|
||||||
|
fn(&list)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func withServices(options ...expectedServiceOption) expectedServiceListOption {
|
||||||
|
return func(list *servingv1.ServiceList) {
|
||||||
|
list.Items = append(list.Items, getServiceWithOptions(options...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRevisionListWithOptions(options ...expectedRevisionListOption) servingv1.RevisionList {
|
||||||
|
list := servingv1.RevisionList{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "List",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range options {
|
||||||
|
fn(&list)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func withRevisions(options ...expectedRevisionOption) expectedRevisionListOption {
|
||||||
|
return func(list *servingv1.RevisionList) {
|
||||||
|
list.Items = append(list.Items, getRevisionWithOptions(options...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServiceWithOptions(options ...expectedServiceOption) servingv1.Service {
|
||||||
|
svc := servingv1.Service{
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
Kind: "Service",
|
Kind: "Service",
|
||||||
APIVersion: "serving.knative.dev/v1",
|
APIVersion: "serving.knative.dev/v1",
|
||||||
},
|
},
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, fn := range options {
|
for _, fn := range options {
|
||||||
fn(&svc)
|
fn(&svc)
|
||||||
}
|
}
|
||||||
|
svc.Spec.Template.Spec.ContainerConcurrency = ptr.Int64(int64(0))
|
||||||
|
svc.Spec.Template.Spec.TimeoutSeconds = ptr.Int64(int64(300))
|
||||||
|
|
||||||
return svc
|
return svc
|
||||||
}
|
}
|
||||||
|
func withServiceName(name string) expectedServiceOption {
|
||||||
func getSvcListWOTags() servingv1.ServiceList {
|
|
||||||
return servingv1.ServiceList{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
APIVersion: "v1",
|
|
||||||
Kind: "List",
|
|
||||||
},
|
|
||||||
Items: []servingv1.Service{
|
|
||||||
getSvc(
|
|
||||||
withName("hello"),
|
|
||||||
withRevisionName("hello-rev1"),
|
|
||||||
),
|
|
||||||
getSvc(
|
|
||||||
withName("hello"),
|
|
||||||
withRevisionName("hello-rev2"),
|
|
||||||
withEnv("key1", "val1"),
|
|
||||||
),
|
|
||||||
getSvc(
|
|
||||||
withName("hello"),
|
|
||||||
withRevisionName("hello-rev3"),
|
|
||||||
withEnv("key1", "val1"), withEnv("key2", "val2"),
|
|
||||||
withTrafficSplit([]string{"hello-rev1", "hello-rev2", "hello-rev3"}, []int{30, 30, 40}, []string{"", "", ""}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSvcListWithTags() servingv1.ServiceList {
|
|
||||||
return servingv1.ServiceList{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
APIVersion: "v1",
|
|
||||||
Kind: "List",
|
|
||||||
},
|
|
||||||
Items: []servingv1.Service{
|
|
||||||
getSvc(
|
|
||||||
withName("hello"),
|
|
||||||
withRevisionName("hello-rev1"),
|
|
||||||
),
|
|
||||||
getSvc(
|
|
||||||
withName("hello"),
|
|
||||||
withRevisionName("hello-rev2"),
|
|
||||||
withEnv("key1", "val1"),
|
|
||||||
withTrafficSplit([]string{"latest", "hello-rev1"}, []int{98, 2}, []string{"", "candidate"}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSvcListWithOneRevision() servingv1.ServiceList {
|
|
||||||
return servingv1.ServiceList{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
APIVersion: "v1",
|
|
||||||
Kind: "List",
|
|
||||||
},
|
|
||||||
Items: []servingv1.Service{
|
|
||||||
getSvc(
|
|
||||||
withName("hello"),
|
|
||||||
withRevisionName("hello-rev2"),
|
|
||||||
withEnv("key1", "val1"),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func withRevisionName(revName string) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
svc.Spec.ConfigurationSpec.Template.ObjectMeta.Name = revName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func withAnnotations() expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
|
||||||
svc.Spec.ConfigurationSpec.Template.ObjectMeta.Annotations = map[string]string{
|
|
||||||
"client.knative.dev/user-image": "gcr.io/knative-samples/helloworld-go",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func withName(name string) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
return func(svc *servingv1.Service) {
|
||||||
svc.ObjectMeta.Name = name
|
svc.ObjectMeta.Name = name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func withConfigurationLabels(labels map[string]string) expectedServiceOption {
|
||||||
func withEnv(key string, val string) expectedServiceOption {
|
|
||||||
return func(svc *servingv1.Service) {
|
return func(svc *servingv1.Service) {
|
||||||
env := []corev1.EnvVar{
|
svc.Spec.Template.ObjectMeta.Labels = labels
|
||||||
{
|
}
|
||||||
Name: key,
|
}
|
||||||
Value: val,
|
func withConfigurationAnnotations() expectedServiceOption {
|
||||||
},
|
return func(svc *servingv1.Service) {
|
||||||
}
|
svc.Spec.Template.ObjectMeta.Annotations = map[string]string{
|
||||||
currentEnv := svc.Spec.ConfigurationSpec.Template.Spec.PodSpec.Containers[0].Env
|
"client.knative.dev/user-image": "gcr.io/knative-samples/helloworld-go",
|
||||||
if len(currentEnv) > 0 {
|
}
|
||||||
svc.Spec.ConfigurationSpec.Template.Spec.PodSpec.Containers[0].Env = append(currentEnv, env...)
|
}
|
||||||
} else {
|
}
|
||||||
svc.Spec.ConfigurationSpec.Template.Spec.PodSpec.Containers[0].Env = env
|
func withServiceRevisionName(name string) expectedServiceOption {
|
||||||
}
|
return func(svc *servingv1.Service) {
|
||||||
|
svc.Spec.Template.ObjectMeta.Name = name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func withTrafficSplit(revisions []string, percentages []int, tags []string) expectedServiceOption {
|
func withTrafficSplit(revisions []string, percentages []int, tags []string) expectedServiceOption {
|
||||||
return func(svc *servingv1.Service) {
|
return func(svc *servingv1.Service) {
|
||||||
var trafficTargets []servingv1.TrafficTarget
|
var trafficTargets []servingv1.TrafficTarget
|
||||||
|
|
@ -268,3 +451,76 @@ func withTrafficSplit(revisions []string, percentages []int, tags []string) expe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func withServicePodSpecOption(options ...podSpecOption) expectedServiceOption {
|
||||||
|
return func(svc *servingv1.Service) {
|
||||||
|
svc.Spec.Template.Spec.PodSpec = getPodSpecWithOptions(options...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func getRevisionWithOptions(options ...expectedRevisionOption) servingv1.Revision {
|
||||||
|
rev := servingv1.Revision{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "Revision",
|
||||||
|
APIVersion: "serving.knative.dev/v1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, fn := range options {
|
||||||
|
fn(&rev)
|
||||||
|
}
|
||||||
|
rev.Spec.ContainerConcurrency = ptr.Int64(int64(0))
|
||||||
|
rev.Spec.TimeoutSeconds = ptr.Int64(int64(300))
|
||||||
|
return rev
|
||||||
|
}
|
||||||
|
func withRevisionName(name string) expectedRevisionOption {
|
||||||
|
return func(rev *servingv1.Revision) {
|
||||||
|
rev.ObjectMeta.Name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func withRevisionLabels(labels map[string]string) expectedRevisionOption {
|
||||||
|
return func(rev *servingv1.Revision) {
|
||||||
|
rev.ObjectMeta.Labels = labels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func withRevisionAnnotations(Annotations map[string]string) expectedRevisionOption {
|
||||||
|
return func(rev *servingv1.Revision) {
|
||||||
|
rev.ObjectMeta.Annotations = Annotations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func withRevisionPodSpecOption(options ...podSpecOption) expectedRevisionOption {
|
||||||
|
return func(rev *servingv1.Revision) {
|
||||||
|
rev.Spec.PodSpec = getPodSpecWithOptions(options...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPodSpecWithOptions(options ...podSpecOption) corev1.PodSpec {
|
||||||
|
spec := corev1.PodSpec{}
|
||||||
|
for _, fn := range options {
|
||||||
|
fn(&spec)
|
||||||
|
}
|
||||||
|
return spec
|
||||||
|
}
|
||||||
|
|
||||||
|
func withEnv(env []corev1.EnvVar) podSpecOption {
|
||||||
|
return func(spec *corev1.PodSpec) {
|
||||||
|
spec.Containers[0].Env = env
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withContainer() podSpecOption {
|
||||||
|
return func(spec *corev1.PodSpec) {
|
||||||
|
spec.Containers = []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "user-container",
|
||||||
|
Image: test.KnDefaultTestImage,
|
||||||
|
Resources: corev1.ResourceRequirements{},
|
||||||
|
ReadinessProbe: &corev1.Probe{
|
||||||
|
SuccessThreshold: int32(1),
|
||||||
|
Handler: corev1.Handler{
|
||||||
|
TCPSocket: &corev1.TCPSocketAction{
|
||||||
|
Port: intstr.FromInt(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue