mirror of https://github.com/knative/client.git
add git ops options (#1122)
* add git ops options * add git ops options * add unit tests * add unit tests * add unit tests * add unit test * add unit test * add unit test * review comments * review comments * add single file mode * add single file mode * add single file mode * add single file mode * review comments
This commit is contained in:
parent
174e41b628
commit
f5ac4413d0
|
|
@ -48,6 +48,11 @@ kn service create NAME --image IMAGE
|
|||
# [https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/]
|
||||
# [https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/]
|
||||
kn service create s4gpu --image knativesamples/hellocuda-go --request memory=250Mi,cpu=200m --limit nvidia.com/gpu=1
|
||||
|
||||
# Create the service in offline mode instead of kubernetes cluster
|
||||
kn service create gitopstest -n test-ns --image knativesamples/helloworld --target=/user/knfiles
|
||||
kn service create gitopstest --image knativesamples/helloworld --target=/user/knfiles/test.yaml
|
||||
kn service create gitopstest --image knativesamples/helloworld --target=/user/knfiles/test.json
|
||||
```
|
||||
|
||||
### Options
|
||||
|
|
@ -88,6 +93,7 @@ kn service create NAME --image IMAGE
|
|||
--scale-max int Maximum number of replicas.
|
||||
--scale-min int Minimum number of replicas.
|
||||
--service-account string Service account name to set. An empty argument ("") clears the service account. The referenced service account must exist in the service's namespace.
|
||||
--target string work on local directory instead of a remote cluster
|
||||
--user int The user ID to run the container (e.g., 1001).
|
||||
--volume stringArray Add a volume from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret: or sc:). Example: --volume myvolume=cm:myconfigmap or --volume myvolume=secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --volume myvolume-.
|
||||
--wait Wait for 'service create' operation to be completed. (default true)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ kn service delete NAME [NAME ...]
|
|||
|
||||
# Delete all services in 'ns1' namespace
|
||||
kn service delete --all -n ns1
|
||||
|
||||
# Delete the services in offline mode instead of kubernetes cluster
|
||||
kn service delete test -n test-ns --target=/user/knfiles
|
||||
kn service delete test --target=/user/knfiles/test.yaml
|
||||
kn service delete test --target=/user/knfiles/test.json
|
||||
```
|
||||
|
||||
### Options
|
||||
|
|
@ -31,6 +36,7 @@ kn service delete NAME [NAME ...]
|
|||
-h, --help help for delete
|
||||
-n, --namespace string Specify the namespace to operate in.
|
||||
--no-wait Do not wait for 'service delete' operation to be completed. (default true)
|
||||
--target string work on local directory instead of a remote cluster
|
||||
--wait Wait for 'service delete' operation to be completed.
|
||||
--wait-timeout int Seconds to wait before giving up on waiting for service to be deleted. (default 600)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ kn service describe NAME
|
|||
|
||||
# Print only service URL
|
||||
kn service describe svc -o url
|
||||
|
||||
# Describe the services in offline mode instead of kubernetes cluster
|
||||
kn service describe test -n test-ns --target=/user/knfiles
|
||||
kn service describe test --target=/user/knfiles/test.yaml
|
||||
kn service describe test --target=/user/knfiles/test.json
|
||||
```
|
||||
|
||||
### Options
|
||||
|
|
@ -31,6 +36,7 @@ kn service describe NAME
|
|||
-h, --help help for describe
|
||||
-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|url.
|
||||
--target string work on local directory instead of a remote cluster
|
||||
--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].
|
||||
-v, --verbose More output.
|
||||
```
|
||||
|
|
|
|||
|
|
@ -22,6 +22,12 @@ kn service list
|
|||
|
||||
# List service 'web'
|
||||
kn service list web
|
||||
|
||||
# List the services in offline mode instead of kubernetes cluster
|
||||
kn service list --target=/user/knfiles
|
||||
kn service list --target=/user/knfiles/test.json
|
||||
kn service list --target=/user/knfiles/test.yaml
|
||||
kn service list -n test-ns --target=/user/knfiles
|
||||
```
|
||||
|
||||
### Options
|
||||
|
|
@ -33,6 +39,7 @@ kn service list
|
|||
-n, --namespace string Specify the namespace to operate in.
|
||||
--no-headers When using the default output format, don't print headers (default: print headers).
|
||||
-o, --output string Output format. One of: json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-file.
|
||||
--target string work on local directory instead of a remote cluster
|
||||
--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].
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ kn service update NAME
|
|||
|
||||
# Add tag 'test' to echo-v3 revision with 10% traffic and rest to latest ready revision of service
|
||||
kn service update svc --tag echo-v3=test --traffic test=10,@latest=90
|
||||
|
||||
# Update the service in offline mode instead of kubernetes cluster
|
||||
kn service update gitopstest -n test-ns --env KEY1=VALUE1 --target=/user/knfiles
|
||||
kn service update gitopstest --env KEY1=VALUE1 --target=/user/knfiles/test.yaml
|
||||
kn service update gitopstest --env KEY1=VALUE1 --target=/user/knfiles/test.json
|
||||
```
|
||||
|
||||
### Options
|
||||
|
|
@ -72,6 +77,7 @@ kn service update NAME
|
|||
--scale-min int Minimum number of replicas.
|
||||
--service-account string Service account name to set. An empty argument ("") clears the service account. The referenced service account must exist in the service's namespace.
|
||||
--tag strings Set tag (format: --tag revisionRef=tagName) where revisionRef can be a revision or '@latest' string representing latest ready revision. This flag can be specified multiple times.
|
||||
--target string work on local directory instead of a remote cluster
|
||||
--traffic strings Set traffic distribution (format: --traffic revisionRef=percent) where revisionRef can be a revision or a tag or '@latest' string representing latest ready revision. This flag can be given multiple times with percent summing up to 100%.
|
||||
--untag strings Untag revision (format: --untag tagName). This flag can be specified multiple times.
|
||||
--user int The user ID to run the container (e.g., 1001).
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/duration"
|
||||
hprinters "knative.dev/client/pkg/printers"
|
||||
|
|
@ -113,3 +114,8 @@ func TranslateTimestampSince(timestamp metav1.Time) string {
|
|||
}
|
||||
return duration.HumanDuration(time.Since(timestamp.Time))
|
||||
}
|
||||
|
||||
// AddGitOpsFlags adds flags to enable gitops mode
|
||||
func AddGitOpsFlags(flags *pflag.FlagSet) {
|
||||
flags.String("target", "", "work on local directory instead of a remote cluster")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ func NewServiceApplyCommand(p *commands.KnParams) *cobra.Command {
|
|||
|
||||
return showUrl(client, service.Name, "unchanged", "", cmd.OutOrStdout())
|
||||
}
|
||||
return waitIfRequested(client, service.Name, waitFlags, waitDoing, waitVerb, cmd.OutOrStdout())
|
||||
return waitIfRequested(client, waitFlags, service.Name, waitDoing, waitVerb, "", cmd.OutOrStdout())
|
||||
},
|
||||
}
|
||||
commands.AddNamespaceFlags(serviceApplyCommand.Flags(), false)
|
||||
|
|
|
|||
|
|
@ -70,7 +70,12 @@ var create_example = `
|
|||
# Create a service with 250MB memory, 200m CPU requests and a GPU resource limit
|
||||
# [https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/]
|
||||
# [https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/]
|
||||
kn service create s4gpu --image knativesamples/hellocuda-go --request memory=250Mi,cpu=200m --limit nvidia.com/gpu=1`
|
||||
kn service create s4gpu --image knativesamples/hellocuda-go --request memory=250Mi,cpu=200m --limit nvidia.com/gpu=1
|
||||
|
||||
# Create the service in offline mode instead of kubernetes cluster
|
||||
kn service create gitopstest -n test-ns --image knativesamples/helloworld --target=/user/knfiles
|
||||
kn service create gitopstest --image knativesamples/helloworld --target=/user/knfiles/test.yaml
|
||||
kn service create gitopstest --image knativesamples/helloworld --target=/user/knfiles/test.json`
|
||||
|
||||
func NewServiceCreateCommand(p *commands.KnParams) *cobra.Command {
|
||||
var editFlags ConfigurationEditFlags
|
||||
|
|
@ -106,8 +111,8 @@ func NewServiceCreateCommand(p *commands.KnParams) *cobra.Command {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := p.NewServingClient(namespace)
|
||||
targetFlag := cmd.Flag("target").Value.String()
|
||||
client, err := newServingClient(p, namespace, targetFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -123,9 +128,9 @@ func NewServiceCreateCommand(p *commands.KnParams) *cobra.Command {
|
|||
"cannot create service '%s' in namespace '%s' "+
|
||||
"because the service already exists and no --force option was given", service.Name, namespace)
|
||||
}
|
||||
err = replaceService(client, service, waitFlags, out)
|
||||
err = replaceService(client, service, waitFlags, out, targetFlag)
|
||||
} else {
|
||||
err = createService(client, service, waitFlags, out)
|
||||
err = createService(client, service, waitFlags, out, targetFlag)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -134,34 +139,34 @@ func NewServiceCreateCommand(p *commands.KnParams) *cobra.Command {
|
|||
},
|
||||
}
|
||||
commands.AddNamespaceFlags(serviceCreateCommand.Flags(), false)
|
||||
commands.AddGitOpsFlags(serviceCreateCommand.Flags())
|
||||
editFlags.AddCreateFlags(serviceCreateCommand)
|
||||
waitFlags.AddConditionWaitFlags(serviceCreateCommand, commands.WaitDefaultTimeout, "create", "service", "ready")
|
||||
return serviceCreateCommand
|
||||
}
|
||||
|
||||
func createService(client clientservingv1.KnServingClient, service *servingv1.Service, waitFlags commands.WaitFlags, out io.Writer) error {
|
||||
func createService(client clientservingv1.KnServingClient, service *servingv1.Service, waitFlags commands.WaitFlags, out io.Writer, targetFlag string) error {
|
||||
err := client.CreateService(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return waitIfRequested(client, service.Name, waitFlags, "Creating", "created", out)
|
||||
return waitIfRequested(client, waitFlags, service.Name, "Creating", "created", targetFlag, out)
|
||||
}
|
||||
|
||||
func replaceService(client clientservingv1.KnServingClient, service *servingv1.Service, waitFlags commands.WaitFlags, out io.Writer) error {
|
||||
func replaceService(client clientservingv1.KnServingClient, service *servingv1.Service, waitFlags commands.WaitFlags, out io.Writer, targetFlag string) error {
|
||||
err := prepareAndUpdateService(client, service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return waitIfRequested(client, service.Name, waitFlags, "Replacing", "replaced", out)
|
||||
return waitIfRequested(client, waitFlags, service.Name, "Replacing", "replaced", targetFlag, out)
|
||||
}
|
||||
|
||||
func waitIfRequested(client clientservingv1.KnServingClient, serviceName string, waitFlags commands.WaitFlags, verbDoing string, verbDone string, out io.Writer) error {
|
||||
if !waitFlags.Wait {
|
||||
func waitIfRequested(client clientservingv1.KnServingClient, waitFlags commands.WaitFlags, serviceName, verbDoing, verbDone, targetFlag string, out io.Writer) error {
|
||||
if !waitFlags.Wait || targetFlag != "" {
|
||||
fmt.Fprintf(out, "Service '%s' %s in namespace '%s'.\n", serviceName, verbDone, client.Namespace())
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "%s service '%s' in namespace '%s':\n", verbDoing, serviceName, client.Namespace())
|
||||
return waitForServiceToGetReady(client, serviceName, waitFlags.TimeoutInSeconds, verbDone, out)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,12 @@ func NewServiceDeleteCommand(p *commands.KnParams) *cobra.Command {
|
|||
kn service delete svc2 -n ns1
|
||||
|
||||
# Delete all services in 'ns1' namespace
|
||||
kn service delete --all -n ns1`,
|
||||
kn service delete --all -n ns1
|
||||
|
||||
# Delete the services in offline mode instead of kubernetes cluster
|
||||
kn service delete test -n test-ns --target=/user/knfiles
|
||||
kn service delete test --target=/user/knfiles/test.yaml
|
||||
kn service delete test --target=/user/knfiles/test.json`,
|
||||
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
all, err := cmd.Flags().GetBool("all")
|
||||
|
|
@ -62,7 +67,7 @@ func NewServiceDeleteCommand(p *commands.KnParams) *cobra.Command {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := p.NewServingClient(namespace)
|
||||
client, err := newServingClient(p, namespace, cmd.Flag("target").Value.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -100,6 +105,7 @@ func NewServiceDeleteCommand(p *commands.KnParams) *cobra.Command {
|
|||
flags := serviceDeleteCommand.Flags()
|
||||
flags.Bool("all", false, "Delete all services in a namespace.")
|
||||
commands.AddNamespaceFlags(serviceDeleteCommand.Flags(), false)
|
||||
commands.AddGitOpsFlags(serviceDeleteCommand.Flags())
|
||||
waitFlags.AddConditionWaitFlags(serviceDeleteCommand, commands.WaitDefaultTimeout, "delete", "service", "deleted")
|
||||
return serviceDeleteCommand
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,12 @@ var describe_example = `
|
|||
kn service describe svc -o yaml
|
||||
|
||||
# Print only service URL
|
||||
kn service describe svc -o url`
|
||||
kn service describe svc -o url
|
||||
|
||||
# Describe the services in offline mode instead of kubernetes cluster
|
||||
kn service describe test -n test-ns --target=/user/knfiles
|
||||
kn service describe test --target=/user/knfiles/test.yaml
|
||||
kn service describe test --target=/user/knfiles/test.json`
|
||||
|
||||
// NewServiceDescribeCommand returns a new command for describing a service.
|
||||
func NewServiceDescribeCommand(p *commands.KnParams) *cobra.Command {
|
||||
|
|
@ -102,7 +107,7 @@ func NewServiceDescribeCommand(p *commands.KnParams) *cobra.Command {
|
|||
return err
|
||||
}
|
||||
|
||||
client, err := p.NewServingClient(namespace)
|
||||
client, err := newServingClient(p, namespace, cmd.Flag("target").Value.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -141,6 +146,7 @@ func NewServiceDescribeCommand(p *commands.KnParams) *cobra.Command {
|
|||
}
|
||||
flags := command.Flags()
|
||||
commands.AddNamespaceFlags(flags, false)
|
||||
commands.AddGitOpsFlags(flags)
|
||||
flags.BoolP("verbose", "v", false, "More output.")
|
||||
machineReadablePrintFlags.AddFlags(command)
|
||||
command.Flag("output").Usage = fmt.Sprintf("Output format. One of: %s.", strings.Join(append(machineReadablePrintFlags.AllowedFormats(), "url"), "|"))
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ func importWithOwnerRef(client clientservingv1.KnServingClient, filename string,
|
|||
}
|
||||
}
|
||||
|
||||
err = waitIfRequested(client, serviceName, waitFlags, "Importing", "imported", out)
|
||||
err = waitIfRequested(client, waitFlags, serviceName, "Importing", "imported", "", out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,13 +42,20 @@ func NewServiceListCommand(p *commands.KnParams) *cobra.Command {
|
|||
kn service list -o json
|
||||
|
||||
# List service 'web'
|
||||
kn service list web`,
|
||||
kn service list web
|
||||
|
||||
# List the services in offline mode instead of kubernetes cluster
|
||||
kn service list --target=/user/knfiles
|
||||
kn service list --target=/user/knfiles/test.json
|
||||
kn service list --target=/user/knfiles/test.yaml
|
||||
kn service list -n test-ns --target=/user/knfiles`,
|
||||
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
namespace, err := p.GetNamespace(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := p.NewServingClient(namespace)
|
||||
client, err := newServingClient(p, namespace, cmd.Flag("target").Value.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -81,6 +88,7 @@ func NewServiceListCommand(p *commands.KnParams) *cobra.Command {
|
|||
},
|
||||
}
|
||||
commands.AddNamespaceFlags(serviceListCommand.Flags(), true)
|
||||
commands.AddGitOpsFlags(serviceListCommand.Flags())
|
||||
serviceListFlags.AddFlags(serviceListCommand)
|
||||
return serviceListCommand
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,3 +74,10 @@ func showUrl(client clientservingv1.KnServingClient, serviceName string, origina
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newServingClient(p *commands.KnParams, namespace, dir string) (clientservingv1.KnServingClient, error) {
|
||||
if dir != "" {
|
||||
return p.NewGitopsServingClient(namespace, dir)
|
||||
}
|
||||
return p.NewServingClient(namespace)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,12 @@ var updateExample = `
|
|||
kn service update svc --untag testing --tag @latest=staging
|
||||
|
||||
# Add tag 'test' to echo-v3 revision with 10% traffic and rest to latest ready revision of service
|
||||
kn service update svc --tag echo-v3=test --traffic test=10,@latest=90`
|
||||
kn service update svc --tag echo-v3=test --traffic test=10,@latest=90
|
||||
|
||||
# Update the service in offline mode instead of kubernetes cluster
|
||||
kn service update gitopstest -n test-ns --env KEY1=VALUE1 --target=/user/knfiles
|
||||
kn service update gitopstest --env KEY1=VALUE1 --target=/user/knfiles/test.yaml
|
||||
kn service update gitopstest --env KEY1=VALUE1 --target=/user/knfiles/test.json`
|
||||
|
||||
func NewServiceUpdateCommand(p *commands.KnParams) *cobra.Command {
|
||||
var editFlags ConfigurationEditFlags
|
||||
|
|
@ -67,8 +72,8 @@ func NewServiceUpdateCommand(p *commands.KnParams) *cobra.Command {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := p.NewServingClient(namespace)
|
||||
targetFlag := cmd.Flag("target").Value.String()
|
||||
client, err := newServingClient(p, namespace, targetFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -109,7 +114,7 @@ func NewServiceUpdateCommand(p *commands.KnParams) *cobra.Command {
|
|||
}
|
||||
|
||||
out := cmd.OutOrStdout()
|
||||
if waitFlags.Wait {
|
||||
if waitFlags.Wait && targetFlag == "" {
|
||||
fmt.Fprintf(out, "Updating Service '%s' in namespace '%s':\n", args[0], namespace)
|
||||
fmt.Fprintln(out, "")
|
||||
err := waitForService(client, name, out, waitFlags.TimeoutInSeconds)
|
||||
|
|
@ -131,6 +136,7 @@ func NewServiceUpdateCommand(p *commands.KnParams) *cobra.Command {
|
|||
}
|
||||
|
||||
commands.AddNamespaceFlags(serviceUpdateCommand.Flags(), false)
|
||||
commands.AddGitOpsFlags(serviceUpdateCommand.Flags())
|
||||
editFlags.AddUpdateFlags(serviceUpdateCommand)
|
||||
waitFlags.AddConditionWaitFlags(serviceUpdateCommand, commands.WaitDefaultTimeout, "update", "service", "ready")
|
||||
trafficFlags.Add(serviceUpdateCommand)
|
||||
|
|
|
|||
|
|
@ -40,14 +40,15 @@ import (
|
|||
|
||||
// KnParams for creating commands. Useful for inserting mocks for testing.
|
||||
type KnParams struct {
|
||||
Output io.Writer
|
||||
KubeCfgPath string
|
||||
ClientConfig clientcmd.ClientConfig
|
||||
NewServingClient func(namespace string) (clientservingv1.KnServingClient, error)
|
||||
NewSourcesClient func(namespace string) (v1alpha2.KnSourcesClient, error)
|
||||
NewEventingClient func(namespace string) (clienteventingv1beta1.KnEventingClient, error)
|
||||
NewMessagingClient func(namespace string) (clientmessagingv1beta1.KnMessagingClient, error)
|
||||
NewDynamicClient func(namespace string) (clientdynamic.KnDynamicClient, error)
|
||||
Output io.Writer
|
||||
KubeCfgPath string
|
||||
ClientConfig clientcmd.ClientConfig
|
||||
NewServingClient func(namespace string) (clientservingv1.KnServingClient, error)
|
||||
NewGitopsServingClient func(namespace string, dir string) (clientservingv1.KnServingClient, error)
|
||||
NewSourcesClient func(namespace string) (v1alpha2.KnSourcesClient, error)
|
||||
NewEventingClient func(namespace string) (clienteventingv1beta1.KnEventingClient, error)
|
||||
NewMessagingClient func(namespace string) (clientmessagingv1beta1.KnMessagingClient, error)
|
||||
NewDynamicClient func(namespace string) (clientdynamic.KnDynamicClient, error)
|
||||
|
||||
// General global options
|
||||
LogHTTP bool
|
||||
|
|
@ -61,6 +62,10 @@ func (params *KnParams) Initialize() {
|
|||
params.NewServingClient = params.newServingClient
|
||||
}
|
||||
|
||||
if params.NewGitopsServingClient == nil {
|
||||
params.NewGitopsServingClient = params.newGitopsServingClient
|
||||
}
|
||||
|
||||
if params.NewSourcesClient == nil {
|
||||
params.NewSourcesClient = params.newSourcesClient
|
||||
}
|
||||
|
|
@ -84,10 +89,17 @@ func (params *KnParams) newServingClient(namespace string) (clientservingv1.KnSe
|
|||
return nil, err
|
||||
}
|
||||
|
||||
client, _ := servingv1client.NewForConfig(restConfig)
|
||||
client, err := servingv1client.NewForConfig(restConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return clientservingv1.NewKnServingClient(client, namespace), nil
|
||||
}
|
||||
|
||||
func (params *KnParams) newGitopsServingClient(namespace string, dir string) (clientservingv1.KnServingClient, error) {
|
||||
return clientservingv1.NewKnServingGitOpsClient(namespace, dir), nil
|
||||
}
|
||||
|
||||
func (params *KnParams) newSourcesClient(namespace string) (v1alpha2.KnSourcesClient, error) {
|
||||
restConfig, err := params.RestConfig()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,219 @@
|
|||
// 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 v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
|
||||
"knative.dev/client/pkg/wait"
|
||||
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
ksvcKind = "ksvc"
|
||||
)
|
||||
|
||||
// knServingGitOpsClient - kn service client
|
||||
// to work on a local repo instead of a remote cluster
|
||||
type knServingGitOpsClient struct {
|
||||
dir string
|
||||
namespace string
|
||||
fileMode bool
|
||||
fileFormat string
|
||||
KnServingClient
|
||||
}
|
||||
|
||||
// NewKnServingGitOpsClient returns an instance of the
|
||||
// kn service gitops client
|
||||
func NewKnServingGitOpsClient(namespace, dir string) KnServingClient {
|
||||
mode, format := getFileModeAndType(dir)
|
||||
return &knServingGitOpsClient{
|
||||
dir: dir,
|
||||
namespace: namespace,
|
||||
fileMode: mode,
|
||||
fileFormat: format,
|
||||
}
|
||||
}
|
||||
|
||||
func (cl *knServingGitOpsClient) getKsvcFilePath(name string) string {
|
||||
if cl.fileMode {
|
||||
return cl.dir
|
||||
}
|
||||
return filepath.Join(cl.dir, cl.namespace, ksvcKind, name+".yaml")
|
||||
}
|
||||
|
||||
func getFileModeAndType(dir string) (bool, string) {
|
||||
switch {
|
||||
case strings.HasSuffix(dir, ".yaml"):
|
||||
return true, "yaml"
|
||||
case strings.HasSuffix(dir, ".yml"):
|
||||
return true, "yaml"
|
||||
case strings.HasSuffix(dir, ".json"):
|
||||
return true, "json"
|
||||
}
|
||||
return false, "yaml"
|
||||
}
|
||||
|
||||
// Namespace returns the namespace
|
||||
func (cl *knServingGitOpsClient) Namespace() string {
|
||||
return cl.namespace
|
||||
}
|
||||
|
||||
// GetService returns the knative service for the name
|
||||
func (cl *knServingGitOpsClient) GetService(name string) (*servingv1.Service, error) {
|
||||
return readServiceFromFile(cl.getKsvcFilePath(name), name)
|
||||
}
|
||||
|
||||
// ListServices lists the services in the path provided
|
||||
func (cl *knServingGitOpsClient) ListServices(config ...ListConfig) (*servingv1.ServiceList, error) {
|
||||
svcs, err := cl.listServicesFromDirectory()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typeMeta := metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "List",
|
||||
}
|
||||
serviceList := &servingv1.ServiceList{
|
||||
TypeMeta: typeMeta,
|
||||
Items: svcs,
|
||||
}
|
||||
return serviceList, nil
|
||||
}
|
||||
|
||||
func (cl *knServingGitOpsClient) listServicesFromDirectory() ([]servingv1.Service, error) {
|
||||
if cl.fileMode {
|
||||
svc, err := readServiceFromFile(cl.dir, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []servingv1.Service{*svc}, nil
|
||||
}
|
||||
var services []servingv1.Service
|
||||
root := cl.dir
|
||||
if cl.namespace != "" {
|
||||
root = filepath.Join(cl.dir, cl.namespace)
|
||||
}
|
||||
if _, err := os.Stat(root); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
switch {
|
||||
// skip if dir is not ksvc
|
||||
case info.IsDir():
|
||||
return nil
|
||||
|
||||
// skip non yaml files
|
||||
case !strings.HasSuffix(info.Name(), ".yaml"):
|
||||
return nil
|
||||
|
||||
// skip non ksvc dir
|
||||
case !strings.Contains(path, ksvcKind):
|
||||
return filepath.SkipDir
|
||||
|
||||
default:
|
||||
svc, err := readServiceFromFile(path, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
services = append(services, *svc)
|
||||
return nil
|
||||
}
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// CreateService saves the knative service spec in
|
||||
// yaml format in the local path provided
|
||||
func (cl *knServingGitOpsClient) CreateService(service *servingv1.Service) error {
|
||||
updateServingGvk(service)
|
||||
if cl.fileMode {
|
||||
return writeFile(service, cl.dir, cl.fileFormat)
|
||||
}
|
||||
//check if dir exist
|
||||
if _, err := os.Stat(cl.dir); os.IsNotExist(err) {
|
||||
return fmt.Errorf("directory '%s' not present, please create the directory and try again", cl.dir)
|
||||
}
|
||||
return writeFile(service, cl.getKsvcFilePath(service.ObjectMeta.Name), cl.fileFormat)
|
||||
}
|
||||
|
||||
func writeFile(obj runtime.Object, fp, format string) error {
|
||||
if _, err := os.Stat(fp); os.IsNotExist(err) {
|
||||
os.MkdirAll(filepath.Dir(fp), 0755)
|
||||
}
|
||||
w, err := os.Create(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
yamlPrinter, err := genericclioptions.NewJSONYamlPrintFlags().ToPrinter(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return yamlPrinter.PrintObj(obj, w)
|
||||
}
|
||||
|
||||
// UpdateService updates the service in
|
||||
// the local directory
|
||||
func (cl *knServingGitOpsClient) UpdateService(service *servingv1.Service) error {
|
||||
// check if file exist
|
||||
if _, err := cl.GetService(service.ObjectMeta.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
// replace file
|
||||
return cl.CreateService(service)
|
||||
}
|
||||
|
||||
// UpdateServiceWithRetry updates the service in the local directory
|
||||
func (cl *knServingGitOpsClient) UpdateServiceWithRetry(name string, updateFunc ServiceUpdateFunc, nrRetries int) error {
|
||||
return updateServiceWithRetry(cl, name, updateFunc, nrRetries)
|
||||
}
|
||||
|
||||
// DeleteService removes the file from the local file system
|
||||
func (cl *knServingGitOpsClient) DeleteService(serviceName string, timeout time.Duration) error {
|
||||
return os.Remove(cl.getKsvcFilePath(serviceName))
|
||||
}
|
||||
|
||||
// WaitForService always returns success for this client
|
||||
func (cl *knServingGitOpsClient) WaitForService(name string, timeout time.Duration, msgCallback wait.MessageCallback) (error, time.Duration) {
|
||||
return nil, 1 * time.Second
|
||||
}
|
||||
|
||||
func readServiceFromFile(fileKey, name string) (*servingv1.Service, error) {
|
||||
var svc servingv1.Service
|
||||
file, err := os.Open(fileKey)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, apierrors.NewNotFound(servingv1.Resource("services"), name)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
decoder := yaml.NewYAMLOrJSONDecoder(file, 512)
|
||||
if err := decoder.Decode(&svc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &svc, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
// 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 v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gotest.tools/assert"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
servingtest "knative.dev/serving/pkg/testing/v1"
|
||||
|
||||
libtest "knative.dev/client/lib/test"
|
||||
"knative.dev/pkg/ptr"
|
||||
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||
)
|
||||
|
||||
func TestGitOpsOperations(t *testing.T) {
|
||||
c1TempDir, err := ioutil.TempDir("", "kn-files-cluster1")
|
||||
assert.NilError(t, err)
|
||||
c2TempDir, err := ioutil.TempDir("", "kn-files-cluster2")
|
||||
assert.NilError(t, err)
|
||||
defer os.RemoveAll(c1TempDir)
|
||||
defer os.RemoveAll(c2TempDir)
|
||||
// create clients
|
||||
fooclient := NewKnServingGitOpsClient("foo-ns", c1TempDir)
|
||||
bazclient := NewKnServingGitOpsClient("baz-ns", c1TempDir)
|
||||
globalclient := NewKnServingGitOpsClient("", c1TempDir)
|
||||
diffClusterClient := NewKnServingGitOpsClient("", "tmp")
|
||||
|
||||
// set up test services
|
||||
fooSvc := libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()))
|
||||
barSvc := libtest.BuildServiceWithOptions("bar", servingtest.WithConfigSpec(buildConfiguration()))
|
||||
fooUpdateSvc := libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()), servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}))
|
||||
|
||||
fooserviceList := getServiceList([]servingv1.Service{*barSvc, *fooSvc})
|
||||
allServices := getServiceList([]servingv1.Service{*barSvc, *barSvc, *fooSvc})
|
||||
|
||||
t.Run("get file path for foo service in foo namespace", func(t *testing.T) {
|
||||
fp := fooclient.(*knServingGitOpsClient).getKsvcFilePath("foo")
|
||||
assert.Equal(t, filepath.Join(c1TempDir, "foo-ns/ksvc/foo.yaml"), fp)
|
||||
})
|
||||
t.Run("get namespace for bazclient client", func(t *testing.T) {
|
||||
ns := bazclient.Namespace()
|
||||
assert.Equal(t, "baz-ns", ns)
|
||||
})
|
||||
t.Run("create service foo in foo namespace", func(t *testing.T) {
|
||||
err := fooclient.CreateService(fooSvc)
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
t.Run("wait for foo service in foo namespace", func(t *testing.T) {
|
||||
err, d := fooclient.WaitForService("foo", 5*time.Second, nil)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, 1*time.Second, d)
|
||||
})
|
||||
t.Run("get service foo", func(t *testing.T) {
|
||||
result, err := fooclient.GetService("foo")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, fooSvc, result)
|
||||
})
|
||||
t.Run("create service bar in foo namespace", func(t *testing.T) {
|
||||
err := fooclient.CreateService(barSvc)
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
t.Run("create service bar in baz namespace", func(t *testing.T) {
|
||||
err := bazclient.CreateService(barSvc)
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
t.Run("list services in foo namespace", func(t *testing.T) {
|
||||
result, err := fooclient.ListServices()
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, fooserviceList, result)
|
||||
})
|
||||
t.Run("create service without master directory", func(t *testing.T) {
|
||||
err := diffClusterClient.CreateService(fooSvc)
|
||||
assert.ErrorContains(t, err, "directory 'tmp' not present, please create the directory and try again")
|
||||
})
|
||||
diffClusterClient = NewKnServingGitOpsClient("", c2TempDir)
|
||||
t.Run("create service foo in foo namespace in cluster 2", func(t *testing.T) {
|
||||
err := diffClusterClient.CreateService(fooSvc)
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
t.Run("list services in all namespaces in cluster 1", func(t *testing.T) {
|
||||
result, err := globalclient.ListServices()
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, allServices, result)
|
||||
})
|
||||
t.Run("update service with retry foo", func(t *testing.T) {
|
||||
err := fooclient.UpdateServiceWithRetry("foo", func(svc *servingv1.Service) (*servingv1.Service, error) {
|
||||
return svc, nil
|
||||
}, 1)
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
t.Run("update service foo", func(t *testing.T) {
|
||||
err := fooclient.UpdateService(fooUpdateSvc)
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
t.Run("check updated service foo", func(t *testing.T) {
|
||||
result, err := fooclient.GetService("foo")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, fooUpdateSvc, result)
|
||||
})
|
||||
t.Run("delete service foo", func(t *testing.T) {
|
||||
err := fooclient.DeleteService("foo", 5*time.Second)
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
t.Run("get service foo", func(t *testing.T) {
|
||||
_, err := fooclient.GetService("foo")
|
||||
assert.ErrorType(t, err, apierrors.IsNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGitOpsSingleFile(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("", "singlefile")
|
||||
assert.NilError(t, err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
// create clients
|
||||
fooclient := NewKnServingGitOpsClient("", filepath.Join(tmpDir, "test.yaml"))
|
||||
barclient := NewKnServingGitOpsClient("", filepath.Join(tmpDir, "test.yml"))
|
||||
bazclient := NewKnServingGitOpsClient("", filepath.Join(tmpDir, "test.json"))
|
||||
|
||||
// set up test services
|
||||
testSvc := libtest.BuildServiceWithOptions("test", servingtest.WithConfigSpec(buildConfiguration()))
|
||||
updateSvc := libtest.BuildServiceWithOptions("test", servingtest.WithConfigSpec(buildConfiguration()), servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}))
|
||||
|
||||
svcList := getServiceList([]servingv1.Service{*updateSvc})
|
||||
|
||||
t.Run("get file path for fooclient", func(t *testing.T) {
|
||||
fp := fooclient.(*knServingGitOpsClient).getKsvcFilePath("test")
|
||||
assert.Equal(t, filepath.Join(tmpDir, "test.yaml"), fp)
|
||||
})
|
||||
t.Run("get namespace for fooclient", func(t *testing.T) {
|
||||
ns := fooclient.Namespace()
|
||||
assert.Equal(t, "", ns)
|
||||
})
|
||||
t.Run("create service in single file mode in different formats", func(t *testing.T) {
|
||||
err := fooclient.CreateService(testSvc)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = barclient.CreateService(testSvc)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = bazclient.CreateService(testSvc)
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
t.Run("retrieve services", func(t *testing.T) {
|
||||
result, err := fooclient.GetService("test")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, testSvc, result)
|
||||
|
||||
result, err = barclient.GetService("test")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, testSvc, result)
|
||||
|
||||
result, err = bazclient.GetService("test")
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, testSvc, result)
|
||||
})
|
||||
t.Run("update service foo", func(t *testing.T) {
|
||||
err := fooclient.UpdateService(updateSvc)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = barclient.UpdateService(updateSvc)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = bazclient.UpdateService(updateSvc)
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
t.Run("list services", func(t *testing.T) {
|
||||
result, err := fooclient.ListServices()
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, svcList, result)
|
||||
|
||||
result, err = barclient.ListServices()
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, svcList, result)
|
||||
|
||||
result, err = bazclient.ListServices()
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, svcList, result)
|
||||
})
|
||||
t.Run("delete service foo", func(t *testing.T) {
|
||||
err := fooclient.DeleteService("test", 5*time.Second)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = barclient.DeleteService("test", 5*time.Second)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = bazclient.DeleteService("test", 5*time.Second)
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
t.Run("get service foo", func(t *testing.T) {
|
||||
_, err := fooclient.GetService("test")
|
||||
assert.ErrorType(t, err, apierrors.IsNotFound)
|
||||
|
||||
_, err = barclient.GetService("test")
|
||||
assert.ErrorType(t, err, apierrors.IsNotFound)
|
||||
|
||||
_, err = bazclient.GetService("test")
|
||||
assert.ErrorType(t, err, apierrors.IsNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func getServiceList(services []servingv1.Service) *servingv1.ServiceList {
|
||||
return &servingv1.ServiceList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "List",
|
||||
},
|
||||
Items: services,
|
||||
}
|
||||
}
|
||||
|
||||
func buildConfiguration() *servingv1.ConfigurationSpec {
|
||||
c := &servingv1.Configuration{
|
||||
Spec: servingv1.ConfigurationSpec{
|
||||
Template: servingv1.RevisionTemplateSpec{
|
||||
Spec: *revisionSpec.DeepCopy(),
|
||||
},
|
||||
},
|
||||
}
|
||||
c.SetDefaults(context.Background())
|
||||
return &c.Spec
|
||||
}
|
||||
|
||||
var revisionSpec = servingv1.RevisionSpec{
|
||||
PodSpec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{{
|
||||
Image: "busybox",
|
||||
}},
|
||||
EnableServiceLinks: ptr.Bool(false),
|
||||
},
|
||||
TimeoutSeconds: ptr.Int64(300),
|
||||
}
|
||||
Loading…
Reference in New Issue