Skip to content
Merged
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
5 changes: 3 additions & 2 deletions .github/workflows/add-pr-label.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ jobs:
add-label:
name: Add `keep pr updated` Label
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v3
- uses: christianvuerings/add-labels@v1
with:
labels: |
keep pr updated
env:
GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
GITHUB_TOKEN: ${{ github.token }}
5 changes: 5 additions & 0 deletions changelog/v0.41.2/github-token.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
changelog:
- type: NON_USER_FACING
description: Use the workflow-provided GitHub token when labeling newly opened pull requests.
- type: NON_USER_FACING
description: Retry test CRD registration when a previous definition is still terminating.
35 changes: 32 additions & 3 deletions test/helpers/crd_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"sort"
"sync"
"time"

"github.com/solo-io/go-utils/contextutils"

Expand All @@ -20,6 +21,12 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"
)

const (
crdRegistrationPollInterval = 100 * time.Millisecond
crdRegistrationTimeout = 30 * time.Second
)

// The CRDRegistry is designed to be used only by tests
Expand Down Expand Up @@ -134,9 +141,31 @@ func (r *crdRegistry) registerCrd(ctx context.Context, gvk schema.GroupVersionKi
if err != nil {
return err
}
_, err = clientset.ApiextensionsV1().CustomResourceDefinitions().Create(context.TODO(), toRegister, metav1.CreateOptions{})
if err != nil && !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("failed to register crd: %v", err)
crdClient := clientset.ApiextensionsV1().CustomResourceDefinitions()
err = wait.PollUntilContextTimeout(ctx, crdRegistrationPollInterval, crdRegistrationTimeout, true, func(ctx context.Context) (bool, error) {
_, err := crdClient.Create(ctx, toRegister.DeepCopy(), metav1.CreateOptions{})
if err == nil {
return true, nil
}
if !apierrors.IsAlreadyExists(err) {
return false, fmt.Errorf("create crd %s: %w", toRegister.Name, err)
}

existing, err := crdClient.Get(ctx, toRegister.Name, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return false, nil
}
if err != nil {
return false, fmt.Errorf("get existing crd %s: %w", toRegister.Name, err)
}

// A CRD remains visible with its Established condition while deletion is
// in progress, but its resource endpoint may already return 404. Wait for
// it to disappear so the desired definition can be created cleanly.
return existing.DeletionTimestamp == nil, nil
})
if err != nil {
return fmt.Errorf("failed to register crd %s: %w", toRegister.Name, err)
}
return kubeutils.WaitForCrdActive(ctx, clientset, toRegister.Name)
}
Expand Down
83 changes: 83 additions & 0 deletions test/helpers/crd_registry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package helpers

import (
"context"
"testing"

"github.com/solo-io/solo-kit/pkg/api/v1/clients/kube/crd"
resourcev1 "github.com/solo-io/solo-kit/pkg/api/v1/clients/kube/crd/solo.io/v1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
kubetesting "k8s.io/client-go/testing"
)

func TestRegisterCrdRetriesWhilePreviousDefinitionIsTerminating(t *testing.T) {
testCrd := crd.Crd{
CrdMeta: crd.CrdMeta{
Plural: "widgets",
Group: "registration.test",
KindName: "Widget",
ShortName: "wd",
},
Version: crd.Version{
Version: "v1",
Type: &resourcev1.Resource{},
},
}

registry := &crdRegistry{}
if err := registry.addCrd(testCrd); err != nil {
t.Fatalf("add CRD to registry: %v", err)
}

deletionTime := metav1.Now()
terminating := &apiextv1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: testCrd.FullName(),
DeletionTimestamp: &deletionTime,
},
Status: apiextv1.CustomResourceDefinitionStatus{
Conditions: []apiextv1.CustomResourceDefinitionCondition{{
Type: apiextv1.Established,
Status: apiextv1.ConditionTrue,
}},
},
}

clientset := apiextfake.NewSimpleClientset()
createAttempts := 0
var registered *apiextv1.CustomResourceDefinition
clientset.Fake.PrependReactor("create", "customresourcedefinitions", func(action kubetesting.Action) (bool, runtime.Object, error) {
createAttempts++
if createAttempts == 1 {
return true, nil, apierrors.NewAlreadyExists(schema.GroupResource{
Group: apiextv1.GroupName,
Resource: "customresourcedefinitions",
}, testCrd.FullName())
}

registered = action.(kubetesting.CreateAction).GetObject().(*apiextv1.CustomResourceDefinition).DeepCopy()
registered.Status.Conditions = []apiextv1.CustomResourceDefinitionCondition{{
Type: apiextv1.Established,
Status: apiextv1.ConditionTrue,
}}
return true, registered, nil
})
clientset.Fake.PrependReactor("get", "customresourcedefinitions", func(kubetesting.Action) (bool, runtime.Object, error) {
if registered == nil {
return true, terminating.DeepCopy(), nil
}
return true, registered.DeepCopy(), nil
})

if err := registry.registerCrd(context.Background(), testCrd.GroupVersionKind(), clientset); err != nil {
t.Fatalf("register CRD: %v", err)
}
if createAttempts != 2 {
t.Fatalf("expected registration to retry once, got %d create attempts", createAttempts)
}
}