@@ -2,10 +2,12 @@ package certsyncpod
22
33import (
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+
2428type 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-
7167func (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