client/pkg/commands/service/import.go

145 lines
4.2 KiB
Go

// 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 (
"context"
"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/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 (Beta)
kn service import /path/to/file.yaml
# Import a service from JSON file (Beta)
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(cmd.Context(), client, filename, cmd.OutOrStdout(), waitFlags)
},
}
flags := command.Flags()
commands.AddNamespaceFlags(flags, false)
waitFlags.AddConditionWaitFlags(command, commands.WaitDefaultTimeout, "import", "service", "ready")
return command
}
func importWithOwnerRef(ctx context.Context, 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(ctx, 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(ctx, &export.Spec.Service)
if err != nil {
return err
}
// Retrieve current Configuration to be use in OwnerReference
currentConf, err := getConfigurationWithRetry(ctx, 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(ctx, tmp); err != nil {
return err
}
}
}
err = waitIfRequested(ctx, client, waitFlags, serviceName, "Importing", "imported", "", out)
if err != nil {
return err
}
return err
}
func getConfigurationWithRetry(ctx context.Context, 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(ctx, name)
return err
})
return conf, err
}