diff --git a/go.mod b/go.mod index a39ec1d079..56450ddf08 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/openshift/api v0.0.0-20200326160804-ecb9283fe820 github.com/openshift/build-machinery-go v0.0.0-20200211121458-5e3d6e570160 github.com/openshift/client-go v0.0.0-20200326155132-2a6cd50aedd0 - github.com/openshift/library-go v0.0.0-20200331191807-3eb0070c91ed + github.com/openshift/library-go v0.0.0-20200422120251-a5cb46356745 github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum index 5da0eccd0d..318075e361 100644 --- a/go.sum +++ b/go.sum @@ -287,8 +287,8 @@ github.com/openshift/build-machinery-go v0.0.0-20200211121458-5e3d6e570160 h1:V4 github.com/openshift/build-machinery-go v0.0.0-20200211121458-5e3d6e570160/go.mod h1:1CkcsT3aVebzRBzVTSbiKSkJMsC/CASqxesfqEMfJEc= github.com/openshift/client-go v0.0.0-20200326155132-2a6cd50aedd0 h1:kMiuiZXH1GdfbiMwsuAQOqGaMxlo9NCUk0wT4XAdfNM= github.com/openshift/client-go v0.0.0-20200326155132-2a6cd50aedd0/go.mod h1:uUQ4LClRO+fg5MF/P6QxjMCb1C9f7Oh4RKepftDnEJE= -github.com/openshift/library-go v0.0.0-20200331191807-3eb0070c91ed h1:7dAzYWRjXQDPVBtkoaLLLeYXE/sRv0fXnACxDxGBX/E= -github.com/openshift/library-go v0.0.0-20200331191807-3eb0070c91ed/go.mod h1:CfydoH0B+RYs22uQZQ36A1mz5m5zhucpMGh8t5s71v4= +github.com/openshift/library-go v0.0.0-20200422120251-a5cb46356745 h1:/AnLD1CD5zkXTcacqh9FFUMO0rK0S86vVX6b1JyLHGg= +github.com/openshift/library-go v0.0.0-20200422120251-a5cb46356745/go.mod h1:CfydoH0B+RYs22uQZQ36A1mz5m5zhucpMGh8t5s71v4= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= diff --git a/pkg/operator2/deployment.go b/pkg/operator2/deployment.go index 225e5e1d86..c66207ec94 100644 --- a/pkg/operator2/deployment.go +++ b/pkg/operator2/deployment.go @@ -23,6 +23,7 @@ func defaultDeployment( operatorConfig *operatorv1.Authentication, syncData *configSyncData, proxyConfig *configv1.Proxy, + bootstrapUserExists bool, resourceVersions ...string, ) *appsv1.Deployment { @@ -47,6 +48,11 @@ func defaultDeployment( } deployment.Spec.Template.Annotations[deploymentVersionHashKey] = rvsHashStr + // Ensure a rollout when the bootstrap user goes away + if bootstrapUserExists { + deployment.Spec.Template.Annotations["operator.openshift.io/bootstrap-user-exists"] = "true" + } + templateSpec := &deployment.Spec.Template.Spec container := &templateSpec.Containers[0] diff --git a/pkg/operator2/operator.go b/pkg/operator2/operator.go index 58890bf987..8872e8737c 100644 --- a/pkg/operator2/operator.go +++ b/pkg/operator2/operator.go @@ -321,43 +321,36 @@ func (c *authOperator) handleSync(ctx context.Context, operatorConfig *operatorv proxyConfig := c.handleProxyConfig(ctx) resourceVersions = append(resourceVersions, "proxy:"+proxyConfig.Name+":"+proxyConfig.ResourceVersion) - operatorDeployment, err := c.deployments.Deployments("openshift-authentication-operator").Get(ctx, "authentication-operator", metav1.GetOptions{}) - if err != nil { - return err - } - // prefix the RV to make it clear where it came from since each resource can be from different etcd - resourceVersions = append(resourceVersions, "deployments:"+operatorDeployment.Name+":"+operatorDeployment.ResourceVersion) - configResourceVersions, err := c.handleConfigResourceVersions(ctx) if err != nil { return err } resourceVersions = append(resourceVersions, configResourceVersions...) + // Determine whether the bootstrap user has been deleted so that + // detail can be used in computing the deployment. + if c.bootstrapUserChangeRollOut { + if userExists, err := c.bootstrapUserDataGetter.IsEnabled(); err != nil { + klog.Warningf("Unable to determine the state of bootstrap user: %v", err) + } else { + c.bootstrapUserChangeRollOut = userExists + } + } + // deployment, have RV of all resources expectedDeployment := defaultDeployment( operatorConfig, syncData, proxyConfig, + c.bootstrapUserChangeRollOut, resourceVersions..., ) - // redeploy on operatorConfig.spec changes or when bootstrap user is deleted - forceRollOut := operatorConfig.Generation != operatorConfig.Status.ObservedGeneration - if c.bootstrapUserChangeRollOut { - if userExists, err := c.bootstrapUserDataGetter.IsEnabled(); err != nil { - klog.Warningf("Unable to determine the state of bootstrap user: %v", err) - } else if !userExists { - forceRollOut = true - c.bootstrapUserChangeRollOut = false - } - } deployment, _, err := resourceapply.ApplyDeployment( c.deployments, c.recorder, expectedDeployment, resourcemerge.ExpectedDeploymentGeneration(expectedDeployment, operatorConfig.Status.Generations), - forceRollOut, ) if err != nil { return fmt.Errorf("failed applying deployment for the integrated OAuth server: %v", err) diff --git a/vendor/github.com/openshift/library-go/pkg/controller/controllercmd/builder.go b/vendor/github.com/openshift/library-go/pkg/controller/controllercmd/builder.go index 1865b026b4..850b8ecc9d 100644 --- a/vendor/github.com/openshift/library-go/pkg/controller/controllercmd/builder.go +++ b/vendor/github.com/openshift/library-go/pkg/controller/controllercmd/builder.go @@ -5,12 +5,16 @@ import ( "fmt" "io/ioutil" "os" + "strings" "sync" "time" + "k8s.io/component-base/metrics" + "k8s.io/component-base/metrics/legacyregistry" "k8s.io/klog" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/version" genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/apiserver/pkg/server/healthz" "k8s.io/client-go/kubernetes" @@ -72,6 +76,8 @@ type ControllerBuilder struct { authorizationConfig *operatorv1alpha1.DelegatedAuthorization healthChecks []healthz.HealthChecker + versionInfo *version.Info + // nonZeroExitFn takes a function that exit the process with non-zero code. // This stub exists for unit test where we can check if the graceful termination work properly. // Default function will klog.Warning(args) and os.Exit(1). @@ -134,6 +140,12 @@ func (b *ControllerBuilder) WithLeaderElection(leaderElection configv1.LeaderEle return b } +// WithVersion accepts a getting that provide binary version information that is used to report build_info information to prometheus +func (b *ControllerBuilder) WithVersion(info version.Info) *ControllerBuilder { + b.versionInfo = &info + return b +} + // WithServer adds a server that provides metrics and healthz func (b *ControllerBuilder) WithServer(servingInfo configv1.HTTPServingInfo, authenticationConfig operatorv1alpha1.DelegatedAuthentication, authorizationConfig operatorv1alpha1.DelegatedAuthorization) *ControllerBuilder { b.servingInfo = servingInfo.DeepCopy() @@ -194,6 +206,23 @@ func (b *ControllerBuilder) Run(ctx context.Context, config *unstructured.Unstru } } + // report the binary version metrics to prometheus + if b.versionInfo != nil { + buildInfo := metrics.NewGaugeVec( + &metrics.GaugeOpts{ + Name: strings.Replace(namespace, "-", "_", -1) + "_build_info", + Help: "A metric with a constant '1' value labeled by major, minor, git version, git commit, git tree state, build date, Go version, " + + "and compiler from which " + b.componentName + " was built, and platform on which it is running.", + StabilityLevel: metrics.ALPHA, + }, + []string{"major", "minor", "gitVersion", "gitCommit", "gitTreeState", "buildDate", "goVersion", "compiler", "platform"}, + ) + legacyregistry.MustRegister(buildInfo) + buildInfo.WithLabelValues(b.versionInfo.Major, b.versionInfo.Minor, b.versionInfo.GitVersion, b.versionInfo.GitCommit, b.versionInfo.GitTreeState, b.versionInfo.BuildDate, b.versionInfo.GoVersion, + b.versionInfo.Compiler, b.versionInfo.Platform).Set(1) + klog.Infof("%s version %s-%s", b.componentName, b.versionInfo.GitVersion, b.versionInfo.GitCommit) + } + kubeConfig := "" if b.kubeAPIServerConfigFile != nil { kubeConfig = *b.kubeAPIServerConfigFile diff --git a/vendor/github.com/openshift/library-go/pkg/controller/controllercmd/cmd.go b/vendor/github.com/openshift/library-go/pkg/controller/controllercmd/cmd.go index c517bec083..1e603efe16 100644 --- a/vendor/github.com/openshift/library-go/pkg/controller/controllercmd/cmd.go +++ b/vendor/github.com/openshift/library-go/pkg/controller/controllercmd/cmd.go @@ -1,7 +1,6 @@ package controllercmd import ( - "bytes" "context" "fmt" "io/ioutil" @@ -198,6 +197,11 @@ func (c *ControllerCommandConfig) AddDefaultRotationToConfig(config *operatorv1a config.ServingInfo.KeyFile = filepath.Join(certDir, "tls.key") } else { klog.Warningf("Using insecure, self-signed certificates") + // If we generate our own certificates, then we want to specify empty content to avoid a starting race. This way, + // if any change comes in, we will properly restart + startingFileContent[filepath.Join(certDir, "tls.crt")] = []byte{} + startingFileContent[filepath.Join(certDir, "tls.key")] = []byte{} + temporaryCertDir, err := ioutil.TempDir("", "serving-cert-") if err != nil { return nil, nil, err @@ -213,11 +217,10 @@ func (c *ControllerCommandConfig) AddDefaultRotationToConfig(config *operatorv1a if err != nil { return nil, nil, err } - certDir = temporaryCertDir // force the values to be set to where we are writing the certs - config.ServingInfo.CertFile = filepath.Join(certDir, "tls.crt") - config.ServingInfo.KeyFile = filepath.Join(certDir, "tls.key") + config.ServingInfo.CertFile = filepath.Join(temporaryCertDir, "tls.crt") + config.ServingInfo.KeyFile = filepath.Join(temporaryCertDir, "tls.key") // nothing can trust this, so we don't really care about hostnames servingCert, err := ca.MakeServerCert(sets.NewString("localhost"), 30) if err != nil { @@ -226,16 +229,6 @@ func (c *ControllerCommandConfig) AddDefaultRotationToConfig(config *operatorv1a if err := servingCert.WriteCertConfigFile(config.ServingInfo.CertFile, config.ServingInfo.KeyFile); err != nil { return nil, nil, err } - crtContent := &bytes.Buffer{} - keyContent := &bytes.Buffer{} - if err := servingCert.WriteCertConfig(crtContent, keyContent); err != nil { - return nil, nil, err - } - - // If we generate our own certificates, then we want to specify empty content to avoid a starting race. This way, - // if any change comes in, we will properly restart - startingFileContent[filepath.Join(certDir, "tls.crt")] = crtContent.Bytes() - startingFileContent[filepath.Join(certDir, "tls.key")] = keyContent.Bytes() } } return startingFileContent, observedFiles, nil @@ -273,6 +266,7 @@ func (c *ControllerCommandConfig) StartController(ctx context.Context) error { WithKubeConfigFile(c.basicFlags.KubeConfigFile, nil). WithComponentNamespace(c.basicFlags.Namespace). WithLeaderElection(config.LeaderElection, c.basicFlags.Namespace, c.componentName+"-lock"). + WithVersion(c.version). WithRestartOnChange(exitOnChangeReactorCh, startingFileContent, observedFiles...) if !c.DisableServing { diff --git a/vendor/github.com/openshift/library-go/pkg/controller/fileobserver/observer_polling.go b/vendor/github.com/openshift/library-go/pkg/controller/fileobserver/observer_polling.go index 4867a51ebe..d444469529 100644 --- a/vendor/github.com/openshift/library-go/pkg/controller/fileobserver/observer_polling.go +++ b/vendor/github.com/openshift/library-go/pkg/controller/fileobserver/observer_polling.go @@ -57,7 +57,11 @@ func (o *pollingObserver) AddReactor(reaction ReactorFn, startingFileContent map // in case the file does not exists but empty string is specified as initial content, without this // the content will be hashed and reaction will trigger as if the content changed. if len(startingContent) == 0 { - o.files[f] = fileHashAndState{exists: true} + var fileExists bool + if fileExists, err = isFile(f); err != nil { + panic(fmt.Sprintf("unexpected error while adding reactor for %#v: %v", files, err)) + } + o.files[f] = fileHashAndState{exists: fileExists} o.reactors[f] = append(o.reactors[f], reaction) continue } @@ -165,22 +169,15 @@ type fileHashAndState struct { func calculateFileHash(path string) (fileHashAndState, error) { result := fileHashAndState{} - stat, err := os.Stat(path) - if err != nil { + if exists, err := isFile(path); !exists || err != nil { return result, err } - // this is fatal - if stat.IsDir() { - return result, fmt.Errorf("you can watch only files, %s is a directory", path) - } - f, err := os.Open(path) if err != nil { return result, err } defer f.Close() - // at this point we know for sure the file exists and we can read its content even if that content is empty result.exists = true @@ -207,3 +204,20 @@ func calculateHash(content io.Reader) (string, bool, error) { } return hex.EncodeToString(hasher.Sum(nil)), false, nil } + +func isFile(path string) (bool, error) { + stat, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + + // this is fatal + if stat.IsDir() { + return false, fmt.Errorf("%s is a directory", path) + } + + return true, nil +} diff --git a/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/apps.go b/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/apps.go index 253b17ef1d..5377688188 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/apps.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/apps.go @@ -2,6 +2,9 @@ package resourceapply import ( "context" + "crypto/sha256" + "encoding/json" + "fmt" "k8s.io/klog" @@ -15,13 +18,103 @@ import ( "github.com/openshift/library-go/pkg/operator/resource/resourcemerge" ) -// ApplyDeployment merges objectmeta and requires matching generation. It returns the final Object, whether any change as made, and an error -func ApplyDeployment(client appsclientv1.DeploymentsGetter, recorder events.Recorder, required *appsv1.Deployment, expectedGeneration int64, +// The Apply methods in this file ensure that a resource is created or updated to match +// the form provided by the caller. +// +// If the resource does not yet exist, it will be created. +// +// If the resource exists, the metadata of the required resource will be merged with the +// existing resource and an update will be performed if the spec and metadata differ between +// the required and existing resources. To be reliable, the input of the required spec from +// the operator should be stable. It does not need to set all fields, since some fields are +// defaulted server-side. Detection of spec drift from intent by other actors is determined +// by generation, not by spec comparison. +// +// To ensure an update in response to state external to the resource spec, the caller should +// set an annotation representing that external state e.g. +// +// `myoperator.openshift.io/config-resource-version: ` +// +// An update will be performed if: +// +// - The required resource metadata differs from that of the existing resource. +// - The difference will be detected by comparing the name, namespace, labels and +// annotations of the 2 resources. +// +// - The generation expected by the operator differs from generation of the existing +// resource. +// - This is the likely result of an actor other than the operator updating a resource +// managed by the operator. +// +// - The spec of the required resource differs from the spec of the existing resource. +// - The difference will be detected via metadata comparison since the hash of the +// resource's spec will be set as an annotation prior to comparison. + +const specHashAnnotation = "operator.openshift.io/spec-hash" + +// SetSpecHashAnnotation computes the hash of the provided spec and sets an annotation of the +// hash on the provided ObjectMeta. This method is used internally by Apply methods, and +// is exposed to support testing with fake clients that need to know the mutated form of the +// resource resulting from an Apply call. +func SetSpecHashAnnotation(objMeta *metav1.ObjectMeta, spec interface{}) error { + jsonBytes, err := json.Marshal(spec) + if err != nil { + return err + } + specHash := fmt.Sprintf("%x", sha256.Sum256(jsonBytes)) + if objMeta.Annotations == nil { + objMeta.Annotations = map[string]string{} + } + objMeta.Annotations[specHashAnnotation] = specHash + return nil +} + +// ApplyDeployment ensures the form of the specified deployment is present in the API. If it +// does not exist, it will be created. If it does exist, the metadata of the required +// deployment will be merged with the existing deployment and an update performed if the +// deployment spec and metadata differ from the previously required spec and metadata. For +// further detail, check the top-level comment. +// +// NOTE: The previous implementation of this method was renamed to +// ApplyDeploymentWithForce. If are reading this in response to a compile error due to the +// change in signature, you have the following options: +// +// - Update the calling code to rely on the spec comparison provided by the new +// implementation. If the code in question was specifying the force parameter to ensure +// rollout in response to changes in resources external to the deployment, it will need to be +// revised to set that external state as an annotation e.g. +// +// myoperator.openshift.io/my-resource: +// +// - Update the call to use ApplyDeploymentWithForce. This is available as a temporary measure +// but the method is deprecated and will be removed in 4.6. +func ApplyDeployment(client appsclientv1.DeploymentsGetter, recorder events.Recorder, + requiredOriginal *appsv1.Deployment, expectedGeneration int64) (*appsv1.Deployment, bool, error) { + + required := requiredOriginal.DeepCopy() + err := SetSpecHashAnnotation(&required.ObjectMeta, required.Spec) + if err != nil { + return nil, false, err + } + + return ApplyDeploymentWithForce(client, recorder, required, expectedGeneration, false) +} + +// ApplyDeploymentWithForce merges objectmeta and requires matching generation. It returns the final Object, whether any change as made, and an error. +// +// DEPRECATED - This method will be removed in 4.6 and callers will need to migrate to ApplyDeployment before then. +func ApplyDeploymentWithForce(client appsclientv1.DeploymentsGetter, recorder events.Recorder, requiredOriginal *appsv1.Deployment, expectedGeneration int64, forceRollout bool) (*appsv1.Deployment, bool, error) { + + required := requiredOriginal.DeepCopy() if required.Annotations == nil { required.Annotations = map[string]string{} } - required.Annotations["operator.openshift.io/pull-spec"] = required.Spec.Template.Spec.Containers[0].Image + if _, ok := required.Annotations[specHashAnnotation]; !ok { + // If the spec hash annotation is not present, the caller expects the + // pull-spec annotation to be applied. + required.Annotations["operator.openshift.io/pull-spec"] = required.Spec.Template.Spec.Containers[0].Image + } existing, err := client.Deployments(required.Namespace).Get(context.TODO(), required.Name, metav1.GetOptions{}) if apierrors.IsNotFound(err) { actual, err := client.Deployments(required.Namespace).Create(context.TODO(), required, metav1.CreateOptions{}) @@ -66,12 +159,49 @@ func ApplyDeployment(client appsclientv1.DeploymentsGetter, recorder events.Reco return actual, true, err } -// ApplyDaemonSet merges objectmeta and requires matching generation. It returns the final Object, whether any change as made, and an error -func ApplyDaemonSet(client appsclientv1.DaemonSetsGetter, recorder events.Recorder, required *appsv1.DaemonSet, expectedGeneration int64, forceRollout bool) (*appsv1.DaemonSet, bool, error) { +// ApplyDaemonSet ensures the form of the specified daemonset is present in the API. If it +// does not exist, it will be created. If it does exist, the metadata of the required +// daemonset will be merged with the existing daemonset and an update performed if the +// daemonset spec and metadata differ from the previously required spec and metadata. For +// further detail, check the top-level comment. +// +// NOTE: The previous implementation of this method was renamed to ApplyDaemonSetWithForce. If +// are reading this in response to a compile error due to the change in signature, you have +// the following options: +// +// - Update the calling code to rely on the spec comparison provided by the new +// implementation. If the code in question was specifying the force parameter to ensure +// rollout in response to changes in resources external to the daemonset, it will need to be +// revised to set that external state as an annotation e.g. +// +// myoperator.openshift.io/my-resource: +// +// - Update the call to use ApplyDaemonSetWithForce. This is available as a temporary measure +// but the method is deprecated and will be removed in 4.6. +func ApplyDaemonSet(client appsclientv1.DaemonSetsGetter, recorder events.Recorder, + requiredOriginal *appsv1.DaemonSet, expectedGeneration int64) (*appsv1.DaemonSet, bool, error) { + + required := requiredOriginal.DeepCopy() + err := SetSpecHashAnnotation(&required.ObjectMeta, required.Spec) + if err != nil { + return nil, false, err + } + + return ApplyDaemonSetWithForce(client, recorder, required, expectedGeneration, false) +} + +// ApplyDaemonSetWithForce merges objectmeta and requires matching generation. It returns the final Object, whether any change as made, and an error +// DEPRECATED - This method will be removed in 4.6 and callers will need to migrate to ApplyDaemonSet before then. +func ApplyDaemonSetWithForce(client appsclientv1.DaemonSetsGetter, recorder events.Recorder, requiredOriginal *appsv1.DaemonSet, expectedGeneration int64, forceRollout bool) (*appsv1.DaemonSet, bool, error) { + required := requiredOriginal.DeepCopy() if required.Annotations == nil { required.Annotations = map[string]string{} } - required.Annotations["operator.openshift.io/pull-spec"] = required.Spec.Template.Spec.Containers[0].Image + if _, ok := required.Annotations[specHashAnnotation]; !ok { + // If the spec hash annotation is not present, the caller expects the + // pull-spec annotation to be applied. + required.Annotations["operator.openshift.io/pull-spec"] = required.Spec.Template.Spec.Containers[0].Image + } existing, err := client.DaemonSets(required.Namespace).Get(context.TODO(), required.Name, metav1.GetOptions{}) if apierrors.IsNotFound(err) { actual, err := client.DaemonSets(required.Namespace).Create(context.TODO(), required, metav1.CreateOptions{}) diff --git a/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/core.go b/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/core.go index 5da1ed9d06..09fa9f1e60 100644 --- a/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/core.go +++ b/vendor/github.com/openshift/library-go/pkg/operator/resource/resourceapply/core.go @@ -273,6 +273,11 @@ func ApplySecret(client coreclientv1.SecretsGetter, recorder events.Recorder, re existingCopy.Type = required.Type + // Server defaults some values and we need to do it as well or it will never equal. + if existingCopy.Type == "" { + existingCopy.Type = corev1.SecretTypeOpaque + } + if equality.Semantic.DeepEqual(existingCopy, existing) { return existing, false, nil } @@ -280,14 +285,23 @@ func ApplySecret(client coreclientv1.SecretsGetter, recorder events.Recorder, re if klog.V(4) { klog.Infof("Secret %s/%s changes: %v", required.Namespace, required.Name, JSONPatchSecretNoError(existing, existingCopy)) } - actual, err := client.Secrets(required.Namespace).Update(context.TODO(), existingCopy, metav1.UpdateOptions{}) - reportUpdateEvent(recorder, existingCopy, err) - if err == nil { - return actual, true, err - } - if !strings.Contains(err.Error(), "field is immutable") { - return actual, true, err + var actual *corev1.Secret + /* + * Kubernetes validation silently hides failures to update secret type. + * https://github.com/kubernetes/kubernetes/blob/98e65951dccfd40d3b4f31949c2ab8df5912d93e/pkg/apis/core/validation/validation.go#L5048 + * We need to explicitly opt for delete+create in that case. + */ + if existingCopy.Type == existing.Type { + actual, err = client.Secrets(required.Namespace).Update(context.TODO(), existingCopy, metav1.UpdateOptions{}) + reportUpdateEvent(recorder, existingCopy, err) + + if err == nil { + return actual, true, err + } + if !strings.Contains(err.Error(), "field is immutable") { + return actual, true, err + } } // if the field was immutable on a secret, we're going to be stuck until we delete it. Try to delete and then create @@ -348,6 +362,24 @@ func SyncSecret(client coreclientv1.SecretsGetter, recorder events.Recorder, sou case err != nil: return nil, false, err default: + if source.Type == corev1.SecretTypeServiceAccountToken { + + // Make sure the token is already present, otherwise we have to wait before creating the target + if len(source.Data[corev1.ServiceAccountTokenKey]) == 0 { + return nil, false, fmt.Errorf("secret %s/%s doesn't have a token yet", source.Namespace, source.Name) + } + + if source.Annotations != nil { + // When syncing a service account token we have to remove the SA annotation to disable injection into copies + delete(source.Annotations, corev1.ServiceAccountNameKey) + // To make it clean, remove the dormant annotations as well + delete(source.Annotations, corev1.ServiceAccountUIDKey) + } + + // SecretTypeServiceAccountToken implies required fields and injection which we do not want in copies + source.Type = corev1.SecretTypeOpaque + } + source.Namespace = targetNamespace source.Name = targetName source.ResourceVersion = "" diff --git a/vendor/modules.txt b/vendor/modules.txt index 94c1838754..b01c2b75ee 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -178,7 +178,7 @@ github.com/openshift/client-go/route/informers/externalversions/internalinterfac github.com/openshift/client-go/route/informers/externalversions/route github.com/openshift/client-go/route/informers/externalversions/route/v1 github.com/openshift/client-go/route/listers/route/v1 -# github.com/openshift/library-go v0.0.0-20200331191807-3eb0070c91ed +# github.com/openshift/library-go v0.0.0-20200422120251-a5cb46356745 github.com/openshift/library-go/pkg/authentication/bootstrapauthenticator github.com/openshift/library-go/pkg/config/client github.com/openshift/library-go/pkg/config/clusteroperator/v1helpers