Files
open-cluster-management/pkg/spoke/managedcluster/secret_controller.go
2021-03-31 13:45:18 +08:00

119 lines
4.1 KiB
Go

package managedcluster
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"path"
"time"
"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/operator/events"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
corev1informers "k8s.io/client-go/informers/core/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/klog/v2"
)
// hubKubeconfigSecretController watches the HubKubeconfig secret, if the secret is changed, this controller creates/updates the
// corresponding configuration files from the secret
type hubKubeconfigSecretController struct {
hubKubeconfigDir string
hubKubeconfigSecretNamespace string
hubKubeconfigSecretName string
spokeCoreClient corev1client.CoreV1Interface
}
// NewHubKubeconfigSecretController returns a new HubKubeconfigSecretController
func NewHubKubeconfigSecretController(
hubKubeconfigDir, hubKubeconfigSecretNamespace, hubKubeconfigSecretName string,
spokeCoreClient corev1client.CoreV1Interface,
spokeSecretInformer corev1informers.SecretInformer,
recorder events.Recorder) factory.Controller {
s := &hubKubeconfigSecretController{
hubKubeconfigDir: hubKubeconfigDir,
hubKubeconfigSecretNamespace: hubKubeconfigSecretNamespace,
hubKubeconfigSecretName: hubKubeconfigSecretName,
spokeCoreClient: spokeCoreClient,
}
return factory.New().
WithFilteredEventsInformersQueueKeyFunc(
func(obj runtime.Object) string {
accessor, _ := meta.Accessor(obj)
return accessor.GetName()
},
func(obj interface{}) bool {
accessor, err := meta.Accessor(obj)
if err != nil {
return false
}
// only enqueue when hub kubeconfig secret is changed
if accessor.GetNamespace() == hubKubeconfigSecretNamespace && accessor.GetName() == hubKubeconfigSecretName {
return true
}
return false
}, spokeSecretInformer.Informer()).
WithSync(s.sync).
ResyncEvery(5*time.Minute).
ToController("HubKubeconfigSecretController", recorder)
}
func (s *hubKubeconfigSecretController) sync(ctx context.Context, syncCtx factory.SyncContext) error {
klog.V(4).Infof("Reconciling Hub KubeConfig secret %q", s.hubKubeconfigSecretName)
return DumpSecret(s.spokeCoreClient, s.hubKubeconfigSecretNamespace, s.hubKubeconfigSecretName, s.hubKubeconfigDir, ctx, syncCtx.Recorder())
}
// DumpSecret dumps the data in the given seccret into a directory in file system.
// The output directory will be created if not exists.
// TO DO: remove the file once the corresponding key is removed from secret.
func DumpSecret(
coreV1Client corev1client.CoreV1Interface,
secretNamespace, secretName, outputDir string,
ctx context.Context,
recorder events.Recorder) error {
secret, err := coreV1Client.Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
if errors.IsNotFound(err) {
return nil
}
if err != nil {
return fmt.Errorf("unable to get secret %s/%s : %w", secretNamespace, secretName, err)
}
if err := os.MkdirAll(outputDir, 0700); err != nil {
return fmt.Errorf("unable to create dir %q : %w", outputDir, err)
}
// create/update files from the secret
for key, data := range secret.Data {
filename := path.Clean(path.Join(outputDir, key))
lastData, err := ioutil.ReadFile(filename)
switch {
case os.IsNotExist(err):
// create file
if err := ioutil.WriteFile(filename, data, 0600); err != nil {
return fmt.Errorf("unable to write file %q: %w", filename, err)
}
recorder.Event("FileCreated", fmt.Sprintf("File %q is created from secret %s/%s", filename, secretNamespace, secretName))
case err != nil:
return fmt.Errorf("unable to read file %q: %w", filename, err)
case bytes.Equal(lastData, data):
// skip file without any change
continue
default:
// update file
if err := ioutil.WriteFile(path.Clean(filename), data, 0600); err != nil {
return fmt.Errorf("unable to write file %q: %w", filename, err)
}
recorder.Event("FileUpdated", fmt.Sprintf("File %q is updated from secret %s/%s", filename, secretNamespace, secretName))
}
}
return nil
}