crossplane

Форк
0
/
install.go 
223 строки · 7.2 Кб
1
/*
2
Copyright 2020 The Crossplane Authors.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package xpkg
18

19
import (
20
	"context"
21
	"fmt"
22
	"net/http"
23
	"time"
24

25
	"github.com/alecthomas/kong"
26
	"github.com/google/go-containerregistry/pkg/name"
27
	corev1 "k8s.io/api/core/v1"
28
	kerrors "k8s.io/apimachinery/pkg/api/errors"
29
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30
	"k8s.io/apimachinery/pkg/runtime"
31
	"k8s.io/apimachinery/pkg/util/wait"
32
	ctrl "sigs.k8s.io/controller-runtime"
33
	"sigs.k8s.io/controller-runtime/pkg/client"
34

35
	"github.com/crossplane/crossplane-runtime/pkg/errors"
36
	"github.com/crossplane/crossplane-runtime/pkg/logging"
37

38
	v1 "github.com/crossplane/crossplane/apis/pkg/v1"
39
	"github.com/crossplane/crossplane/apis/pkg/v1beta1"
40
	"github.com/crossplane/crossplane/internal/version"
41
	"github.com/crossplane/crossplane/internal/xpkg"
42

43
	// Load all the auth plugins for the cloud providers.
44
	_ "k8s.io/client-go/plugin/pkg/client/auth"
45
)
46

47
const (
48
	errPkgIdentifier = "invalid package image identifier"
49
	errKubeConfig    = "failed to get kubeconfig"
50
	errKubeClient    = "failed to create kube client"
51
)
52

53
// installCmd installs a package.
54
type installCmd struct {
55
	// Arguments.
56
	Kind    string `arg:"" help:"The kind of package to install. One of \"provider\", \"configuration\", or \"function\"." enum:"provider,configuration,function"`
57
	Package string `arg:"" help:"The package to install."`
58
	Name    string `arg:""  optional:"" help:"The name of the new package in the Crossplane API. Derived from the package repository and tag by default."`
59

60
	// Flags. Keep sorted alphabetically.
61
	RuntimeConfig        string        `placeholder:"NAME" help:"Install the package with a runtime configuration (for example a DeploymentRuntimeConfig)."`
62
	ManualActivation     bool          `short:"m" help:"Require the new package's first revision to be manually activated."`
63
	PackagePullSecrets   []string      `placeholder:"NAME" help:"A comma-separated list of secrets the package manager should use to pull the package from the registry."`
64
	RevisionHistoryLimit int64         `short:"r" placeholder:"LIMIT" help:"How many package revisions may exist before the oldest revisions are deleted."`
65
	Wait                 time.Duration `short:"w" default:"0s" help:"How long to wait for the package to install before returning. The command does not wait by default. Returns an error if the timeout is exceeded."`
66
}
67

68
func (c *installCmd) Help() string {
69
	return `
70
This command installs a package in a Crossplane control plane. It uses
71
~/.kube/config to connect to the control plane. You can override this using the
72
KUBECONFIG environment variable.
73

74
Examples:
75

76
  # Wait 1 minute for the package to finish installing before returning.
77
  crossplane xpkg install provider upbound/provider-aws-eks:v0.41.0 --wait=1m
78

79
  # Install a Function named function-eg that uses a runtime config named
80
  # customconfig.
81
  crossplane xpkg install function upbound/function-example:v0.1.4 function-eg \
82
    --runtime-config=customconfig
83
`
84
}
85

86
// Run the package install cmd.
87
func (c *installCmd) Run(k *kong.Context, logger logging.Logger) error { //nolint:gocyclo // TODO(negz): Can anything be broken out here?
88
	pkgName := c.Name
89
	if pkgName == "" {
90
		ref, err := name.ParseReference(c.Package, name.WithDefaultRegistry(xpkg.DefaultRegistry))
91
		if err != nil {
92
			logger.Debug(errPkgIdentifier, "error", err)
93
			return errors.Wrap(err, errPkgIdentifier)
94
		}
95
		pkgName = xpkg.ToDNSLabel(ref.Context().RepositoryStr())
96
	}
97

98
	logger = logger.WithValues(
99
		"kind", c.Kind,
100
		"ref", c.Package,
101
		"name", pkgName,
102
	)
103

104
	rap := v1.AutomaticActivation
105
	if c.ManualActivation {
106
		rap = v1.ManualActivation
107
	}
108
	secrets := make([]corev1.LocalObjectReference, len(c.PackagePullSecrets))
109
	for i, s := range c.PackagePullSecrets {
110
		secrets[i] = corev1.LocalObjectReference{
111
			Name: s,
112
		}
113
	}
114

115
	spec := v1.PackageSpec{
116
		Package:                  c.Package,
117
		RevisionActivationPolicy: &rap,
118
		RevisionHistoryLimit:     &c.RevisionHistoryLimit,
119
		PackagePullSecrets:       secrets,
120
	}
121

122
	var pkg v1.Package
123
	switch c.Kind {
124
	case "provider":
125
		pkg = &v1.Provider{
126
			ObjectMeta: metav1.ObjectMeta{Name: pkgName},
127
			Spec:       v1.ProviderSpec{PackageSpec: spec},
128
		}
129
	case "configuration":
130
		pkg = &v1.Configuration{
131
			ObjectMeta: metav1.ObjectMeta{Name: pkgName},
132
			Spec:       v1.ConfigurationSpec{PackageSpec: spec},
133
		}
134
	case "function":
135
		pkg = &v1beta1.Function{
136
			ObjectMeta: metav1.ObjectMeta{Name: pkgName},
137
			Spec:       v1beta1.FunctionSpec{PackageSpec: spec},
138
		}
139
	default:
140
		// The enum struct tag on the Kind field should make this impossible.
141
		return errors.Errorf("unsupported package kind %q", c.Kind)
142
	}
143

144
	if c.RuntimeConfig != "" {
145
		rpkg, ok := pkg.(v1.PackageWithRuntime)
146
		if !ok {
147
			return errors.Errorf("package kind %T does not support runtime configuration", pkg)
148
		}
149
		rpkg.SetRuntimeConfigRef(&v1.RuntimeConfigReference{Name: c.RuntimeConfig})
150
	}
151

152
	cfg, err := ctrl.GetConfig()
153
	if err != nil {
154
		return errors.Wrap(err, errKubeConfig)
155
	}
156
	logger.Debug("Found kubeconfig")
157

158
	s := runtime.NewScheme()
159
	_ = v1.AddToScheme(s)
160
	_ = v1beta1.AddToScheme(s)
161

162
	kube, err := client.New(cfg, client.Options{Scheme: s})
163
	if err != nil {
164
		return errors.Wrap(err, errKubeClient)
165
	}
166
	logger.Debug("Created kubernetes client")
167

168
	timeout := 10 * time.Second
169
	if c.Wait > 0 {
170
		timeout = c.Wait
171
	}
172
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
173
	defer cancel()
174

175
	if err := kube.Create(ctx, pkg); err != nil {
176
		return errors.Wrap(warnIfNotFound(err), "cannot create package")
177
	}
178

179
	if c.Wait > 0 {
180
		// Poll every 2 seconds to see whether the package is ready.
181
		logger.Debug("Waiting for package to be ready", "timeout", timeout)
182
		go wait.UntilWithContext(ctx, func(ctx context.Context) {
183
			if err := kube.Get(ctx, client.ObjectKeyFromObject(pkg), pkg); err != nil {
184
				logger.Debug("Cannot get package", "error", err)
185
				return
186
			}
187

188
			// Our package is ready, cancel the context to stop our wait loop.
189
			if pkg.GetCondition(v1.TypeHealthy).Status == corev1.ConditionTrue {
190
				logger.Debug("Package is ready")
191
				cancel()
192
				return
193
			}
194

195
			logger.Debug("Package is not yet ready")
196
		}, 2*time.Second)
197

198
		<-ctx.Done()
199

200
		if err := ctx.Err(); errors.Is(err, context.DeadlineExceeded) {
201
			return errors.Wrap(err, "Package did not become ready")
202
		}
203

204
	}
205

206
	_, err = fmt.Fprintf(k.Stdout, "%s/%s created\n", c.Kind, pkg.GetName())
207
	return err
208
}
209

210
// TODO(negz): What is this trying to do? My guess is its trying to handle the
211
// case where the CRD of the package kind isn't installed. Perhaps we could be
212
// clearer in the error?
213

214
func warnIfNotFound(err error) error {
215
	serr := &kerrors.StatusError{}
216
	if !errors.As(err, &serr) {
217
		return err
218
	}
219
	if serr.ErrStatus.Code != http.StatusNotFound {
220
		return err
221
	}
222
	return errors.WithMessagef(err, "crossplane CLI (version %s) might be out of date", version.New().GetVersionString())
223
}
224

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.