Skip to content

Commit

Permalink
rollouts: support reposync option (#3850)
Browse files Browse the repository at this point in the history
  • Loading branch information
natasha41575 committed Mar 7, 2023
1 parent 6475e31 commit 2c097c1
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 49 deletions.
9 changes: 4 additions & 5 deletions rollouts/api/v1alpha1/remoterootsync_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,10 @@ import (

// RemoteRootSyncSpec defines the desired state of RemoteRootSync
type RemoteRootSyncSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file

ClusterRef ClusterRef `json:"clusterRef,omitempty"`
Template *RootSyncInfo `json:"template,omitempty"`
// ClusterReference contains the identify information need to refer a cluster.
ClusterRef ClusterRef `json:"clusterRef,omitempty"`
Template *RootSyncInfo `json:"template,omitempty"`
Type SyncTemplateType `json:"type,omitempty"`
}

type RootSyncInfo struct {
Expand Down
5 changes: 5 additions & 0 deletions rollouts/config/crd/bases/gitops.kpt.dev_remoterootsyncs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ spec:
type: string
type: object
type: object
type:
enum:
- RootSync
- RepoSync
type: string
type: object
status:
description: RemoteRootSyncStatus defines the observed state of RemoteRootSync
Expand Down
84 changes: 67 additions & 17 deletions rollouts/controllers/remoterootsync_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,13 @@ import (
)

var (
// The RootSync object always gets put in the config-management-system namespace,
// while the RepoSync object will get its namespace from the RemoteRootSync object's
// metadata.namespace.
// See examples at: https://cloud.google.com/anthos-config-management/docs/how-to/multiple-repositories
rootSyncNamespace = "config-management-system"
rootSyncGVK = schema.GroupVersionKind{

rootSyncGVK = schema.GroupVersionKind{
Group: "configsync.gke.io",
Version: "v1beta1",
Kind: "RootSync",
Expand All @@ -58,6 +63,17 @@ var (
Resource: "rootsyncs",
}

repoSyncGVK = schema.GroupVersionKind{
Group: "configsync.gke.io",
Version: "v1beta1",
Kind: "RepoSync",
}
repoSyncGVR = schema.GroupVersionResource{
Group: "configsync.gke.io",
Version: "v1beta1",
Resource: "reposyncs",
}

remoteRootSyncNameLabel = "gitops.kpt.dev/remoterootsync-name"
remoteRootSyncNamespaceLabel = "gitops.kpt.dev/remoterootsync-namespace"
)
Expand Down Expand Up @@ -167,23 +183,22 @@ func (r *RemoteRootSyncReconciler) Reconcile(ctx context.Context, req ctrl.Reque
}

func (r *RemoteRootSyncReconciler) syncExternalSync(ctx context.Context, rrs *gitopsv1alpha1.RemoteRootSync) (string, error) {
syncName := rrs.Name
clusterRef := &rrs.Spec.ClusterRef

dynCl, err := r.getDynamicClientForCluster(ctx, clusterRef)
if err != nil {
return "", fmt.Errorf("failed to create client: %w", err)
}

if err := r.patchRootSync(ctx, dynCl, syncName, rrs); err != nil {
if err := r.patchExternalSync(ctx, dynCl, rrs); err != nil {
return "", fmt.Errorf("failed to create/update sync: %w", err)
}

r.setupWatches(ctx, rrs.Name, rrs.Namespace, rrs.Spec.ClusterRef)

syncStatus, err := checkSyncStatus(ctx, dynCl, syncName)
syncStatus, err := checkSyncStatus(ctx, dynCl, rrs)
if err != nil {
return "", fmt.Errorf("faild to check status: %w", err)
return "", fmt.Errorf("failed to check status: %w", err)
}

return syncStatus, nil
Expand Down Expand Up @@ -225,25 +240,33 @@ func (r *RemoteRootSyncReconciler) updateStatus(ctx context.Context, rrs *gitops
return r.Client.Status().Update(ctx, rrs)
}

// patchRootSync patches the RootSync in the remote clusters targeted by
// patchExternalSync patches the external sync in the remote clusters targeted by
// the clusterRefs based on the latest revision of the template in the RemoteRootSync.
func (r *RemoteRootSyncReconciler) patchRootSync(ctx context.Context, client dynamic.Interface, name string, rrs *gitopsv1alpha1.RemoteRootSync) error {
func (r *RemoteRootSyncReconciler) patchExternalSync(ctx context.Context, client dynamic.Interface, rrs *gitopsv1alpha1.RemoteRootSync) error {
logger := klog.FromContext(ctx)

newRootSync, err := BuildObjectsToApply(rrs)
gvr, gvk, err := getGvrAndGvk(rrs.Spec.Type)
if err != nil {
return err
}

namespace := getExternalSyncNamespace(rrs)

newRootSync, err := BuildObjectsToApply(rrs, gvk, namespace)
if err != nil {
return err
}
data, err := json.Marshal(newRootSync)
if err != nil {
return fmt.Errorf("failed to encode root sync to JSON: %w", err)
return fmt.Errorf("failed to encode %s to JSON: %w", gvk.Kind, err)
}
_, err = client.Resource(rootSyncGVR).Namespace(rootSyncNamespace).Patch(ctx, name, types.ApplyPatchType, data, metav1.PatchOptions{FieldManager: name})

_, err = client.Resource(gvr).Namespace(namespace).Patch(ctx, rrs.Name, types.ApplyPatchType, data, metav1.PatchOptions{FieldManager: rrs.Name})
if err != nil {
return fmt.Errorf("failed to patch RootSync: %w", err)
return fmt.Errorf("failed to patch %s: %w", gvk.Kind, err)
}

logger.Info("RootSync resource created/updated", "rootSync", klog.KRef(rootSyncNamespace, name))
logger.Info(fmt.Sprintf("%s resource created/updated", gvk.Kind), gvr.Resource, klog.KRef(namespace, rrs.Name))
return nil
}

Expand Down Expand Up @@ -313,17 +336,20 @@ func (r *RemoteRootSyncReconciler) pruneWatches(rrsnn types.NamespacedName, clus
}
}

// BuildObjectsToApply config root sync
func BuildObjectsToApply(remoterootsync *gitopsv1alpha1.RemoteRootSync) (*unstructured.Unstructured, error) {
// BuildObjectsToApply configures the external sync
func BuildObjectsToApply(remoterootsync *gitopsv1alpha1.RemoteRootSync,
gvk schema.GroupVersionKind,
namespace string) (*unstructured.Unstructured, error) {

newRootSync, err := runtime.DefaultUnstructuredConverter.ToUnstructured(remoterootsync.Spec.Template)
if err != nil {
return nil, fmt.Errorf("failed to convert to unstructured type: %w", err)
}

u := unstructured.Unstructured{Object: newRootSync}
u.SetGroupVersionKind(rootSyncGVK)
u.SetGroupVersionKind(gvk)
u.SetName(remoterootsync.Name)
u.SetNamespace(rootSyncNamespace)
u.SetNamespace(namespace)

labels := u.GetLabels()
if labels == nil {
Expand All @@ -345,8 +371,13 @@ func (r *RemoteRootSyncReconciler) deleteExternalResources(ctx context.Context,
return err
}

gvr, _, err := getGvrAndGvk(remoterootsync.Spec.Type)
if err != nil {
return err
}

logger.Info("Deleting external resource")
err = dynCl.Resource(rootSyncGVR).Namespace("config-management-system").Delete(ctx, remoterootsync.Name, metav1.DeleteOptions{})
err = dynCl.Resource(gvr).Namespace(getExternalSyncNamespace(remoterootsync)).Delete(ctx, remoterootsync.Name, metav1.DeleteOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return err
}
Expand All @@ -368,6 +399,25 @@ func (r *RemoteRootSyncReconciler) getDynamicClientForCluster(ctx context.Contex
return dynamicClient, nil
}

func getGvrAndGvk(t gitopsv1alpha1.SyncTemplateType) (schema.GroupVersionResource, schema.GroupVersionKind, error) {
switch t {
case gitopsv1alpha1.TemplateTypeRootSync, "":
return rootSyncGVR, rootSyncGVK, nil
case gitopsv1alpha1.TemplateTypeRepoSync:
return repoSyncGVR, repoSyncGVK, nil
default:
return schema.GroupVersionResource{}, schema.GroupVersionKind{}, fmt.Errorf("invalid sync type %q", t)
}
}

func getExternalSyncNamespace(rrs *gitopsv1alpha1.RemoteRootSync) string {
if rrs.Spec.Type == gitopsv1alpha1.TemplateTypeRepoSync {
return rrs.Namespace
} else {
return rootSyncNamespace
}
}

// SetupWithManager sets up the controller with the Manager.
func (r *RemoteRootSyncReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.channel = make(chan event.GenericEvent, 10)
Expand Down
48 changes: 29 additions & 19 deletions rollouts/controllers/rollout_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,14 +290,6 @@ func (r *RolloutReconciler) getPackageDiscoveryClient(rolloutNamespacedName type
func (r *RolloutReconciler) reconcileRollout(ctx context.Context, rollout *gitopsv1alpha1.Rollout, strategy *gitopsv1alpha1.ProgressiveRolloutStrategy, packageDiscoveryClient *packagediscovery.PackageDiscovery) error {
logger := klog.FromContext(ctx)

if rollout != nil && rollout.Spec.SyncTemplate != nil {
if rollout.Spec.SyncTemplate.Type == gitopsv1alpha1.TemplateTypeRepoSync {
err := fmt.Errorf("reposync is not yet supported")
logger.Error(err, "")
return err
}
}

targetClusters, err := r.store.ListClusters(ctx, &rollout.Spec.Clusters, rollout.Spec.Targets.Selector)
discoveredPackages, err := packageDiscoveryClient.GetPackages(ctx, rollout.Spec.Packages)
if err != nil {
Expand Down Expand Up @@ -453,11 +445,11 @@ func (r *RolloutReconciler) computeTargets(ctx context.Context,

func pkgNeedsUpdate(rollout *gitopsv1alpha1.Rollout, rrs gitopsv1alpha1.RemoteRootSync, pkg *packagediscovery.DiscoveredPackage) (*gitopsv1alpha1.RemoteRootSync, bool) {
// TODO: We need to check other things here besides git.Revision and metadata
if pkg.Revision != rrs.Spec.Template.Spec.Git.Revision ||
!reflect.DeepEqual(rollout.Spec.SyncTemplate.RootSync.Metadata,
rrs.Spec.Template.Metadata) {
metadata := getSpecMetadata(rollout)
if pkg.Revision != rrs.Spec.Template.Spec.Git.Revision || !reflect.DeepEqual(metadata, rrs.Spec.Template.Metadata) || rrs.Spec.Type != rollout.Spec.SyncTemplate.Type {
rrs.Spec.Template.Spec.Git.Revision = pkg.Revision
rrs.Spec.Template.Metadata = rollout.Spec.SyncTemplate.RootSync.Metadata
rrs.Spec.Template.Metadata = metadata
rrs.Spec.Type = rollout.Spec.SyncTemplate.Type
return &rrs, true
}
return nil, false
Expand Down Expand Up @@ -541,20 +533,14 @@ func (r *RolloutReconciler) rolloutTargets(ctx context.Context, rollout *gitopsv
}
}

var metadata *gitopsv1alpha1.Metadata
if rollout != nil && rollout.Spec.SyncTemplate != nil &&
rollout.Spec.SyncTemplate.RootSync != nil {
metadata = rollout.Spec.SyncTemplate.RootSync.Metadata
}

for _, target := range targets.ToBeCreated {
rootSyncSpec := toRootSyncSpec(target.packageRef)
rrs := newRemoteRootSync(rollout,
gitopsv1alpha1.ClusterRef{Name: target.cluster.Name},
rootSyncSpec,
pkgID(target.packageRef),
wave.Name,
metadata,
getSpecMetadata(rollout),
)

if maxConcurrent > concurrentUpdates {
Expand Down Expand Up @@ -734,6 +720,9 @@ func newRemoteRootSync(rollout *gitopsv1alpha1.Rollout,
t := true
clusterName := clusterRef.Name[strings.LastIndex(clusterRef.Name, "/")+1:]

// The RemoteRootSync object is created in the same namespace as the Rollout
// object. The RemoteRootSync will create either a RepoSync in the same namespace,
// or a RootSync in the config-management-system namespace.
return &gitopsv1alpha1.RemoteRootSync{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-%s", pkgID, clusterName),
Expand All @@ -751,7 +740,9 @@ func newRemoteRootSync(rollout *gitopsv1alpha1.Rollout,
},
},
},

Spec: gitopsv1alpha1.RemoteRootSyncSpec{
Type: rollout.Spec.SyncTemplate.Type,
ClusterRef: clusterRef,
Template: &gitopsv1alpha1.RootSyncInfo{
Spec: rssSpec,
Expand Down Expand Up @@ -933,3 +924,22 @@ func filterClusters(allClusters []clusterstore.Cluster, labelSelector *metav1.La

return clusters, nil
}

func getSpecMetadata(rollout *gitopsv1alpha1.Rollout) *gitopsv1alpha1.Metadata {
if rollout == nil || rollout.Spec.SyncTemplate == nil {
return nil
}
switch rollout.Spec.SyncTemplate.Type {
case gitopsv1alpha1.TemplateTypeRepoSync:
if rollout.Spec.SyncTemplate.RepoSync == nil {
return nil
}
return rollout.Spec.SyncTemplate.RepoSync.Metadata
case gitopsv1alpha1.TemplateTypeRootSync, "":
if rollout.Spec.SyncTemplate.RootSync == nil {
return nil
}
return rollout.Spec.SyncTemplate.RootSync.Metadata
}
return nil
}
22 changes: 14 additions & 8 deletions rollouts/controllers/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,34 @@ import (
"context"
"fmt"

gitopsv1alpha1 "github.com/GoogleContainerTools/kpt/rollouts/api/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
)

// checkSyncStatus fetches the RootSync using the provided client and computes the sync status. The rules
// checkSyncStatus fetches the external sync using the provided client and computes the sync status. The rules
// for computing status here mirrors the one used in the status command in the nomos cli.
func checkSyncStatus(ctx context.Context, client dynamic.Interface, rrsName string) (string, error) {
// TODO: Change this to use the RootSync type instead of Unstructured.
rs, err := client.Resource(rootSyncGVR).Namespace(rootSyncNamespace).Get(ctx, rrsName, metav1.GetOptions{})
func checkSyncStatus(ctx context.Context, client dynamic.Interface, rrs *gitopsv1alpha1.RemoteRootSync) (string, error) {
gvr, gvk, err := getGvrAndGvk(rrs.Spec.Type)
if err != nil {
return "", fmt.Errorf("failed to get RootSync: %w", err)
return "", err
}

rs, err := client.Resource(gvr).Namespace(getExternalSyncNamespace(rrs)).Get(ctx, rrs.Name, metav1.GetOptions{})
if err != nil {
return "", fmt.Errorf("failed to get %s: %w", gvk.Kind, err)
}

// TODO: Change this to use the RootSync type instead of Unstructured.
generation, _, err := unstructured.NestedInt64(rs.Object, "metadata", "generation")
if err != nil {
return "", fmt.Errorf("failed to read generation from RootSync: %w", err)
return "", fmt.Errorf("failed to read generation from %s: %w", gvk.Kind, err)
}

observedGeneration, _, err := unstructured.NestedInt64(rs.Object, "status", "observedGeneration")
if err != nil {
return "", fmt.Errorf("failed to read observedGeneration from RootSync: %w", err)
return "", fmt.Errorf("failed to read observedGeneration from %s: %w", gvk.Kind, err)
}

if generation != observedGeneration {
Expand All @@ -48,7 +54,7 @@ func checkSyncStatus(ctx context.Context, client dynamic.Interface, rrsName stri

conditions, _, err := unstructured.NestedSlice(rs.Object, "status", "conditions")
if err != nil {
return "", fmt.Errorf("failed to extract conditions from RootSync: %w", err)
return "", fmt.Errorf("failed to extract conditions from %s: %w", gvk.Kind, err)
}

val, found, err := getConditionStatus(conditions, "Stalled")
Expand Down

0 comments on commit 2c097c1

Please sign in to comment.