Skip to content

Commit e9c2485

Browse files
Merge pull request #2009 from tchap/atomic-certsync
OCPBUGS-33013: certsyncpod+installerpod: Swap secret/cm directories atomically
2 parents 277736d + f2df141 commit e9c2485

File tree

5 files changed

+970
-137
lines changed

5 files changed

+970
-137
lines changed

pkg/operator/staticpod/certsyncpod/certsync_controller.go

Lines changed: 69 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package certsyncpod
22

33
import (
44
"context"
5+
"fmt"
56
"os"
67
"path/filepath"
78
"reflect"
89

10+
"github.com/openshift/library-go/pkg/operator/staticpod/internal/atomicdir/types"
911
apierrors "k8s.io/apimachinery/pkg/api/errors"
1012
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1113
utilerrors "k8s.io/apimachinery/pkg/util/errors"
@@ -17,17 +19,19 @@ import (
1719

1820
"github.com/openshift/library-go/pkg/controller/factory"
1921
"github.com/openshift/library-go/pkg/operator/events"
20-
"github.com/openshift/library-go/pkg/operator/staticpod"
2122
"github.com/openshift/library-go/pkg/operator/staticpod/controller/installer"
23+
"github.com/openshift/library-go/pkg/operator/staticpod/internal/atomicdir"
2224
)
2325

26+
const stagingDirUID = "cert-sync"
27+
2428
type CertSyncController struct {
2529
destinationDir string
2630
namespace string
2731
configMaps []installer.UnrevisionedResource
2832
secrets []installer.UnrevisionedResource
2933

30-
configmapGetter corev1interface.ConfigMapInterface
34+
configMapGetter corev1interface.ConfigMapInterface
3135
configMapLister v1.ConfigMapLister
3236
secretGetter corev1interface.SecretInterface
3337
secretLister v1.SecretLister
@@ -42,10 +46,10 @@ func NewCertSyncController(targetDir, targetNamespace string, configmaps, secret
4246
secrets: secrets,
4347
eventRecorder: eventRecorder.WithComponentSuffix("cert-sync-controller"),
4448

45-
configmapGetter: kubeClient.CoreV1().ConfigMaps(targetNamespace),
49+
configMapGetter: kubeClient.CoreV1().ConfigMaps(targetNamespace),
4650
configMapLister: informers.Core().V1().ConfigMaps().Lister(),
47-
secretLister: informers.Core().V1().Secrets().Lister(),
4851
secretGetter: kubeClient.CoreV1().Secrets(targetNamespace),
52+
secretLister: informers.Core().V1().Secrets().Lister(),
4953
}
5054

5155
return factory.New().
@@ -60,15 +64,12 @@ func NewCertSyncController(targetDir, targetNamespace string, configmaps, secret
6064
)
6165
}
6266

63-
func getConfigMapDir(targetDir, configMapName string) string {
64-
return filepath.Join(targetDir, "configmaps", configMapName)
65-
}
66-
67-
func getSecretDir(targetDir, secretName string) string {
68-
return filepath.Join(targetDir, "secrets", secretName)
69-
}
70-
7167
func (c *CertSyncController) sync(ctx context.Context, syncCtx factory.SyncContext) error {
68+
if err := os.RemoveAll(getStagingDir(c.destinationDir)); err != nil {
69+
c.eventRecorder.Warningf("CertificateUpdateFailed", fmt.Sprintf("Failed to prune staging directory: %v", err))
70+
return err
71+
}
72+
7273
errors := []error{}
7374

7475
klog.Infof("Syncing configmaps: %v", c.configMaps)
@@ -80,15 +81,15 @@ func (c *CertSyncController) sync(ctx context.Context, syncCtx factory.SyncConte
8081
continue
8182

8283
case apierrors.IsNotFound(err) && cm.Optional:
83-
configMapFile := getConfigMapDir(c.destinationDir, cm.Name)
84+
configMapFile := getConfigMapTargetDir(c.destinationDir, cm.Name)
8485
if _, err := os.Stat(configMapFile); os.IsNotExist(err) {
8586
// if the configmap file does not exist, there is no work to do, so skip making any live check and just return.
8687
// if the configmap actually exists in the API, we'll eventually see it on the watch.
8788
continue
8889
}
8990

9091
// Check with the live call it is really missing
91-
configMap, err = c.configmapGetter.Get(ctx, cm.Name, metav1.GetOptions{})
92+
configMap, err = c.configMapGetter.Get(ctx, cm.Name, metav1.GetOptions{})
9293
if err == nil {
9394
klog.Infof("Caches are stale. They don't see configmap '%s/%s', yet it is present", configMap.Namespace, configMap.Name)
9495
// We will get re-queued when we observe the change
@@ -113,9 +114,10 @@ func (c *CertSyncController) sync(ctx context.Context, syncCtx factory.SyncConte
113114
continue
114115
}
115116

116-
contentDir := getConfigMapDir(c.destinationDir, cm.Name)
117+
contentDir := getConfigMapTargetDir(c.destinationDir, cm.Name)
118+
stagingDir := getConfigMapStagingDir(c.destinationDir, cm.Name)
117119

118-
data := map[string]string{}
120+
data := make(map[string]string, len(configMap.Data))
119121
for filename := range configMap.Data {
120122
fullFilename := filepath.Join(contentDir, filename)
121123

@@ -138,7 +140,7 @@ func (c *CertSyncController) sync(ctx context.Context, syncCtx factory.SyncConte
138140
klog.V(2).Infof("Syncing updated configmap '%s/%s'.", configMap.Namespace, configMap.Name)
139141

140142
// We need to do a live get here so we don't overwrite a newer file with one from a stale cache
141-
configMap, err = c.configmapGetter.Get(ctx, configMap.Name, metav1.GetOptions{})
143+
configMap, err = c.configMapGetter.Get(ctx, configMap.Name, metav1.GetOptions{})
142144
if err != nil {
143145
// Even if the error is not exists we will act on it when caches catch up
144146
c.eventRecorder.Warningf("CertificateUpdateFailed", "Failed getting configmap: %s/%s: %v", c.namespace, cm.Name, err)
@@ -152,27 +154,11 @@ func (c *CertSyncController) sync(ctx context.Context, syncCtx factory.SyncConte
152154
continue
153155
}
154156

155-
klog.Infof("Creating directory %q ...", contentDir)
156-
if err := os.MkdirAll(contentDir, 0755); err != nil && !os.IsExist(err) {
157-
c.eventRecorder.Warningf("CertificateUpdateFailed", "Failed creating directory for configmap: %s/%s: %v", configMap.Namespace, configMap.Name, err)
158-
errors = append(errors, err)
159-
continue
160-
}
161-
for filename, content := range configMap.Data {
162-
fullFilename := filepath.Join(contentDir, filename)
163-
// if the existing is the same, do nothing
164-
if reflect.DeepEqual(data[fullFilename], content) {
165-
continue
166-
}
167-
168-
klog.Infof("Writing configmap manifest %q ...", fullFilename)
169-
if err := staticpod.WriteFileAtomic([]byte(content), 0644, fullFilename); err != nil {
170-
c.eventRecorder.Warningf("CertificateUpdateFailed", "Failed writing file for configmap: %s/%s: %v", configMap.Namespace, configMap.Name, err)
171-
errors = append(errors, err)
172-
continue
173-
}
157+
files := make(map[string][]byte, len(configMap.Data))
158+
for k, v := range configMap.Data {
159+
files[k] = []byte(v)
174160
}
175-
c.eventRecorder.Eventf("CertificateUpdated", "Wrote updated configmap: %s/%s", configMap.Namespace, configMap.Name)
161+
errors = append(errors, syncDirectory(c.eventRecorder, "configmap", configMap.ObjectMeta, contentDir, 0755, stagingDir, files, 0644))
176162
}
177163

178164
klog.Infof("Syncing secrets: %v", c.secrets)
@@ -184,7 +170,7 @@ func (c *CertSyncController) sync(ctx context.Context, syncCtx factory.SyncConte
184170
continue
185171

186172
case apierrors.IsNotFound(err) && s.Optional:
187-
secretFile := getSecretDir(c.destinationDir, s.Name)
173+
secretFile := getSecretTargetDir(c.destinationDir, s.Name)
188174
if _, err := os.Stat(secretFile); os.IsNotExist(err) {
189175
// if the secret file does not exist, there is no work to do, so skip making any live check and just return.
190176
// if the secret actually exists in the API, we'll eventually see it on the watch.
@@ -218,9 +204,10 @@ func (c *CertSyncController) sync(ctx context.Context, syncCtx factory.SyncConte
218204
continue
219205
}
220206

221-
contentDir := getSecretDir(c.destinationDir, s.Name)
207+
contentDir := getSecretTargetDir(c.destinationDir, s.Name)
208+
stagingDir := getSecretStagingDir(c.destinationDir, s.Name)
222209

223-
data := map[string][]byte{}
210+
data := make(map[string][]byte, len(secret.Data))
224211
for filename := range secret.Data {
225212
fullFilename := filepath.Join(contentDir, filename)
226213

@@ -257,29 +244,50 @@ func (c *CertSyncController) sync(ctx context.Context, syncCtx factory.SyncConte
257244
continue
258245
}
259246

260-
klog.Infof("Creating directory %q ...", contentDir)
261-
if err := os.MkdirAll(contentDir, 0755); err != nil && !os.IsExist(err) {
262-
c.eventRecorder.Warningf("CertificateUpdateFailed", "Failed creating directory for secret: %s/%s: %v", secret.Namespace, secret.Name, err)
263-
errors = append(errors, err)
264-
continue
265-
}
266-
for filename, content := range secret.Data {
267-
// TODO fix permissions
268-
fullFilename := filepath.Join(contentDir, filename)
269-
// if the existing is the same, do nothing
270-
if reflect.DeepEqual(data[fullFilename], content) {
271-
continue
272-
}
247+
errors = append(errors, syncDirectory(c.eventRecorder, "secret", secret.ObjectMeta, contentDir, 0755, stagingDir, secret.Data, 0600))
248+
}
249+
return utilerrors.NewAggregate(errors)
250+
}
273251

274-
klog.Infof("Writing secret manifest %q ...", fullFilename)
275-
if err := staticpod.WriteFileAtomic(content, 0600, fullFilename); err != nil {
276-
c.eventRecorder.Warningf("CertificateUpdateFailed", "Failed writing file for secret: %s/%s: %v", secret.Namespace, secret.Name, err)
277-
errors = append(errors, err)
278-
continue
279-
}
252+
func syncDirectory(
253+
eventRecorder events.Recorder,
254+
typeName string, o metav1.ObjectMeta,
255+
targetDir string, targetDirPerm os.FileMode, stagingDir string,
256+
fileContents map[string][]byte, filePerm os.FileMode,
257+
) error {
258+
files := make(map[string]types.File, len(fileContents))
259+
for filename, content := range fileContents {
260+
files[filename] = types.File{
261+
Content: content,
262+
Perm: filePerm,
280263
}
281-
c.eventRecorder.Eventf("CertificateUpdated", "Wrote updated secret: %s/%s", secret.Namespace, secret.Name)
282264
}
283265

284-
return utilerrors.NewAggregate(errors)
266+
if err := atomicdir.Sync(targetDir, targetDirPerm, stagingDir, files); err != nil {
267+
err = fmt.Errorf("failed to sync %s %s/%s (directory %q): %w", typeName, o.Name, o.Namespace, targetDir, err)
268+
eventRecorder.Warning("CertificateUpdateFailed", err.Error())
269+
return err
270+
}
271+
eventRecorder.Eventf("CertificateUpdated", "Wrote updated %s: %s/%s", typeName, o.Namespace, o.Name)
272+
return nil
273+
}
274+
275+
func getStagingDir(targetDir string) string {
276+
return filepath.Join(targetDir, "staging", stagingDirUID)
277+
}
278+
279+
func getConfigMapTargetDir(targetDir, configMapName string) string {
280+
return filepath.Join(targetDir, "configmaps", configMapName)
281+
}
282+
283+
func getConfigMapStagingDir(targetDir, configMapName string) string {
284+
return filepath.Join(getStagingDir(targetDir), "configmaps", configMapName)
285+
}
286+
287+
func getSecretTargetDir(targetDir, secretName string) string {
288+
return filepath.Join(targetDir, "secrets", secretName)
289+
}
290+
291+
func getSecretStagingDir(targetDir, secretName string) string {
292+
return filepath.Join(getStagingDir(targetDir), "secrets", secretName)
285293
}

0 commit comments

Comments
 (0)