@@ -2,10 +2,13 @@ package certsyncpod
22
33import (
44 "context"
5+ "fmt"
56 "os"
67 "path/filepath"
78 "reflect"
9+ "strings"
810
11+ "github.com/openshift/library-go/pkg/operator/staticpod/internal/atomicdir/types"
912 apierrors "k8s.io/apimachinery/pkg/api/errors"
1013 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1114 utilerrors "k8s.io/apimachinery/pkg/util/errors"
@@ -17,17 +20,19 @@ import (
1720
1821 "github.com/openshift/library-go/pkg/controller/factory"
1922 "github.com/openshift/library-go/pkg/operator/events"
20- "github.com/openshift/library-go/pkg/operator/staticpod"
2123 "github.com/openshift/library-go/pkg/operator/staticpod/controller/installer"
24+ "github.com/openshift/library-go/pkg/operator/staticpod/internal/atomicdir"
2225)
2326
27+ const stagingDirUID = "cert-sync"
28+
2429type CertSyncController struct {
2530 destinationDir string
2631 namespace string
2732 configMaps []installer.UnrevisionedResource
2833 secrets []installer.UnrevisionedResource
2934
30- configmapGetter corev1interface.ConfigMapInterface
35+ configMapGetter corev1interface.ConfigMapInterface
3136 configMapLister v1.ConfigMapLister
3237 secretGetter corev1interface.SecretInterface
3338 secretLister v1.SecretLister
@@ -42,10 +47,10 @@ func NewCertSyncController(targetDir, targetNamespace string, configmaps, secret
4247 secrets : secrets ,
4348 eventRecorder : eventRecorder .WithComponentSuffix ("cert-sync-controller" ),
4449
45- configmapGetter : kubeClient .CoreV1 ().ConfigMaps (targetNamespace ),
50+ configMapGetter : kubeClient .CoreV1 ().ConfigMaps (targetNamespace ),
4651 configMapLister : informers .Core ().V1 ().ConfigMaps ().Lister (),
47- secretLister : informers .Core ().V1 ().Secrets ().Lister (),
4852 secretGetter : kubeClient .CoreV1 ().Secrets (targetNamespace ),
53+ secretLister : informers .Core ().V1 ().Secrets ().Lister (),
4954 }
5055
5156 return factory .New ().
@@ -60,15 +65,12 @@ func NewCertSyncController(targetDir, targetNamespace string, configmaps, secret
6065 )
6166}
6267
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-
7168func (c * CertSyncController ) sync (ctx context.Context , syncCtx factory.SyncContext ) error {
69+ if err := os .RemoveAll (getStagingDir (c .destinationDir )); err != nil {
70+ c .eventRecorder .Warningf ("CertificateUpdateFailed" , fmt .Sprintf ("Failed to prune staging directory: %v" , err ))
71+ return err
72+ }
73+
7274 errors := []error {}
7375
7476 klog .Infof ("Syncing configmaps: %v" , c .configMaps )
@@ -80,15 +82,15 @@ func (c *CertSyncController) sync(ctx context.Context, syncCtx factory.SyncConte
8082 continue
8183
8284 case apierrors .IsNotFound (err ) && cm .Optional :
83- configMapFile := getConfigMapDir (c .destinationDir , cm .Name )
85+ configMapFile := getConfigMapTargetDir (c .destinationDir , cm .Name )
8486 if _ , err := os .Stat (configMapFile ); os .IsNotExist (err ) {
8587 // if the configmap file does not exist, there is no work to do, so skip making any live check and just return.
8688 // if the configmap actually exists in the API, we'll eventually see it on the watch.
8789 continue
8890 }
8991
9092 // Check with the live call it is really missing
91- configMap , err = c .configmapGetter .Get (ctx , cm .Name , metav1.GetOptions {})
93+ configMap , err = c .configMapGetter .Get (ctx , cm .Name , metav1.GetOptions {})
9294 if err == nil {
9395 klog .Infof ("Caches are stale. They don't see configmap '%s/%s', yet it is present" , configMap .Namespace , configMap .Name )
9496 // We will get re-queued when we observe the change
@@ -113,9 +115,10 @@ func (c *CertSyncController) sync(ctx context.Context, syncCtx factory.SyncConte
113115 continue
114116 }
115117
116- contentDir := getConfigMapDir (c .destinationDir , cm .Name )
118+ contentDir := getConfigMapTargetDir (c .destinationDir , cm .Name )
119+ stagingDir := getConfigMapStagingDir (c .destinationDir , cm .Name )
117120
118- data := map [string ]string {}
121+ data := make ( map [string ]string , len ( configMap . Data ))
119122 for filename := range configMap .Data {
120123 fullFilename := filepath .Join (contentDir , filename )
121124
@@ -138,7 +141,7 @@ func (c *CertSyncController) sync(ctx context.Context, syncCtx factory.SyncConte
138141 klog .V (2 ).Infof ("Syncing updated configmap '%s/%s'." , configMap .Namespace , configMap .Name )
139142
140143 // 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 {})
144+ configMap , err = c .configMapGetter .Get (ctx , configMap .Name , metav1.GetOptions {})
142145 if err != nil {
143146 // Even if the error is not exists we will act on it when caches catch up
144147 c .eventRecorder .Warningf ("CertificateUpdateFailed" , "Failed getting configmap: %s/%s: %v" , c .namespace , cm .Name , err )
@@ -152,27 +155,11 @@ func (c *CertSyncController) sync(ctx context.Context, syncCtx factory.SyncConte
152155 continue
153156 }
154157
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
158+ files := make (map [string ][]byte , len (configMap .Data ))
159+ for k , v := range configMap .Data {
160+ files [k ] = []byte (v )
160161 }
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- }
174- }
175- c .eventRecorder .Eventf ("CertificateUpdated" , "Wrote updated configmap: %s/%s" , configMap .Namespace , configMap .Name )
162+ errors = append (errors , syncDirectory (c .eventRecorder , "configmap" , configMap .ObjectMeta , contentDir , 0755 , stagingDir , files , 0644 ))
176163 }
177164
178165 klog .Infof ("Syncing secrets: %v" , c .secrets )
@@ -184,7 +171,7 @@ func (c *CertSyncController) sync(ctx context.Context, syncCtx factory.SyncConte
184171 continue
185172
186173 case apierrors .IsNotFound (err ) && s .Optional :
187- secretFile := getSecretDir (c .destinationDir , s .Name )
174+ secretFile := getSecretTargetDir (c .destinationDir , s .Name )
188175 if _ , err := os .Stat (secretFile ); os .IsNotExist (err ) {
189176 // if the secret file does not exist, there is no work to do, so skip making any live check and just return.
190177 // if the secret actually exists in the API, we'll eventually see it on the watch.
@@ -218,9 +205,10 @@ func (c *CertSyncController) sync(ctx context.Context, syncCtx factory.SyncConte
218205 continue
219206 }
220207
221- contentDir := getSecretDir (c .destinationDir , s .Name )
208+ contentDir := getSecretTargetDir (c .destinationDir , s .Name )
209+ stagingDir := getSecretStagingDir (c .destinationDir , s .Name )
222210
223- data := map [string ][]byte {}
211+ data := make ( map [string ][]byte , len ( secret . Data ))
224212 for filename := range secret .Data {
225213 fullFilename := filepath .Join (contentDir , filename )
226214
@@ -257,29 +245,57 @@ func (c *CertSyncController) sync(ctx context.Context, syncCtx factory.SyncConte
257245 continue
258246 }
259247
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- }
248+ errors = append (errors , syncDirectory (c .eventRecorder , "secret" , secret .ObjectMeta , contentDir , 0755 , stagingDir , secret .Data , 0600 ))
249+ }
250+ return utilerrors .NewAggregate (errors )
251+ }
273252
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- }
253+ func syncDirectory (
254+ eventRecorder events.Recorder ,
255+ typeName string , o metav1.ObjectMeta ,
256+ targetDir string , targetDirPerm os.FileMode , stagingDir string ,
257+ fileContents map [string ][]byte , defaultFilePerm os.FileMode ,
258+ ) error {
259+ files := make (map [string ]types.File , len (fileContents ))
260+ for filename , content := range fileContents {
261+ files [filename ] = types.File {
262+ Content : content ,
263+ Perm : getFilePermissions (filename , defaultFilePerm ),
280264 }
281- c .eventRecorder .Eventf ("CertificateUpdated" , "Wrote updated secret: %s/%s" , secret .Namespace , secret .Name )
282265 }
283266
284- return utilerrors .NewAggregate (errors )
267+ if err := atomicdir .Sync (targetDir , targetDirPerm , stagingDir , files ); err != nil {
268+ err = fmt .Errorf ("failed to sync %s %s/%s (directory %q): %w" , typeName , o .Name , o .Namespace , targetDir , err )
269+ eventRecorder .Warning ("CertificateUpdateFailed" , err .Error ())
270+ return err
271+ }
272+ eventRecorder .Eventf ("CertificateUpdated" , "Wrote updated %s: %s/%s" , typeName , o .Namespace , o .Name )
273+ return nil
274+ }
275+
276+ func getStagingDir (targetDir string ) string {
277+ return filepath .Join (targetDir , "staging" , stagingDirUID )
278+ }
279+
280+ func getConfigMapTargetDir (targetDir , configMapName string ) string {
281+ return filepath .Join (targetDir , "configmaps" , configMapName )
282+ }
283+
284+ func getConfigMapStagingDir (targetDir , secretName string ) string {
285+ return filepath .Join (getStagingDir (targetDir ), "configmaps" , secretName )
286+ }
287+
288+ func getSecretTargetDir (targetDir , secretName string ) string {
289+ return filepath .Join (targetDir , "secrets" , secretName )
290+ }
291+
292+ func getSecretStagingDir (targetDir , secretName string ) string {
293+ return filepath .Join (getStagingDir (targetDir ), "secrets" , secretName )
294+ }
295+
296+ func getFilePermissions (filename string , defaults os.FileMode ) os.FileMode {
297+ if strings .HasSuffix (filename , ".sh" ) {
298+ defaults |= 0111
299+ }
300+ return defaults
285301}
0 commit comments