-
Notifications
You must be signed in to change notification settings - Fork 263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add "kn service export" (#653) #669
Changes from all commits
391ee2d
86f865a
0cd315b
768ac45
8082f98
edb7cf0
43baea4
3da751c
4abb2be
a1a59c4
de0e4d6
5854bb3
ec9d369
d317429
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 { | ||
itsmurugappan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 { | ||
itsmurugappan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 { | ||
itsmurugappan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we remove the shortcut for now. I think its better to spell out the option full each time to make it explicit. Also, the |
||
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we would need annotations, too. See below. |
||
}, | ||
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's with custom annotations provided by the user ? We might want to filter out automatically added annotations (which we now), but as we allow a |
||
}, | ||
TypeMeta: latestSvc.TypeMeta, | ||
} | ||
|
||
exportedSvc.Spec.Template = servingv1.RevisionTemplateSpec{ | ||
Spec: revision.Spec, | ||
ObjectMeta: latestSvc.Spec.ConfigurationSpec.Template.ObjectMeta, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not from the revision ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From revision they were lot other values. From ksvc is see only the below
|
||
} | ||
|
||
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...) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could directly use revisionList, err := client.ListRevisions(clientservingv1.WithService(latestSvc.ObjectMeta.Name)) which is shorter and supposedly easier to read the intention. |
||
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) | ||
itsmurugappan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please change the short description to be consistent with the rest of commands. It's used in generated
.md
as a sentence.