Skip to content

Commit

Permalink
RemoteRootSyncSet: able to specify a packageRef to a package (#3734)
Browse files Browse the repository at this point in the history
This makes it easy to apply packages we create.
  • Loading branch information
justinsb committed Jan 21, 2023
1 parent e6fef8f commit dc44dbd
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ spec:
repository:
type: string
type: object
packageRef:
description: PackageRef specifies a package as the source of the
objects to be applied.
properties:
name:
type: string
type: object
sourceFormat:
type: string
type: object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,27 @@ type PackageRef struct {
Name string `json:"name,omitempty"`
}

func (r *PackageRef) GetName() string {
if r == nil {
return ""
}
return r.Name
}

type RootSyncTemplate struct {
SourceFormat string `json:"sourceFormat,omitempty"`
// Git *GitInfo `json:"git,omitempty"`
OCI *OCISpec `json:"oci,omitempty"`

// PackageRef specifies a package as the source of the objects to be applied.
PackageRef *PackageRef `json:"packageRef,omitempty"`
}

func (o *RootSyncTemplate) GetSourceFormat() string {
if o == nil {
return ""
}
return o.SourceFormat
}

func (o *RootSyncTemplate) GetOCI() *OCISpec {
Expand All @@ -107,6 +124,13 @@ func (o *RootSyncTemplate) GetOCI() *OCISpec {
return o.OCI
}

func (o *RootSyncTemplate) GetPackageRef() *PackageRef {
if o == nil {
return nil
}
return o.PackageRef
}

type OCISpec struct {
Repository string `json:"repository,omitempty"`
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions porch/controllers/remoterootsyncsets/config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,12 @@ rules:
- get
- patch
- update
- apiGroups:
- porch.kpt.dev
resources:
- packagerevisionresources
- packagerevisions
verbs:
- get
- list
- watch
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,19 @@ import (
"context"
"flag"
"fmt"
"strconv"
"strings"

kptoci "github.com/GoogleContainerTools/kpt/pkg/oci"
porchapi "github.com/GoogleContainerTools/kpt/porch/api/porch/v1alpha1"
api "github.com/GoogleContainerTools/kpt/porch/controllers/remoterootsyncsets/api/v1alpha1"
"github.com/GoogleContainerTools/kpt/porch/controllers/remoterootsyncsets/pkg/applyset"
"github.com/GoogleContainerTools/kpt/porch/controllers/remoterootsyncsets/pkg/remoteclient"
"github.com/GoogleContainerTools/kpt/porch/pkg/objects"
"github.com/GoogleContainerTools/kpt/porch/pkg/oci"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
Expand Down Expand Up @@ -54,9 +58,15 @@ func (o *Options) BindFlags(prefix string, flags *flag.FlagSet) {
type RemoteRootSyncSetReconciler struct {
Options

remoteclient.RemoteClientGetter
remoteClientGetter remoteclient.RemoteClientGetter

client.Client
client client.Client

// uncachedClient queries the apiserver without using a watch cache.
// This is useful for PackageRevisionResources, which are large
// and would consume a lot of memory, and so we deliberately don't
// support watching them.
uncachedClient client.Client

ociStorage *kptoci.Storage

Expand All @@ -70,11 +80,12 @@ type RemoteRootSyncSetReconciler struct {
//+kubebuilder:rbac:groups=config.porch.kpt.dev,resources=remoterootsyncsets,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=config.porch.kpt.dev,resources=remoterootsyncsets/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=config.porch.kpt.dev,resources=remoterootsyncsets/finalizers,verbs=update
//+kubebuilder:rbac:groups=porch.kpt.dev,resources=packagerevisions;packagerevisionresources,verbs=get;list;watch

// Reconcile implements the main kubernetes reconciliation loop.
func (r *RemoteRootSyncSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var subject api.RemoteRootSyncSet
if err := r.Get(ctx, req.NamespacedName, &subject); err != nil {
if err := r.client.Get(ctx, req.NamespacedName, &subject); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
myFinalizerName := "config.porch.kpt.dev/finalizer"
Expand All @@ -84,7 +95,7 @@ func (r *RemoteRootSyncSetReconciler) Reconcile(ctx context.Context, req ctrl.Re
// registering our finalizer.
if !controllerutil.ContainsFinalizer(&subject, myFinalizerName) {
controllerutil.AddFinalizer(&subject, myFinalizerName)
if err := r.Update(ctx, &subject); err != nil {
if err := r.client.Update(ctx, &subject); err != nil {
return ctrl.Result{}, err
}
}
Expand All @@ -99,7 +110,7 @@ func (r *RemoteRootSyncSetReconciler) Reconcile(ctx context.Context, req ctrl.Re
}
// remove our finalizer from the list and update it.
controllerutil.RemoveFinalizer(&subject, myFinalizerName)
if err := r.Update(ctx, &subject); err != nil {
if err := r.client.Update(ctx, &subject); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to update %s after delete finalizer: %w", req.Name, err)
}
}
Expand All @@ -116,7 +127,7 @@ func (r *RemoteRootSyncSetReconciler) Reconcile(ctx context.Context, req ctrl.Re
patchErrs = append(patchErrs, err)
}
if updateTargetStatus(&subject, clusterRef, results, err) {
if err := r.Status().Update(ctx, &subject); err != nil {
if err := r.client.Status().Update(ctx, &subject); err != nil {
patchErrs = append(patchErrs, err)
}
}
Expand Down Expand Up @@ -226,7 +237,7 @@ func updateAggregateStatus(subject *api.RemoteRootSyncSet) bool {
}

func (r *RemoteRootSyncSetReconciler) applyToClusterRef(ctx context.Context, subject *api.RemoteRootSyncSet, clusterRef *api.ClusterRef) (*applyset.ApplyResults, error) {
remoteClient, err := r.GetRemoteClient(ctx, clusterRef, subject.Namespace)
remoteClient, err := r.remoteClientGetter.GetRemoteClient(ctx, clusterRef, subject.Namespace)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -281,6 +292,18 @@ func (r *RemoteRootSyncSetReconciler) applyToClusterRef(ctx context.Context, sub

// BuildObjectsToApply config root sync
func (r *RemoteRootSyncSetReconciler) BuildObjectsToApply(ctx context.Context, subject *api.RemoteRootSyncSet) ([]applyset.ApplyableObject, error) {
sourceFormat := subject.GetSpec().GetTemplate().GetSourceFormat()
switch sourceFormat {
case "oci":
return r.buildObjectsToApplyFromOci(ctx, subject)
case "package":
return r.buildObjectsToApplyFromPackage(ctx, subject)
default:
return nil, fmt.Errorf("unknown sourceFormat %q", sourceFormat)
}
}

func (r *RemoteRootSyncSetReconciler) buildObjectsToApplyFromOci(ctx context.Context, subject *api.RemoteRootSyncSet) ([]applyset.ApplyableObject, error) {
repository := subject.GetSpec().GetTemplate().GetOCI().GetRepository()
if repository == "" {
return nil, fmt.Errorf("spec.template.oci.repository is not set")
Expand Down Expand Up @@ -313,17 +336,132 @@ func (r *RemoteRootSyncSetReconciler) BuildObjectsToApply(ctx context.Context, s
return applyables, nil
}

func (r *RemoteRootSyncSetReconciler) buildObjectsToApplyFromPackage(ctx context.Context, subject *api.RemoteRootSyncSet) ([]applyset.ApplyableObject, error) {
packageName := subject.GetSpec().GetTemplate().GetPackageRef().GetName()
if packageName == "" {
return nil, fmt.Errorf("spec.template.packageRef.name is not set")
}

ns := subject.GetNamespace()

var packageRevisions porchapi.PackageRevisionList
// Note that latest revision is planned for removal: #3672

// TODO: publish package name as label?
// TODO: Make package a first class concept?
// TODO: Have some indicator of latest revision?
if err := r.client.List(ctx, &packageRevisions, client.InNamespace(ns)); err != nil {
// Not found here is unexpected
return nil, fmt.Errorf("error listing package revisions: %w", err)
}

var latestPackageRevision *porchapi.PackageRevision
for i := range packageRevisions.Items {
candidate := &packageRevisions.Items[i]
if candidate.Spec.PackageName != packageName {
continue
}
if !strings.Contains(candidate.Spec.RepositoryName, "deployment") {
// TODO: How can we only pick up deployment packages? Probably labels...
klog.Warningf("HACK: ignoring package that does not appear to be a deployment package")
continue
}

candidateRevision := candidate.Spec.Revision
if !strings.HasPrefix(candidateRevision, "v") {
klog.Warningf("ignoring revision %q with unexpected format %q", candidate.Name, candidateRevision)
continue
}

if latestPackageRevision == nil {
latestPackageRevision = candidate
} else {
latestRevision := latestPackageRevision.Spec.Revision

if !strings.HasPrefix(latestRevision, "v") {
return nil, fmt.Errorf("unexpected revision format %q", latestRevision)
}
latestRevision = strings.TrimPrefix(latestRevision, "v")

if !strings.HasPrefix(candidateRevision, "v") {
return nil, fmt.Errorf("unexpected revision format %q", candidateRevision)
}
candidateRevision = strings.TrimPrefix(candidateRevision, "v")

latestRevisionInt, err := strconv.Atoi(latestRevision)
if err != nil {
return nil, fmt.Errorf("unexpected revision format %q", latestRevision)
}

candidateRevisionInt, err := strconv.Atoi(candidateRevision)
if err != nil {
return nil, fmt.Errorf("unexpected revision format %q", candidateRevision)
}

if candidateRevisionInt == latestRevisionInt {
return nil, fmt.Errorf("found two package revision with same revision: %q and %q", candidate.Name, latestPackageRevision.Name)
}

if candidateRevisionInt > latestRevisionInt {
latestPackageRevision = candidate
}
}
}
if latestPackageRevision == nil {
return nil, fmt.Errorf("cannot find latest version of package %q in namespace %q", packageName, ns)
}

id := types.NamespacedName{
Namespace: latestPackageRevision.Namespace,
Name: latestPackageRevision.Name,
}
klog.Infof("found latest package %q", id)
latestPackageRevisionResources := &porchapi.PackageRevisionResources{}
if err := r.uncachedClient.Get(ctx, id, latestPackageRevisionResources); err != nil {
// Not found here is unexpected
return nil, fmt.Errorf("error getting package revision resources for %v: %w", id, err)
}

unstructureds, err := objects.Parser{}.AsUnstructureds(latestPackageRevisionResources.Spec.Resources)
if err != nil {
return nil, err
}

var applyables []applyset.ApplyableObject
for _, u := range unstructureds {
applyables = append(applyables, u)
}
return applyables, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *RemoteRootSyncSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
if err := api.AddToScheme(mgr.GetScheme()); err != nil {
return err
}
if err := porchapi.AddToScheme(mgr.GetScheme()); err != nil {
return err
}

if err := r.RemoteClientGetter.Init(mgr); err != nil {
if err := r.remoteClientGetter.Init(mgr); err != nil {
return err
}

r.Client = mgr.GetClient()
r.client = mgr.GetClient()

// We need an uncachedClient to query objects directly.
// In particular we don't want to watch PackageRevisionResources,
// they are large so would have a large memory footprint,
// and we don't want to support watch on them anyway.
// If you need to watch PackageRevisionResources, you can watch PackageRevisions instead.
uncachedClient, err := client.New(mgr.GetConfig(), client.Options{
Scheme: mgr.GetScheme(),
Mapper: mgr.GetRESTMapper(),
})
if err != nil {
return fmt.Errorf("creating uncached client: %w", err)
}
r.uncachedClient = uncachedClient

if err := ctrl.NewControllerManagedBy(mgr).
For(&api.RemoteRootSyncSet{}).
Expand Down

0 comments on commit dc44dbd

Please sign in to comment.