// Copyright © 2020 The Knative Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package service import ( "errors" "fmt" "io" "os" "github.com/spf13/cobra" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/client-go/util/retry" clientv1alpha1 "knative.dev/client/pkg/apis/client/v1alpha1" "knative.dev/client/pkg/kn/commands" clientservingv1 "knative.dev/client/pkg/serving/v1" "knative.dev/pkg/kmeta" servingv1 "knative.dev/serving/pkg/apis/serving/v1" ) // NewServiceImportCommand returns a new command for importing a service. func NewServiceImportCommand(p *commands.KnParams) *cobra.Command { var waitFlags commands.WaitFlags command := &cobra.Command{ Use: "import FILENAME", Short: "Import a service and its revisions (experimental)", Example: ` # Import a service from YAML file kn service import /path/to/file.yaml # Import a service from JSON file kn service import /path/to/file.json`, RunE: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return errors.New("'kn service import' requires filename of import file as single argument") } filename := args[0] namespace, err := p.GetNamespace(cmd) if err != nil { return err } client, err := p.NewServingClient(namespace) if err != nil { return err } return importWithOwnerRef(client, filename, cmd.OutOrStdout(), waitFlags) }, } flags := command.Flags() commands.AddNamespaceFlags(flags, false) waitFlags.AddConditionWaitFlags(command, commands.WaitDefaultTimeout, "import", "service", "ready") return command } func importWithOwnerRef(client clientservingv1.KnServingClient, filename string, out io.Writer, waitFlags commands.WaitFlags) error { var export clientv1alpha1.Export file, err := os.Open(filename) if err != nil { return err } decoder := yaml.NewYAMLOrJSONDecoder(file, 512) err = decoder.Decode(&export) if err != nil { return err } if export.Spec.Service.Name == "" { return fmt.Errorf("provided import file doesn't contain service name, please note that only kn's custom export format is supported") } serviceName := export.Spec.Service.Name // Return error if service already exists svcExists, err := serviceExists(client, serviceName) if err != nil { return err } if svcExists { return fmt.Errorf("cannot import service '%s' in namespace '%s' because the service already exists", serviceName, client.Namespace()) } err = client.CreateService(&export.Spec.Service) if err != nil { return err } // Retrieve current Configuration to be use in OwnerReference currentConf, err := getConfigurationWithRetry(client, serviceName) if err != nil { return err } // Create revision with current Configuration's OwnerReference if len(export.Spec.Revisions) > 0 { for _, r := range export.Spec.Revisions { tmp := r.DeepCopy() // OwnerRef ensures that Revisions are recognized by controller tmp.OwnerReferences = []metav1.OwnerReference{*kmeta.NewControllerRef(currentConf)} if err = client.CreateRevision(tmp); err != nil { return err } } } err = waitIfRequested(client, serviceName, waitFlags, "Importing", "imported", out) if err != nil { return err } return err } func getConfigurationWithRetry(client clientservingv1.KnServingClient, name string) (*servingv1.Configuration, error) { var conf *servingv1.Configuration var err error err = retry.OnError(retry.DefaultBackoff, func(err error) bool { return apierrors.IsNotFound(err) }, func() error { conf, err = client.GetConfiguration(name) return err }) return conf, err }