Reorganise migrate command into a its own struct & add --force flag
Signed-off-by: James Munnelly <jmunnelly@apple.com>
This commit is contained in:
parent
0ca2db69a3
commit
438d74be15
@ -2,7 +2,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["migrate.go"],
|
||||
srcs = [
|
||||
"command.go",
|
||||
"migrator.go",
|
||||
],
|
||||
importpath = "github.com/jetstack/cert-manager/cmd/ctl/pkg/upgrade/migrate",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
|
||||
123
cmd/ctl/pkg/upgrade/migrate/command.go
Normal file
123
cmd/ctl/pkg/upgrade/migrate/command.go
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
Copyright 2022 The cert-manager 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 migrate
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
apiextinstall "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/jetstack/cert-manager/cmd/ctl/pkg/build"
|
||||
"github.com/jetstack/cert-manager/cmd/ctl/pkg/factory"
|
||||
acmeinstall "github.com/jetstack/cert-manager/internal/apis/acme/install"
|
||||
cminstall "github.com/jetstack/cert-manager/internal/apis/certmanager/install"
|
||||
)
|
||||
|
||||
var (
|
||||
long = templates.LongDesc(i18n.T(`
|
||||
Ensures resources in your Kubernetes cluster are persisted in the v1 API version.
|
||||
|
||||
This must be run prior to upgrading to ensure your cluster is ready to upgrade to cert-manager v1.7 and beyond.
|
||||
|
||||
This command must be run with a cluster running cert-manager v1.0 or greater.`))
|
||||
|
||||
example = templates.Examples(i18n.T(build.WithTemplate(`
|
||||
# Check the cert-manager installation is ready to be upgraded to v1.7
|
||||
{{.BuildName}} upgrade migrate
|
||||
`)))
|
||||
)
|
||||
|
||||
// Options is a struct to support renew command
|
||||
type Options struct {
|
||||
genericclioptions.IOStreams
|
||||
*factory.Factory
|
||||
|
||||
client client.Client
|
||||
force bool
|
||||
}
|
||||
|
||||
// NewOptions returns initialized Options
|
||||
func NewOptions(ioStreams genericclioptions.IOStreams) *Options {
|
||||
return &Options{
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdMigrate returns a cobra command for updating resources in an apiserver
|
||||
// to force a new storage version to be used.
|
||||
func NewCmdMigrate(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := NewOptions(ioStreams)
|
||||
cmd := &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Migrate all existing persisted cert-manager resources to the v1 API version",
|
||||
Long: long,
|
||||
Example: example,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Validate(args))
|
||||
cmdutil.CheckErr(o.Complete())
|
||||
cmdutil.CheckErr(o.Run(ctx, args))
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&o.force, "force", "f", o.force, ""+
|
||||
"If true, all resources will be read and written regardless of the 'status.storedVersions' on the CRD resource. "+
|
||||
"Use this mode if you have previously manually modified the 'status.storedVersions' field on CRD resources.")
|
||||
|
||||
o.Factory = factory.New(ctx, cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Validate validates the provided options
|
||||
func (o *Options) Validate(_ []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Complete takes the command arguments and factory and infers any remaining options.
|
||||
func (o *Options) Complete() error {
|
||||
var err error
|
||||
scheme := runtime.NewScheme()
|
||||
apiextinstall.Install(scheme)
|
||||
cminstall.Install(scheme)
|
||||
acmeinstall.Install(scheme)
|
||||
|
||||
o.client, err = client.New(o.RESTConfig, client.Options{Scheme: scheme})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run executes renew command
|
||||
func (o *Options) Run(ctx context.Context, args []string) error {
|
||||
return NewMigrator(o.client, o.force, o.Out, o.ErrOut).Run(ctx, "v1", []string{
|
||||
"certificates.cert-manager.io",
|
||||
"certificaterequests.cert-manager.io",
|
||||
"issuers.cert-manager.io",
|
||||
"clusterissuers.cert-manager.io",
|
||||
"orders.acme.cert-manager.io",
|
||||
"challenges.acme.cert-manager.io",
|
||||
})
|
||||
}
|
||||
@ -1,282 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 The cert-manager 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 migrate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
apiextinstall "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install"
|
||||
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/jetstack/cert-manager/cmd/ctl/pkg/build"
|
||||
"github.com/jetstack/cert-manager/cmd/ctl/pkg/factory"
|
||||
acmeinstall "github.com/jetstack/cert-manager/internal/apis/acme/install"
|
||||
cminstall "github.com/jetstack/cert-manager/internal/apis/certmanager/install"
|
||||
)
|
||||
|
||||
var (
|
||||
long = templates.LongDesc(i18n.T(`
|
||||
Ensures resources in your Kubernetes cluster are persisted in the v1 API version.
|
||||
|
||||
This must be run prior to upgrading to ensure your cluster is ready to upgrade to cert-manager v1.7 and beyond.
|
||||
|
||||
This command must be run with a cluster running cert-manager v1.0 or greater.`))
|
||||
|
||||
example = templates.Examples(i18n.T(build.WithTemplate(`
|
||||
# Check the cert-manager installation is ready to be upgraded to v1.7
|
||||
{{.BuildName}} upgrade migrate
|
||||
`)))
|
||||
)
|
||||
|
||||
var scheme = runtime.NewScheme()
|
||||
|
||||
func init() {
|
||||
apiextinstall.Install(scheme)
|
||||
cminstall.Install(scheme)
|
||||
acmeinstall.Install(scheme)
|
||||
}
|
||||
|
||||
// Options is a struct to support renew command
|
||||
type Options struct {
|
||||
genericclioptions.IOStreams
|
||||
*factory.Factory
|
||||
|
||||
client client.Client
|
||||
}
|
||||
|
||||
// NewOptions returns initialized Options
|
||||
func NewOptions(ioStreams genericclioptions.IOStreams) *Options {
|
||||
return &Options{
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCmdMigrate returns a cobra command for updating resources in an apiserver
|
||||
// to force a new storage version to be used.
|
||||
func NewCmdMigrate(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
o := NewOptions(ioStreams)
|
||||
cmd := &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Migrate all existing persisted cert-manager resources to the v1 API version",
|
||||
Long: long,
|
||||
Example: example,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Validate(cmd, args))
|
||||
cmdutil.CheckErr(o.Complete())
|
||||
cmdutil.CheckErr(o.Run(ctx, args))
|
||||
},
|
||||
}
|
||||
|
||||
o.Factory = factory.New(ctx, cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Validate validates the provided options
|
||||
func (o *Options) Validate(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Complete takes the command arguments and factory and infers any remaining options.
|
||||
func (o *Options) Complete() error {
|
||||
var err error
|
||||
o.client, err = client.New(o.RESTConfig, client.Options{Scheme: scheme})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run executes renew command
|
||||
func (o *Options) Run(ctx context.Context, args []string) error {
|
||||
// Check all cert-manager CRDs to ensure that they have their
|
||||
// storage version set to v1.
|
||||
allCRDNames := []string{
|
||||
"certificates.cert-manager.io",
|
||||
"certificaterequests.cert-manager.io",
|
||||
"issuers.cert-manager.io",
|
||||
"clusterissuers.cert-manager.io",
|
||||
"orders.acme.cert-manager.io",
|
||||
"challenges.acme.cert-manager.io",
|
||||
}
|
||||
|
||||
fmt.Fprintln(o.Out, "Checking all cert-manager CustomResourceDefinitions have storage version set to 'v1'")
|
||||
allV1, crdsRequiringMigration, err := o.ensureCRDStorageVersionEquals(ctx, "v1", allCRDNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !allV1 {
|
||||
fmt.Fprintln(o.ErrOut, "It looks like you are running a pre-1.0 version of cert-manager. Please upgrade cert-manager to v1.6 before upgrading to v1.7.")
|
||||
return fmt.Errorf("migration failed")
|
||||
}
|
||||
fmt.Fprintln(o.Out, "All CustomResourceDefinitions have 'v1' configured as the storage version.")
|
||||
|
||||
/*
|
||||
fmt.Fprintln(o.Out, "Looking for CRDs that contain resources that require migrating to 'v1'...")
|
||||
crdsRequiringMigration, err := o.discoverCRDsRequiringMigration(ctx, "v1", allCRDNames)
|
||||
if err != nil {
|
||||
fmt.Fprintf(o.ErrOut, "Failed to determine resource types that require migration: %v\n", err)
|
||||
return err
|
||||
}
|
||||
if len(crdsRequiringMigration) == 0 {
|
||||
fmt.Fprintln(o.Out, "Nothing to do. cert-manager CRDs do not have 'status.storedVersions' containing old API versions. You may proceed to upgrade to cert-manager v1.7.")
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
|
||||
fmt.Fprintf(o.Out, "Found %d resource types that require migration:\n", len(crdsRequiringMigration))
|
||||
for _, crd := range crdsRequiringMigration {
|
||||
fmt.Fprintf(o.Out, " - %s\n", crd.Name)
|
||||
}
|
||||
|
||||
for _, crd := range crdsRequiringMigration {
|
||||
if err := o.migrateResourcesForCRD(ctx, crd); err != nil {
|
||||
fmt.Fprintf(o.ErrOut, "Failed to migrate resource: %v\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(o.Out, "Patching CRD resources to set 'status.storedVersions' to 'v1'...")
|
||||
if err := o.patchCRDStoredVersions(ctx, crdsRequiringMigration); err != nil {
|
||||
fmt.Fprintf(o.ErrOut, "Failed to patch 'status.storedVersions' field: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(o.Out, "Successfully migrated all cert-manager resource types. It is now safe to proceed with upgrading to cert-manager v1.7.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Options) ensureCRDStorageVersionEquals(ctx context.Context, vers string, names []string) (bool, []*apiext.CustomResourceDefinition, error) {
|
||||
var crds []*apiext.CustomResourceDefinition
|
||||
for _, crdName := range names {
|
||||
crd := &apiext.CustomResourceDefinition{}
|
||||
if err := o.client.Get(ctx, client.ObjectKey{Name: crdName}, crd); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// Discover the storage version
|
||||
storageVersion := ""
|
||||
for _, v := range crd.Spec.Versions {
|
||||
if v.Storage {
|
||||
storageVersion = v.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if storageVersion != vers {
|
||||
fmt.Fprintf(o.Out, "CustomResourceDefinition object %q has storage version set to %q. You MUST upgrade to cert-manager v1.0-v1.6 before migrating resources for v1.7.\n", crdName, storageVersion)
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
crds = append(crds, crd)
|
||||
}
|
||||
|
||||
return true, crds, nil
|
||||
}
|
||||
|
||||
func (o *Options) discoverCRDsRequiringMigration(ctx context.Context, desiredStorageVersion string, names []string) ([]*apiext.CustomResourceDefinition, error) {
|
||||
var requireMigration []*apiext.CustomResourceDefinition
|
||||
for _, name := range names {
|
||||
crd := &apiext.CustomResourceDefinition{}
|
||||
if err := o.client.Get(ctx, client.ObjectKey{Name: name}, crd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If no versions are stored, there's nothing to migrate.
|
||||
if len(crd.Status.StoredVersions) == 0 {
|
||||
continue
|
||||
}
|
||||
// If more than one entry exists in `storedVersions` OR if the only element in there is not
|
||||
// the desired version, perform a migration.
|
||||
if len(crd.Status.StoredVersions) > 1 || crd.Status.StoredVersions[0] != desiredStorageVersion {
|
||||
requireMigration = append(requireMigration, crd)
|
||||
}
|
||||
}
|
||||
return requireMigration, nil
|
||||
}
|
||||
|
||||
func (o *Options) migrateResourcesForCRD(ctx context.Context, crd *apiext.CustomResourceDefinition) error {
|
||||
startTime := time.Now()
|
||||
fmt.Fprintf(o.Out, "Migrating %q objects in group %q - this may take a while (started at %s)...\n", crd.Spec.Names.Kind, crd.Spec.Group, startTime.Format(time.Stamp))
|
||||
list := &unstructured.UnstructuredList{}
|
||||
list.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: crd.Spec.Group,
|
||||
Version: "v1",
|
||||
Kind: crd.Spec.Names.ListKind,
|
||||
})
|
||||
if err := o.client.List(ctx, list); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(o.Out, " %d resources to migrate\n", len(list.Items))
|
||||
for _, obj := range list.Items {
|
||||
if err := o.client.Update(ctx, &obj); handleUpdateErr(err) != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(o.Out, " Successfully migrated %d %s objects in %s\n", len(list.Items), crd.Spec.Names.Kind, time.Now().Sub(startTime).Round(time.Second))
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleUpdateErr will absorb certain types of errors that we know can be skipped/passed on
|
||||
// during a migration of a particular object.
|
||||
func handleUpdateErr(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// If the resource no longer exists, don't return the error as the object no longer
|
||||
// needs updating to the new API version.
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
// If there was a conflict, another client must have written the object already which
|
||||
// means we don't need to force an update.
|
||||
if apierrors.IsConflict(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *Options) patchCRDStoredVersions(ctx context.Context, crds []*apiext.CustomResourceDefinition) error {
|
||||
for _, crd := range crds {
|
||||
// fetch a fresh copy of the CRD to avoid any conflict errors
|
||||
freshCRD := &apiext.CustomResourceDefinition{}
|
||||
if err := o.client.Get(ctx, client.ObjectKey{Name: crd.Name}, freshCRD); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the `status.storedVersions` field to 'v1'
|
||||
freshCRD.Status.StoredVersions = []string{"v1"}
|
||||
|
||||
if err := o.client.Status().Update(ctx, freshCRD); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
207
cmd/ctl/pkg/upgrade/migrate/migrator.go
Normal file
207
cmd/ctl/pkg/upgrade/migrate/migrator.go
Normal file
@ -0,0 +1,207 @@
|
||||
package migrate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"time"
|
||||
|
||||
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
type Migrator struct {
|
||||
// Client used for API interactions
|
||||
Client client.Client
|
||||
|
||||
// If true, skip checking the 'status.storedVersion' before running the migration.
|
||||
// By default, migration will only be run if the CRD contains storedVersions other
|
||||
// than the desired target version.
|
||||
Force bool
|
||||
|
||||
// Writers to write informational & error messages to
|
||||
Out, ErrOut io.Writer
|
||||
}
|
||||
|
||||
// NewMigrator creates a new migrator with the given API client.
|
||||
// If either of out or errOut are nil, log messages will be discarded.
|
||||
func NewMigrator(client client.Client, force bool, out, errOut io.Writer) *Migrator {
|
||||
if out == nil {
|
||||
out = io.Discard
|
||||
}
|
||||
if errOut == nil {
|
||||
errOut = io.Discard
|
||||
}
|
||||
|
||||
return &Migrator{
|
||||
Client: client,
|
||||
Force: force,
|
||||
Out: out,
|
||||
ErrOut: errOut,
|
||||
}
|
||||
}
|
||||
|
||||
// Run begins the migration of all the named CRDs.
|
||||
// It will attempt to migrate all resources defined as part of these CRDs to the
|
||||
// given 'targetVersion', and after completion will update the `status.storedVersions`
|
||||
// field on the corresponding CRD version to only contain the given targetVersion.
|
||||
func (m *Migrator) Run(ctx context.Context, targetVersion string, names []string) error {
|
||||
fmt.Fprintf(m.Out, "Checking all CustomResourceDefinitions have storage version set to '%s'\n", targetVersion)
|
||||
allV1, allCRDs, err := m.ensureCRDStorageVersionEquals(ctx, targetVersion, names)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !allV1 {
|
||||
fmt.Fprintln(m.ErrOut, "It looks like you are running a pre-1.0 version of cert-manager. Please upgrade cert-manager to v1.6 before upgrading to v1.7.")
|
||||
return fmt.Errorf("preflight checks failed")
|
||||
}
|
||||
fmt.Fprintf(m.Out, "All CustomResourceDefinitions have %q configured as the storage version.\n", targetVersion)
|
||||
|
||||
crdsRequiringMigration := allCRDs
|
||||
if !m.Force {
|
||||
fmt.Fprintln(m.Out, "Looking for CRDs that contain resources that require migrating to 'v1'...")
|
||||
crdsRequiringMigration, err = m.discoverCRDsRequiringMigration(ctx, "v1", names)
|
||||
if err != nil {
|
||||
fmt.Fprintf(m.ErrOut, "Failed to determine resource types that require migration: %v\n", err)
|
||||
return err
|
||||
}
|
||||
if len(crdsRequiringMigration) == 0 {
|
||||
fmt.Fprintln(m.Out, "Nothing to do. cert-manager CRDs do not have 'status.storedVersions' containing old API versions. You may proceed to upgrade to cert-manager v1.7.")
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(m.Out, "Forcing migration of all CRD resources as --force=true")
|
||||
}
|
||||
|
||||
fmt.Fprintf(m.Out, "Found %d resource types that require migration:\n", len(crdsRequiringMigration))
|
||||
for _, crd := range crdsRequiringMigration {
|
||||
fmt.Fprintf(m.Out, " - %s\n", crd.Name)
|
||||
}
|
||||
|
||||
for _, crd := range crdsRequiringMigration {
|
||||
if err := m.migrateResourcesForCRD(ctx, crd); err != nil {
|
||||
fmt.Fprintf(m.ErrOut, "Failed to migrate resource: %v\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(m.Out, "Patching CRD resources to set 'status.storedVersions' to %q...\n", targetVersion)
|
||||
if err := m.patchCRDStoredVersions(ctx, crdsRequiringMigration); err != nil {
|
||||
fmt.Fprintf(m.ErrOut, "Failed to patch 'status.storedVersions' field: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(m.Out, "Successfully migrated all cert-manager resource types. It is now safe to proceed with upgrading to cert-manager v1.7.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) ensureCRDStorageVersionEquals(ctx context.Context, vers string, names []string) (bool, []*apiext.CustomResourceDefinition, error) {
|
||||
var crds []*apiext.CustomResourceDefinition
|
||||
for _, crdName := range names {
|
||||
crd := &apiext.CustomResourceDefinition{}
|
||||
if err := m.Client.Get(ctx, client.ObjectKey{Name: crdName}, crd); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
// Discover the storage version
|
||||
storageVersion := ""
|
||||
for _, v := range crd.Spec.Versions {
|
||||
if v.Storage {
|
||||
storageVersion = v.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if storageVersion != vers {
|
||||
fmt.Fprintf(m.Out, "CustomResourceDefinition object %q has storage version set to %q. You MUST upgrade to cert-manager v1.0-v1.6 before migrating resources for v1.7.\n", crdName, storageVersion)
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
crds = append(crds, crd)
|
||||
}
|
||||
|
||||
return true, crds, nil
|
||||
}
|
||||
|
||||
func (m *Migrator) discoverCRDsRequiringMigration(ctx context.Context, desiredStorageVersion string, names []string) ([]*apiext.CustomResourceDefinition, error) {
|
||||
var requireMigration []*apiext.CustomResourceDefinition
|
||||
for _, name := range names {
|
||||
crd := &apiext.CustomResourceDefinition{}
|
||||
if err := m.Client.Get(ctx, client.ObjectKey{Name: name}, crd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If no versions are stored, there's nothing to migrate.
|
||||
if len(crd.Status.StoredVersions) == 0 {
|
||||
continue
|
||||
}
|
||||
// If more than one entry exists in `storedVersions` OR if the only element in there is not
|
||||
// the desired version, perform a migration.
|
||||
if len(crd.Status.StoredVersions) > 1 || crd.Status.StoredVersions[0] != desiredStorageVersion {
|
||||
requireMigration = append(requireMigration, crd)
|
||||
}
|
||||
}
|
||||
return requireMigration, nil
|
||||
}
|
||||
|
||||
func (m *Migrator) migrateResourcesForCRD(ctx context.Context, crd *apiext.CustomResourceDefinition) error {
|
||||
startTime := time.Now()
|
||||
fmt.Fprintf(m.Out, "Migrating %q objects in group %q - this may take a while (started at %s)...\n", crd.Spec.Names.Kind, crd.Spec.Group, startTime.Format(time.Stamp))
|
||||
list := &unstructured.UnstructuredList{}
|
||||
list.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: crd.Spec.Group,
|
||||
Version: "v1",
|
||||
Kind: crd.Spec.Names.ListKind,
|
||||
})
|
||||
if err := m.Client.List(ctx, list); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(m.Out, " %d resources to migrate\n", len(list.Items))
|
||||
for _, obj := range list.Items {
|
||||
if err := m.Client.Update(ctx, &obj); handleUpdateErr(err) != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(m.Out, " Successfully migrated %d %s objects in %s\n", len(list.Items), crd.Spec.Names.Kind, time.Now().Sub(startTime).Round(time.Second))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) patchCRDStoredVersions(ctx context.Context, crds []*apiext.CustomResourceDefinition) error {
|
||||
for _, crd := range crds {
|
||||
// fetch a fresh copy of the CRD to avoid any conflict errors
|
||||
freshCRD := &apiext.CustomResourceDefinition{}
|
||||
if err := m.Client.Get(ctx, client.ObjectKey{Name: crd.Name}, freshCRD); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the `status.storedVersions` field to 'v1'
|
||||
freshCRD.Status.StoredVersions = []string{"v1"}
|
||||
|
||||
if err := m.Client.Status().Update(ctx, freshCRD); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleUpdateErr will absorb certain types of errors that we know can be skipped/passed on
|
||||
// during a migration of a particular object.
|
||||
func handleUpdateErr(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// If the resource no longer exists, don't return the error as the object no longer
|
||||
// needs updating to the new API version.
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
// If there was a conflict, another client must have written the object already which
|
||||
// means we don't need to force an update.
|
||||
if apierrors.IsConflict(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user