Skip to content

Commit 03f0e3a

Browse files
authored
Update Repository Resource Status (#3020)
When repository interactions encounter errors, we update status of the repository with a failure condition. Likewise, on successful resync of the repository we set a success condition.
1 parent 58f1f32 commit 03f0e3a

File tree

7 files changed

+140
-12
lines changed

7 files changed

+140
-12
lines changed

porch/api/porchconfig/v1alpha1/config.porch.kpt.dev_repositories.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ spec:
2929
singular: repository
3030
scope: Namespaced
3131
versions:
32-
- name: v1alpha1
32+
- additionalPrinterColumns:
33+
- jsonPath: .status.conditions[?(@.type=='Ready')].status
34+
name: Ready
35+
type: string
36+
name: v1alpha1
3337
schema:
3438
openAPIV3Schema:
3539
description: Repository
@@ -338,6 +342,8 @@ spec:
338342
type: object
339343
served: true
340344
storage: true
345+
subresources:
346+
status: {}
341347
status:
342348
acceptedNames:
343349
kind: ""

porch/api/porchconfig/v1alpha1/types.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ import (
1919
)
2020

2121
//+kubebuilder:object:root=true
22+
//+kubebuilder:subresource:status
2223
//+kubebuilder:resource:path=repositories,singular=repository
24+
//+kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=='Ready')].status`
2325

2426
// Repository
2527
type Repository struct {
@@ -142,6 +144,16 @@ type FunctionRef struct {
142144
Name string `json:"name"`
143145
}
144146

147+
const (
148+
// Type of the Repository condition.
149+
RepositoryReady = "Ready"
150+
151+
// Reason for the condition is error.
152+
ReasonError = "Error"
153+
// Reason for the condition is the repository is ready.
154+
ReasonReady = "Ready"
155+
)
156+
145157
// RepositoryStatus defines the observed state of Repository
146158
type RepositoryStatus struct {
147159
// Conditions describes the reconciliation state of the object.

porch/apiserver/pkg/e2e/e2e_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"reflect"
2323
"strings"
2424
"testing"
25+
"time"
2526

2627
kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
2728
porchapi "github.com/GoogleContainerTools/kpt/porch/api/porch/v1alpha1"
@@ -30,6 +31,7 @@ import (
3031
"github.com/google/go-cmp/cmp"
3132
coreapi "k8s.io/api/core/v1"
3233
apierrors "k8s.io/apimachinery/pkg/api/errors"
34+
"k8s.io/apimachinery/pkg/api/meta"
3335
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3436
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
3537
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -1034,6 +1036,71 @@ func (t *PorchSuite) TestPodEvaluator(ctx context.Context) {
10341036
}
10351037
}
10361038

1039+
func (t *PorchSuite) TestRepositoryError(ctx context.Context) {
1040+
const (
1041+
repositoryName = "repo-with-error"
1042+
)
1043+
t.CreateF(ctx, &configapi.Repository{
1044+
TypeMeta: metav1.TypeMeta{
1045+
Kind: configapi.RepositoryGVK.Kind,
1046+
APIVersion: configapi.GroupVersion.Identifier(),
1047+
},
1048+
ObjectMeta: metav1.ObjectMeta{
1049+
Name: repositoryName,
1050+
Namespace: t.namespace,
1051+
},
1052+
Spec: configapi.RepositorySpec{
1053+
Description: "Repository With Error",
1054+
Type: configapi.RepositoryTypeGit,
1055+
Content: configapi.RepositoryContentPackage,
1056+
Git: &configapi.GitRepository{
1057+
// Use `incalid` domain: https://www.rfc-editor.org/rfc/rfc6761#section-6.4
1058+
Repo: "https://repo.invalid/repository.git",
1059+
},
1060+
},
1061+
})
1062+
t.Cleanup(func() {
1063+
t.DeleteL(ctx, &configapi.Repository{
1064+
ObjectMeta: metav1.ObjectMeta{
1065+
Name: repositoryName,
1066+
Namespace: t.namespace,
1067+
},
1068+
})
1069+
})
1070+
1071+
giveUp := time.Now().Add(60 * time.Second)
1072+
1073+
for {
1074+
if time.Now().After(giveUp) {
1075+
t.Errorf("Timed out waiting for Repository Condition")
1076+
break
1077+
}
1078+
1079+
time.Sleep(5 * time.Second)
1080+
1081+
var repository configapi.Repository
1082+
t.GetF(ctx, client.ObjectKey{
1083+
Namespace: t.namespace,
1084+
Name: repositoryName,
1085+
}, &repository)
1086+
1087+
available := meta.FindStatusCondition(repository.Status.Conditions, configapi.RepositoryReady)
1088+
if available == nil {
1089+
// Condition not yet set
1090+
t.Logf("Repository condition not yet available")
1091+
continue
1092+
}
1093+
1094+
if got, want := available.Status, metav1.ConditionFalse; got != want {
1095+
t.Errorf("Repository Available Condition Status; got %q, want %q", got, want)
1096+
}
1097+
if got, want := available.Reason, configapi.ReasonError; got != want {
1098+
t.Errorf("Repository Available Condition Reason: got %q, want %q", got, want)
1099+
}
1100+
break
1101+
}
1102+
}
1103+
10371104
func (t *PorchSuite) registerGitRepositoryF(ctx context.Context, repo, name string) {
10381105
t.CreateF(ctx, &configapi.Repository{
10391106
TypeMeta: metav1.TypeMeta{

porch/apiserver/pkg/registry/porch/background.go

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ import (
1919
"fmt"
2020
"time"
2121

22+
configapi "github.com/GoogleContainerTools/kpt/porch/api/porchconfig/v1alpha1"
23+
"github.com/GoogleContainerTools/kpt/porch/repository/pkg/cache"
24+
"k8s.io/apimachinery/pkg/api/meta"
2225
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2326
"k8s.io/apimachinery/pkg/watch"
2427
"k8s.io/klog/v2"
2528
"sigs.k8s.io/controller-runtime/pkg/client"
26-
27-
configapi "github.com/GoogleContainerTools/kpt/porch/api/porchconfig/v1alpha1"
28-
"github.com/GoogleContainerTools/kpt/porch/repository/pkg/cache"
2929
)
3030

3131
func RunBackground(ctx context.Context, coreClient client.WithWatch, cache *cache.Cache) {
@@ -161,15 +161,38 @@ func (b *background) runOnce(ctx context.Context) error {
161161
}
162162
}
163163

164-
b.cacheRepository(ctx, repo)
164+
if err := b.cacheRepository(ctx, repo); err != nil {
165+
klog.Errorf("Failed to cache repository: %v", err)
166+
}
165167
}
166168

167169
return nil
168170
}
169171

170172
func (b *background) cacheRepository(ctx context.Context, repo *configapi.Repository) error {
171-
if _, err := b.cache.OpenRepository(ctx, repo); err != nil {
172-
return fmt.Errorf("error opening repository: %w", err)
173+
var condition v1.Condition
174+
if _, err := b.cache.OpenRepository(ctx, repo); err == nil {
175+
condition = v1.Condition{
176+
Type: configapi.RepositoryReady,
177+
Status: v1.ConditionTrue,
178+
ObservedGeneration: repo.Generation,
179+
LastTransitionTime: v1.Now(),
180+
Reason: configapi.ReasonReady,
181+
}
182+
} else {
183+
condition = v1.Condition{
184+
Type: configapi.RepositoryReady,
185+
Status: v1.ConditionFalse,
186+
ObservedGeneration: repo.Generation,
187+
LastTransitionTime: v1.Now(),
188+
Reason: configapi.ReasonError,
189+
Message: err.Error(),
190+
}
191+
}
192+
193+
meta.SetStatusCondition(&repo.Status.Conditions, condition)
194+
if err := b.coreClient.Status().Update(ctx, repo); err != nil {
195+
return fmt.Errorf("error updating repository status: %w", err)
173196
}
174197
return nil
175198
}

porch/config/deploy/5-rbac.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ rules:
2525
["mutatingwebhookconfigurations", "validatingwebhookconfigurations"]
2626
verbs: ["get", "watch", "list"]
2727
- apiGroups: ["config.porch.kpt.dev"]
28-
resources: ["repositories"]
28+
resources: ["repositories", "repositories/status"]
2929
verbs: ["get", "list", "watch", "create", "update", "patch"]
3030
# Needed for priority and fairness
3131
- apiGroups: ["flowcontrol.apiserver.k8s.io"]

porch/repository/pkg/cache/cache.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ func (c *Cache) OpenRepository(ctx context.Context, repositorySpec *configapi.Re
109109
cr = newRepository(key, r)
110110
c.repositories[key] = cr
111111
}
112+
} else {
113+
// If there is an error from the background refresh goroutine, return it.
114+
if err := cr.getRefreshError(); err != nil {
115+
return nil, err
116+
}
112117
}
113118
return cr, nil
114119

porch/repository/pkg/cache/repository.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ type cachedRepository struct {
3838
cachedPackages []*cachedPackageRevision
3939
// TODO: Currently we support repositories with homogenous content (only packages xor functions). Model this more optimally?
4040
cachedFunctions []repository.Function
41+
// Error encountered on repository refresh by the refresh goroutine.
42+
// This is returned back by the cache to the background goroutine when it calls periodicall to resync repositories.
43+
refreshError error
4144
}
4245

4346
// We take advantage of the cache having a global view of all the packages
@@ -98,9 +101,16 @@ func (r *cachedRepository) ListFunctions(ctx context.Context) ([]repository.Func
98101
return functions, nil
99102
}
100103

104+
func (r *cachedRepository) getRefreshError() error {
105+
r.mutex.Lock()
106+
defer r.mutex.Unlock()
107+
return r.refreshError
108+
}
109+
101110
func (r *cachedRepository) getPackages(ctx context.Context, forceRefresh bool) ([]repository.PackageRevision, error) {
102111
r.mutex.Lock()
103112
packages := r.cachedPackages
113+
err := r.refreshError
104114
r.mutex.Unlock()
105115

106116
if forceRefresh {
@@ -109,17 +119,22 @@ func (r *cachedRepository) getPackages(ctx context.Context, forceRefresh bool) (
109119

110120
if packages == nil {
111121
// TODO: Avoid simultaneous fetches?
112-
p, err := r.repo.ListPackageRevisions(ctx)
113-
if err != nil {
114-
return nil, err
122+
var p []repository.PackageRevision
123+
p, err = r.repo.ListPackageRevisions(ctx)
124+
if err == nil {
125+
packages = toCachedPackageRevisionSlice(p)
115126
}
116-
packages = toCachedPackageRevisionSlice(p)
117127

118128
r.mutex.Lock()
119129
r.cachedPackages = packages
130+
r.refreshError = err
120131
r.mutex.Unlock()
121132
}
122133

134+
if err != nil {
135+
return nil, err
136+
}
137+
123138
return toPackageRevisionSlice(packages), nil
124139
}
125140

0 commit comments

Comments
 (0)