Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Have PackageVariant set readiness gate on PackageRevisions #156

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion controllers/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ func run(ctx context.Context) error {
Client: client.Options{
Cache: &client.CacheOptions{
DisableFor: []client.Object{
&porchapi.PackageRevisionResources{}},
&porchapi.PackageRevisionResources{},
&porchapi.PackageRevision{}},
},
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ import (
"context"
"fmt"
"path/filepath"
"sigs.k8s.io/kustomize/kyaml/kio"
"sort"
"strings"

"sigs.k8s.io/kustomize/kyaml/kio"

"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
porchapi "github.com/nephio-project/porch/api/porch/v1alpha1"
api "github.com/nephio-project/porch/controllers/packagevariants/api/v1alpha1"
kptfilev1 "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1"
"github.com/nephio-project/porch/pkg/util"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand Down Expand Up @@ -124,7 +126,7 @@ func ensureConfigInjection(ctx context.Context,
return err
}

prr.Spec.Resources["Kptfile"] = kptfile.String()
prr.Spec.Resources[kptfilev1.KptFileName] = util.KubeObjectToYaml(kptfile)

return nil
}
Expand Down
Copy link
Collaborator

@kispaljr kispaljr Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the findAndUpdateExistingRevisions() method, at line 473:

			// Save the updated PackageRevisionResources
			if err := r.updatePackageResources(ctx, prr, pv); err != nil {
				return nil, err
			}

The pipeline will also be executed here, so the Condition should be set according to the results.

Copy link
Collaborator

@kispaljr kispaljr Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in updateDraft() at line 736:

	err := r.Client.Update(ctx, draft)
	if err != nil {
		return nil, err
	}

this is tricky, but technically this Update() call will also result in the re-execution of the pipeline, so we should record the results in the Condition. The tricky part is that I don't know how to get the rendering results, and without it, the best we can do is to report the error or success of Update() itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done both:

  • the findAndUpdateExistingRevisions() one could be a bit better by including the initial set of the Condition directly in prr instead of doing a separate r.Client.Update() - I want to look into automated (and a few more manual) tests first though
  • the updateDraft() one took more time to work around to avoid ending up in an endless loop where doing the Update() to set the Condition would trigger the pipeline, resulting in a rendering result which we'd need to record in the Condition by doing an Update(), triggering the pipeline... you get the idea. :)
    • thanks to @liamfallon for suggesting we have the server detect and skip the pipeline when an update only sets the Condition/readiness info
    • works as desired when creating a PackageVariant - unable to test in this update flow due to immediate Git conflicts as seen in #469

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023-2024 The kpt and Nephio Authors
// Copyright 2022, 2024 The kpt and Nephio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -28,6 +28,7 @@ import (
"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
kptfilev1 "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1"
"github.com/nephio-project/porch/pkg/kpt/kptfileutil"
"github.com/nephio-project/porch/pkg/util"

"golang.org/x/mod/semver"
"k8s.io/apimachinery/pkg/api/meta"
Expand Down Expand Up @@ -56,8 +57,24 @@ type PackageVariantReconciler struct {
const (
workspaceNamePrefix = "packagevariant-"

ConditionTypeStalled = "Stalled" // whether or not the packagevariant object is making progress or not
ConditionTypeReady = "Ready" // whether or not the reconciliation succeeded
ConditionTypeStalled = "Stalled" // whether or not the packagevariant object is making progress
ConditionTypeReady = "Ready" // whether or not the reconciliation succeeded
ConditionTypePipelinePassed = "PackagePipelinePassed" // whether or not the package's pipeline has completed successfully
)

var (
conditionPipelineNotPassed = porchapi.Condition{
Type: ConditionTypePipelinePassed,
Status: porchapi.ConditionFalse,
Reason: "WaitingOnPipeline",
Message: "waiting for package pipeline to pass",
}
conditionPipelinePassed = porchapi.Condition{
Type: ConditionTypePipelinePassed,
Status: porchapi.ConditionTrue,
Reason: "PipelinePassed",
Message: "package pipeline completed successfully",
}
)

//go:generate go run sigs.k8s.io/controller-tools/cmd/[email protected] rbac:headerFile=../../../../../scripts/boilerplate.yaml.txt,roleName=porch-controllers-packagevariants webhook paths="." output:rbac:artifacts:config=../../../config/rbac
Expand Down Expand Up @@ -334,6 +351,13 @@ func (r *PackageVariantReconciler) ensurePackageVariant(ctx context.Context,
if err = r.Client.Create(ctx, newPR); err != nil {
return nil, err
}

setPrReadinessGate(newPR, ConditionTypePipelinePassed)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I'd be interested to see how this works in a full test-infra e2e run.
Also, may be somewhat aligned with what @kispaljr was working on?

setPrStatusCondition(newPR, conditionPipelineNotPassed)
if err := r.Client.Update(ctx, newPR); err != nil {
return nil, err
}

klog.Infoln(fmt.Sprintf("package variant %q created package revision %q", pv.Name, newPR.Name))

prr, changed, err := r.calculateDraftResources(ctx, pv, newPR)
Expand All @@ -347,6 +371,15 @@ func (r *PackageVariantReconciler) ensurePackageVariant(ctx context.Context,
}
}

if err := r.Client.Get(ctx, types.NamespacedName{Name: newPR.GetName(), Namespace: newPR.GetNamespace()}, newPR); err != nil {
return nil, err
}

setPrStatusCondition(newPR, conditionPipelinePassed)
if err := r.Client.Update(ctx, newPR); err != nil {
return nil, err
}

return []*porchapi.PackageRevision{newPR}, nil
}

Expand All @@ -369,10 +402,18 @@ func (r *PackageVariantReconciler) findAndUpdateExistingRevisions(ctx context.Co
downstream.Spec.Lifecycle = porchapi.PackageRevisionLifecyclePublished
// We update this now, because later we may use a Porch call to clone or update
// and we want to make sure the server is in sync with us

setPrReadinessGate(downstream, ConditionTypePipelinePassed)
setPrStatusCondition(downstream, conditionPipelineNotPassed)
if err := r.Client.Update(ctx, downstream); err != nil {
klog.Errorf("error updating package revision lifecycle: %v", err)
return nil, err
}

setPrStatusCondition(downstream, conditionPipelinePassed)
if err := r.Client.Update(ctx, downstream); err != nil {
return nil, err
}
}

// see if the package needs updating due to an upstream change
Expand Down Expand Up @@ -423,9 +464,20 @@ func (r *PackageVariantReconciler) findAndUpdateExistingRevisions(ctx context.Co

}
// Save the updated PackageRevisionResources

setPrReadinessGate(downstream, ConditionTypePipelinePassed)
setPrStatusCondition(downstream, conditionPipelineNotPassed)
if err := r.Client.Update(ctx, downstream); err != nil {
return nil, err
}
if err := r.updatePackageResources(ctx, prr, pv); err != nil {
return nil, err
}

setPrStatusCondition(downstream, conditionPipelinePassed)
if err := r.Client.Update(ctx, downstream); err != nil {
return nil, err
}
}
}
return downstreams, nil
Expand Down Expand Up @@ -694,8 +746,15 @@ func (r *PackageVariantReconciler) updateDraft(ctx context.Context,
updateTask.Update.Upstream.UpstreamRef.Name = newUpstreamPR.Name
draft.Spec.Tasks = append(tasks, updateTask)

err := r.Client.Update(ctx, draft)
if err != nil {
setPrReadinessGate(draft, ConditionTypePipelinePassed)
setPrStatusCondition(draft, conditionPipelineNotPassed)

if err := r.Client.Update(ctx, draft); err != nil {
return nil, err
}

setPrStatusCondition(draft, conditionPipelinePassed)
if err := r.Client.Update(ctx, draft); err != nil {
return nil, err
}
return draft, nil
Expand Down Expand Up @@ -724,10 +783,33 @@ func setTargetStatusConditions(pv *api.PackageVariant, targets []*porchapi.Packa
Type: ConditionTypeReady,
Status: "True",
Reason: "NoErrors",
Message: "successfully ensured downstream package variant",
Message: "successfully ensured downstream target package revision",
})
}

func setPrReadinessGate(pr *porchapi.PackageRevision, conditionType string) {
for _, aGate := range pr.Spec.ReadinessGates {
if aGate.ConditionType == conditionType {
return
}
}

pr.Spec.ReadinessGates = append(pr.Spec.ReadinessGates, porchapi.ReadinessGate{
ConditionType: conditionType,
})
}

func setPrStatusCondition(pr *porchapi.PackageRevision, condition porchapi.Condition) {
for index, aCondition := range pr.Status.Conditions {
if aCondition.Type == condition.Type {
pr.Status.Conditions[index] = condition
return
}
}

pr.Status.Conditions = append(pr.Status.Conditions, condition)
}

// SetupWithManager sets up the controller with the Manager.
func (r *PackageVariantReconciler) SetupWithManager(mgr ctrl.Manager) error {
if err := api.AddToScheme(mgr.GetScheme()); err != nil {
Expand Down Expand Up @@ -1019,7 +1101,7 @@ func ensureKRMFunctions(pv *api.PackageVariant,
}

// update kptfile
prr.Spec.Resources[kptfilev1.KptFileName] = kptfile.String()
prr.Spec.Resources[kptfilev1.KptFileName] = util.KubeObjectToYaml(kptfile)

return nil
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2023 The kpt and Nephio Authors
# Copyright 2023-2024 The kpt and Nephio Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -28,7 +28,7 @@ spec:
- foo-01
- foo-02
- foo-03
name: cluster-02
- name: cluster-02
template:
downstream:
packageExpr: "target.package + '-' + repository.labels['env']"
Expand Down
4 changes: 2 additions & 2 deletions func/internal/podevaluator.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022 The kpt and Nephio Authors
// Copyright 2022, 2024 The kpt and Nephio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -238,7 +238,7 @@ type imagePodAndGRPCClient struct {
func (pcm *podCacheManager) warmupCache(podTTLConfig string) error {
start := time.Now()
defer func() {
klog.Infof("cache warning is completed and it took %v", time.Since(start))
klog.Infof("cache warming is complete after %v", time.Since(start))
}()
content, err := os.ReadFile(podTTLConfig)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ func (cad *cadEngine) CreatePackageRevision(ctx context.Context, repositoryObj *
return nil, err
}
repoPkgRev.SetMeta(pkgRevMeta)

sent := cad.watcherManager.NotifyPackageRevisionChange(watch.Added, repoPkgRev)
klog.Infof("engine: sent %d for new PackageRevision %s/%s", sent, repoPkgRev.KubeObjectNamespace(), repoPkgRev.KubeObjectName())
return repoPkgRev, nil
Expand Down
44 changes: 34 additions & 10 deletions pkg/kpt/api/kptfile/v1/types.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2021 The kpt and Nephio Authors
// Copyright 2021, 2024 The kpt and Nephio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -20,6 +20,8 @@ package v1
import (
"fmt"

"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
"github.com/nephio-project/porch/pkg/util"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
Expand Down Expand Up @@ -76,13 +78,34 @@ type KptFile struct {
// Info contains metadata such as license, documentation, etc.
Info *PackageInfo `yaml:"info,omitempty" json:"info,omitempty"`

Status *Status `yaml:"status,omitempty" json:"status,omitempty"`

// Pipeline declares the pipeline of functions.
Pipeline *Pipeline `yaml:"pipeline,omitempty" json:"pipeline,omitempty"`

// Inventory contains parameters for the inventory object used in apply.
Inventory *Inventory `yaml:"inventory,omitempty" json:"inventory,omitempty"`
}

Status *Status `yaml:"status,omitempty" json:"status,omitempty"`
func FromKubeObject(kptfileKubeObject *fn.KubeObject) (KptFile, error) {
var apiKptfile KptFile
if err := kptfileKubeObject.As(&apiKptfile); err != nil {
return KptFile{}, err
}
return apiKptfile, nil
}

func (file *KptFile) ToYamlString() (string, error) {
b, err := yaml.MarshalWithOptions(file, &yaml.EncoderOptions{SeqIndent: yaml.WideSequenceStyle})
if err != nil {
return "", err
}
kptfileKubeObject, err := util.YamlToKubeObject(string(b))
if err != nil {
return "", err
}

return kptfileKubeObject.String(), nil
}

// OriginType defines the type of origin for a package.
Expand Down Expand Up @@ -210,6 +233,8 @@ type PackageInfo struct {
// Relative slash-delimited path to the license file (e.g. LICENSE.txt)
LicenseFile string `yaml:"licenseFile,omitempty" json:"licenseFile,omitempty"`

ReadinessGates []ReadinessGate `yaml:"readinessGates,omitempty" json:"readinessGates,omitempty"`

kispaljr marked this conversation as resolved.
Show resolved Hide resolved
// Description contains a short description of the package.
Description string `yaml:"description,omitempty" json:"description,omitempty"`

Expand All @@ -218,8 +243,6 @@ type PackageInfo struct {

// Man is the path to documentation about the package
Man string `yaml:"man,omitempty" json:"man,omitempty"`

ReadinessGates []ReadinessGate `yaml:"readinessGates,omitempty" json:"readinessGates,omitempty"`
}

type ReadinessGate struct {
Expand Down Expand Up @@ -289,6 +312,11 @@ func (p *Pipeline) IsEmpty() bool {
// Function specifies a KRM function.
// +kubebuilder:object:generate=true
type Function struct {

// `Name` is used to uniquely identify the function declaration
// this is primarily used for merging function declaration with upstream counterparts
Name string `yaml:"name,omitempty" json:"name,omitempty"`

// `Image` specifies the function container image.
// It can either be fully qualified, e.g.:
//
Expand Down Expand Up @@ -318,10 +346,6 @@ type Function struct {
// `ConfigMap` is a convenient way to specify a function config of kind ConfigMap.
ConfigMap map[string]string `yaml:"configMap,omitempty" json:"configMap,omitempty"`

// `Name` is used to uniquely identify the function declaration
// this is primarily used for merging function declaration with upstream counterparts
Name string `yaml:"name,omitempty" json:"name,omitempty"`

// `Selectors` are used to specify resources on which the function should be executed
// if not specified, all resources are selected
Selectors []Selector `yaml:"selectors,omitempty" json:"selectors,omitempty"`
Expand Down Expand Up @@ -386,9 +410,9 @@ type Condition struct {

Status ConditionStatus `yaml:"status" json:"status"`

Reason string `yaml:"reason,omitempty" json:"reason,omitempty"`

Message string `yaml:"message,omitempty" json:"message,omitempty"`

Reason string `yaml:"reason,omitempty" json:"reason,omitempty"`
}

type ConditionStatus string
Expand Down
9 changes: 4 additions & 5 deletions pkg/kpt/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (

internalpkg "github.com/nephio-project/porch/internal/kpt/pkg"
kptfilev1 "github.com/nephio-project/porch/pkg/kpt/api/kptfile/v1"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

// TODO: Accept a virtual filesystem or other package abstraction
Expand All @@ -38,12 +37,12 @@ func UpdateUpstream(kptfileContents string, name string, upstream kptfilev1.Upst
kptfile.Name = name
}

b, err := yaml.MarshalWithOptions(kptfile, &yaml.EncoderOptions{SeqIndent: yaml.WideSequenceStyle})
kptfileYaml, err := kptfile.ToYamlString()
if err != nil {
return "", fmt.Errorf("cannot save Kptfile: %w", err)
}

return string(b), nil
return kptfileYaml, nil
}

func UpdateName(kptfileContents string, name string) (string, error) {
Expand All @@ -55,12 +54,12 @@ func UpdateName(kptfileContents string, name string) (string, error) {
// update the name of the package
kptfile.Name = name

b, err := yaml.MarshalWithOptions(kptfile, &yaml.EncoderOptions{SeqIndent: yaml.WideSequenceStyle})
kptfileYaml, err := kptfile.ToYamlString()
if err != nil {
return "", fmt.Errorf("cannot save Kptfile: %w", err)
}

return string(b), nil
return kptfileYaml, nil
}

// TODO: accept a virtual filesystem
Expand Down
Loading
Loading