Release AutoscalingRunnerSet deletion when the GitHub config secret is gone (#4688)

Signed-off-by: Ankit Jha <ankit.jha@tradomate.one>
This commit is contained in:
Ankit Jha
2026-09-30 16:45:33 +02:00
committed by GitHub
parent 6a04f3c6d7
commit e14c5a01ea
4 changed files with 379 additions and 19 deletions
@@ -27,6 +27,7 @@ import (
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
"github.com/actions/actions-runner-controller/build"
"github.com/actions/actions-runner-controller/controllers/actions.github.com/secretresolver"
"github.com/actions/scaleset"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
@@ -1220,7 +1221,17 @@ func (r *AutoscalingRunnerSetReconciler) deleteRunnerScaleSet(ctx context.Contex
}
actionsClient, err := r.GetActionsService(ctx, autoscalingRunnerSet)
if err != nil {
switch {
case errors.Is(err, secretresolver.ErrNotFound):
// A secret or config map the client is built from is gone, from Kubernetes or the vault. Retrying cannot bring it back,
// so release the autoscaling runner set instead of leaving it stuck in Terminating.
logger.Error(
err,
"A secret or config map required to reach the Actions service no longer exists in Kubernetes or the configured vault. The runner scale set cannot be deregistered and must be deleted manually from the Actions service",
"runnerScaleSetId", runnerScaleSetID,
)
return r.removeRunnerScaleSetIDAnnotation(ctx, autoscalingRunnerSet, runnerScaleSetID, logger)
case err != nil:
logger.Error(err, "Failed to initialize Actions service client for updating a existing runner scale set")
return err
}
@@ -1235,11 +1246,7 @@ func (r *AutoscalingRunnerSetReconciler) deleteRunnerScaleSet(ctx context.Contex
return err
}
original := autoscalingRunnerSet.DeepCopy()
delete(autoscalingRunnerSet.Annotations, runnerScaleSetIDAnnotationKey)
if err := r.Patch(ctx, autoscalingRunnerSet, client.MergeFrom(original)); err != nil {
logger.Error(err, "Failed to remove runner scale set ID annotation after deleting the runner scale set", "runnerScaleSetId", runnerScaleSetID)
if err := r.removeRunnerScaleSetIDAnnotation(ctx, autoscalingRunnerSet, runnerScaleSetID, logger); err != nil {
return err
}
@@ -1247,6 +1254,18 @@ func (r *AutoscalingRunnerSetReconciler) deleteRunnerScaleSet(ctx context.Contex
return nil
}
func (r *AutoscalingRunnerSetReconciler) removeRunnerScaleSetIDAnnotation(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, runnerScaleSetID int, logger logr.Logger) error {
original := autoscalingRunnerSet.DeepCopy()
delete(autoscalingRunnerSet.Annotations, runnerScaleSetIDAnnotationKey)
if err := r.Patch(ctx, autoscalingRunnerSet, client.MergeFrom(original)); err != nil {
logger.Error(err, "Failed to remove runner scale set ID annotation", "runnerScaleSetId", runnerScaleSetID)
return err
}
return nil
}
func (r *AutoscalingRunnerSetReconciler) createEphemeralRunnerSet(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, log logr.Logger) (ctrl.Result, error) {
r.ResourceCache.ephemeralRunnerSet.Delete(autoscalingRunnerSet)
desiredRunnerSet, err := r.newEphemeralRunnerSet(autoscalingRunnerSet)
@@ -32,6 +32,7 @@ import (
"github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient"
scalefake "github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient/fake"
"github.com/actions/actions-runner-controller/controllers/actions.github.com/secretresolver"
"github.com/actions/actions-runner-controller/vault"
"github.com/actions/scaleset"
)
@@ -2853,6 +2854,184 @@ var _ = Describe("Test AutoscalingRunnerSet with a stale runner scale set", Orde
).Should(Succeed(), "the finalizer should be removed even though the runner scale set is already gone")
})
})
Context("When the GitHub config secret is gone before the AutoscalingRunnerSet is deleted", func() {
var ctx context.Context
var mgr ctrl.Manager
var autoscalingNS *corev1.Namespace
var configSecret *corev1.Secret
var autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet
BeforeEach(func() {
ctx = context.Background()
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
controller := &AutoscalingRunnerSetReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ResourceBuilder: ResourceBuilder{
ResourceCache: newTestResourceCache(),
SecretResolver: secretresolver.New(mgr.GetClient(), scalefake.NewMultiClient(
scalefake.WithClient(
scalefake.NewClient(
scalefake.WithGetRunnerGroupByName(&scaleset.RunnerGroup{ID: 1, Name: "testgroup"}, nil),
scalefake.WithGetRunnerScaleSetByIDFunc(func(_ context.Context, runnerScaleSetID int) (*scaleset.RunnerScaleSet, error) {
return &scaleset.RunnerScaleSet{ID: runnerScaleSetID, Name: "test-asrs", RunnerGroupID: 1, RunnerGroupName: "testgroup"}, nil
}),
scalefake.WithGetRunnerScaleSet(nil, nil),
scalefake.WithCreateRunnerScaleSet(&scaleset.RunnerScaleSet{ID: freshRunnerScaleSetID, Name: "test-asrs", RunnerGroupID: 1, RunnerGroupName: "testgroup"}, nil),
),
),
)),
},
}
Expect(controller.SetupWithManager(mgr)).To(Succeed(), "failed to setup controller")
startManagers(GinkgoT(), mgr)
autoscalingRunnerSet = newAutoscalingRunnerSet(autoscalingNS.Name, configSecret.Name, nil)
Expect(k8sClient.Create(ctx, autoscalingRunnerSet)).To(Succeed(), "failed to create AutoScalingRunnerSet")
Eventually(
func() (string, error) {
current := new(v1alpha1.AutoscalingRunnerSet)
if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(autoscalingRunnerSet), current); err != nil {
return "", err
}
return current.Annotations[runnerScaleSetIDAnnotationKey], nil
},
autoscalingRunnerSetTestTimeout,
autoscalingRunnerSetTestInterval,
).Should(Equal(strconv.Itoa(freshRunnerScaleSetID)), "the runner scale set should be registered")
})
arsGone := func() error {
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(autoscalingRunnerSet), new(v1alpha1.AutoscalingRunnerSet))
if errors.IsNotFound(err) {
return nil
}
if err != nil {
return err
}
return fmt.Errorf("AutoScalingRunnerSet is not deleted")
}
It("removes the finalizer instead of retrying forever", func() {
Expect(k8sClient.Delete(ctx, configSecret)).To(Succeed(), "failed to delete the GitHub config secret")
Expect(k8sClient.Delete(ctx, autoscalingRunnerSet)).To(Succeed(), "failed to delete AutoScalingRunnerSet")
Eventually(
arsGone,
autoscalingRunnerSetTestTimeout,
autoscalingRunnerSetTestInterval,
).Should(Succeed(), "a missing GitHub config secret should not keep the AutoScalingRunnerSet in Terminating")
})
It("keeps the finalizer when the Actions client fails for any other reason", func() {
broken := configSecret.DeepCopy()
broken.Data = map[string][]byte{}
Expect(k8sClient.Update(ctx, broken)).To(Succeed(), "failed to break the GitHub config secret")
Expect(k8sClient.Delete(ctx, autoscalingRunnerSet)).To(Succeed(), "failed to delete AutoScalingRunnerSet")
Consistently(
func() error {
return k8sClient.Get(ctx, client.ObjectKeyFromObject(autoscalingRunnerSet), new(v1alpha1.AutoscalingRunnerSet))
},
3*time.Second,
autoscalingRunnerSetTestInterval,
).Should(Succeed(), "a secret that exists but cannot be used should keep the AutoScalingRunnerSet in Terminating")
restored := new(corev1.Secret)
Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(configSecret), restored)).To(Succeed(), "failed to get the GitHub config secret")
restored.Data = configSecret.Data
Expect(k8sClient.Update(ctx, restored)).To(Succeed(), "failed to restore the GitHub config secret")
Eventually(
arsGone,
autoscalingRunnerSetTestTimeout,
autoscalingRunnerSetTestInterval,
).Should(Succeed(), "deletion should complete once the GitHub config secret is usable again")
})
})
Context("When the vault proxy credential secret is gone before the AutoscalingRunnerSet is deleted", func() {
It("removes the finalizer instead of retrying forever", func() {
ctx := context.Background()
autoscalingNS, mgr := createNamespace(GinkgoT(), k8sClient)
controller := &AutoscalingRunnerSetReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ResourceBuilder: ResourceBuilder{
ResourceCache: newTestResourceCache(),
SecretResolver: secretresolver.New(mgr.GetClient(), scalefake.NewMultiClient()),
},
}
Expect(controller.SetupWithManager(mgr)).To(Succeed(), "failed to setup controller")
startManagers(GinkgoT(), mgr)
proxySecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "vault-proxy-credentials", Namespace: autoscalingNS.Name},
Data: map[string][]byte{"username": []byte("user"), "password": []byte("pass")},
}
Expect(k8sClient.Create(ctx, proxySecret)).To(Succeed(), "failed to create the vault proxy credential secret")
// The vault is never reached: resolving the proxy credentials fails first.
autoscalingRunnerSet := newAutoscalingRunnerSet(autoscalingNS.Name, "github-config-in-vault", registeredAnnotations(freshRunnerScaleSetID))
autoscalingRunnerSet.Spec.VaultConfig = &v1alpha1.VaultConfig{
Type: vault.VaultTypeAzureKeyVault,
AzureKeyVault: &v1alpha1.AzureKeyVaultConfig{
URL: "https://vault.example.com",
TenantID: "tenant",
ClientID: "client",
CertificatePath: "/nonexistent/cert.pem",
},
Proxy: &v1alpha1.ProxyConfig{
HTTPS: &v1alpha1.ProxyServerConfig{
Url: "http://proxy.example.com:3128",
CredentialSecretRef: proxySecret.Name,
},
},
}
Expect(k8sClient.Create(ctx, autoscalingRunnerSet)).To(Succeed(), "failed to create AutoScalingRunnerSet")
Eventually(
func() (bool, error) {
current := new(v1alpha1.AutoscalingRunnerSet)
if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(autoscalingRunnerSet), current); err != nil {
return false, err
}
return controllerutil.ContainsFinalizer(current, autoscalingRunnerSetFinalizerName), nil
},
autoscalingRunnerSetTestTimeout,
autoscalingRunnerSetTestInterval,
).Should(BeTrue(), "the finalizer should be added")
Expect(k8sClient.Delete(ctx, proxySecret)).To(Succeed(), "failed to delete the vault proxy credential secret")
Expect(k8sClient.Delete(ctx, autoscalingRunnerSet)).To(Succeed(), "failed to delete AutoScalingRunnerSet")
Eventually(
func() error {
err := k8sClient.Get(ctx, client.ObjectKeyFromObject(autoscalingRunnerSet), new(v1alpha1.AutoscalingRunnerSet))
if errors.IsNotFound(err) {
return nil
}
if err != nil {
return err
}
return fmt.Errorf("AutoScalingRunnerSet is not deleted")
},
autoscalingRunnerSetTestTimeout,
autoscalingRunnerSetTestInterval,
).Should(Succeed(), "a missing vault proxy credential secret should not keep the AutoScalingRunnerSet in Terminating")
})
})
})
// testHoldFinalizer keeps an AutoscalingListener around after it has been
@@ -4,12 +4,14 @@ import (
"context"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"net/url"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
"github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient"
"github.com/actions/actions-runner-controller/controllers/actions.github.com/object"
@@ -17,10 +19,35 @@ import (
"github.com/actions/actions-runner-controller/vault/azurekeyvault"
"golang.org/x/net/http/httpproxy"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type secretResolverError string
func (e secretResolverError) Error() string { return string(e) }
// ErrNotFound marks a secret or config map that no longer exists, whichever driver resolved it.
const ErrNotFound = secretResolverError("not found")
// wrapKubernetesError tags a Kubernetes NotFound so callers need no knowledge of the driver.
func wrapKubernetesError(err error) error {
if kerrors.IsNotFound(err) {
return fmt.Errorf("%w: %w", ErrNotFound, err)
}
return err
}
// wrapAzureKeyVaultError tags only a missing Azure Key Vault secret, so a 403 or throttled read still requeues.
func wrapAzureKeyVaultError(err error) error {
var responseErr *azcore.ResponseError
if errors.As(err, &responseErr) && responseErr.StatusCode == http.StatusNotFound && responseErr.ErrorCode == "SecretNotFound" {
return fmt.Errorf("%w: %w", ErrNotFound, err)
}
return err
}
type SecretResolver struct {
k8sClient client.Client
multiClient multiclient.MultiClient
@@ -56,12 +83,12 @@ func New(k8sClient client.Client, scalesetMultiClient multiclient.MultiClient, o
func (sr *SecretResolver) GetAppConfig(ctx context.Context, obj object.ActionsGitHubObject) (*appconfig.AppConfig, error) {
resolver, err := sr.resolverForObject(ctx, obj)
if err != nil {
return nil, fmt.Errorf("failed to get resolver for object: %v", err)
return nil, fmt.Errorf("failed to get resolver for object: %w", err)
}
appConfig, err := resolver.appConfig(ctx, obj.GitHubConfigSecret())
if err != nil {
return nil, fmt.Errorf("failed to resolve app config: %v", err)
return nil, fmt.Errorf("failed to resolve app config: %w", err)
}
return appConfig, nil
@@ -70,12 +97,12 @@ func (sr *SecretResolver) GetAppConfig(ctx context.Context, obj object.ActionsGi
func (sr *SecretResolver) GetActionsService(ctx context.Context, obj object.ActionsGitHubObject) (multiclient.Client, error) {
resolver, err := sr.resolverForObject(ctx, obj)
if err != nil {
return nil, fmt.Errorf("failed to get resolver for object: %v", err)
return nil, fmt.Errorf("failed to get resolver for object: %w", err)
}
appConfig, err := resolver.appConfig(ctx, obj.GitHubConfigSecret())
if err != nil {
return nil, fmt.Errorf("failed to resolve app config: %v", err)
return nil, fmt.Errorf("failed to resolve app config: %w", err)
}
var proxyFunc func(req *http.Request) (*url.URL, error)
@@ -93,7 +120,7 @@ func (sr *SecretResolver) GetActionsService(ctx context.Context, obj object.Acti
if ref := proxy.HTTP.CredentialSecretRef; ref != "" {
u.User, err = resolver.proxyCredentials(ctx, ref)
if err != nil {
return nil, fmt.Errorf("failed to resolve proxy credentials: %v", err)
return nil, fmt.Errorf("failed to resolve proxy credentials: %w", err)
}
}
@@ -109,7 +136,7 @@ func (sr *SecretResolver) GetActionsService(ctx context.Context, obj object.Acti
if ref := proxy.HTTPS.CredentialSecretRef; ref != "" {
u.User, err = resolver.proxyCredentials(ctx, ref)
if err != nil {
return nil, fmt.Errorf("failed to resolve proxy credentials: %v", err)
return nil, fmt.Errorf("failed to resolve proxy credentials: %w", err)
}
}
@@ -134,7 +161,7 @@ func (sr *SecretResolver) GetActionsService(ctx context.Context, obj object.Acti
&configmap,
)
if err != nil {
return nil, fmt.Errorf("failed to get configmap %s: %w", name, err)
return nil, fmt.Errorf("failed to get configmap %s: %w", name, wrapKubernetesError(err))
}
return []byte(configmap.Data[key]), nil
@@ -173,12 +200,12 @@ func (sr *SecretResolver) resolverForObject(ctx context.Context, obj object.Acti
var secret corev1.Secret
err := sr.k8sClient.Get(ctx, types.NamespacedName{Name: s, Namespace: obj.GetNamespace()}, &secret)
if err != nil {
return nil, fmt.Errorf("failed to get secret %s: %w", s, err)
return nil, fmt.Errorf("failed to get secret %s: %w", s, wrapKubernetesError(err))
}
return &secret, nil
})
if err != nil {
return nil, fmt.Errorf("failed to create proxy config: %v", err)
return nil, fmt.Errorf("failed to create proxy config: %w", err)
}
proxy = p
}
@@ -225,7 +252,7 @@ func (r *k8sResolver) appConfig(ctx context.Context, key string) (*appconfig.App
nsName,
secret,
); err != nil {
return nil, fmt.Errorf("failed to get kubernetes secret: %q", nsName.String())
return nil, fmt.Errorf("failed to get kubernetes secret %q: %w", nsName.String(), wrapKubernetesError(err))
}
return appconfig.FromSecret(secret)
@@ -239,7 +266,7 @@ func (r *k8sResolver) proxyCredentials(ctx context.Context, key string) (*url.Us
nsName,
secret,
); err != nil {
return nil, fmt.Errorf("failed to get kubernetes secret: %q", nsName.String())
return nil, fmt.Errorf("failed to get kubernetes secret %q: %w", nsName.String(), wrapKubernetesError(err))
}
return url.UserPassword(
@@ -255,7 +282,7 @@ type vaultResolver struct {
func (r *vaultResolver) appConfig(ctx context.Context, key string) (*appconfig.AppConfig, error) {
val, err := r.vault.GetSecret(ctx, key)
if err != nil {
return nil, fmt.Errorf("failed to resolve secret: %v", err)
return nil, fmt.Errorf("failed to resolve secret: %w", wrapAzureKeyVaultError(err))
}
return appconfig.FromJSONString(val)
@@ -264,7 +291,7 @@ func (r *vaultResolver) appConfig(ctx context.Context, key string) (*appconfig.A
func (r *vaultResolver) proxyCredentials(ctx context.Context, key string) (*url.Userinfo, error) {
val, err := r.vault.GetSecret(ctx, key)
if err != nil {
return nil, fmt.Errorf("failed to resolve secret: %v", err)
return nil, fmt.Errorf("failed to resolve secret: %w", wrapAzureKeyVaultError(err))
}
type info struct {
@@ -0,0 +1,135 @@
package secretresolver
import (
"context"
"errors"
"net/http"
"testing"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
"github.com/actions/actions-runner-controller/vault"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
)
type fakeVault struct{ err error }
func (v fakeVault) GetSecret(context.Context, string) (string, error) { return "", v.err }
func TestWrapKubernetesError(t *testing.T) {
notFound := kerrors.NewNotFound(schema.GroupResource{Resource: "secrets"}, "github-config")
if err := wrapKubernetesError(notFound); !errors.Is(err, ErrNotFound) || !kerrors.IsNotFound(err) {
t.Fatalf("NotFound lost its identity: %v", err)
}
forbidden := kerrors.NewForbidden(schema.GroupResource{Resource: "secrets"}, "github-config", errors.New("denied"))
if err := wrapKubernetesError(forbidden); errors.Is(err, ErrNotFound) {
t.Fatalf("Forbidden reported as not found: %v", err)
}
}
func TestVaultResolverTagsOnlySecretNotFound(t *testing.T) {
tests := map[string]struct {
err error
notFound bool
}{
"404": {&azcore.ResponseError{StatusCode: http.StatusNotFound, ErrorCode: "SecretNotFound"}, true},
"404 wrapped": {errors.Join(errors.New("failed to get secret"), &azcore.ResponseError{StatusCode: http.StatusNotFound, ErrorCode: "SecretNotFound"}), true},
"404 other": {&azcore.ResponseError{StatusCode: http.StatusNotFound, ErrorCode: "VaultNotFound"}, false},
"403": {&azcore.ResponseError{StatusCode: http.StatusForbidden, ErrorCode: "Forbidden"}, false},
"network error": {errors.New("dial tcp: i/o timeout"), false},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
r := &vaultResolver{vault: fakeVault{err: tt.err}}
_, appErr := r.appConfig(context.Background(), "github-config")
_, proxyErr := r.proxyCredentials(context.Background(), "proxy")
for _, err := range []error{appErr, proxyErr} {
if err == nil {
t.Fatal("expected an error")
}
if got := errors.Is(err, ErrNotFound); got != tt.notFound {
t.Fatalf("errors.Is(err, ErrNotFound) = %v, want %v: %v", got, tt.notFound, err)
}
}
})
}
}
func TestGetActionsServiceTagsEveryMissingDependency(t *testing.T) {
const ns = "arc-runners"
configSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "github-config", Namespace: ns},
Data: map[string][]byte{"github_token": []byte("token")},
}
proxyWithCredentials := &v1alpha1.ProxyConfig{
HTTP: &v1alpha1.ProxyServerConfig{Url: "http://proxy.example.com:3128", CredentialSecretRef: "proxy-credentials"},
}
forbidden := interceptor.Funcs{
Get: func(ctx context.Context, c client.WithWatch, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
return kerrors.NewForbidden(schema.GroupResource{Resource: "secrets"}, key.Name, errors.New("denied"))
},
}
tests := map[string]struct {
spec v1alpha1.AutoscalingRunnerSetSpec
objects []client.Object
interceptor *interceptor.Funcs
notFound bool
}{
"github config secret missing": {
spec: v1alpha1.AutoscalingRunnerSetSpec{GitHubConfigSecret: "github-config"},
notFound: true,
},
"proxy credential secret missing": {
spec: v1alpha1.AutoscalingRunnerSetSpec{GitHubConfigSecret: "github-config", Proxy: proxyWithCredentials},
objects: []client.Object{configSecret},
notFound: true,
},
"tls config map missing": {
spec: v1alpha1.AutoscalingRunnerSetSpec{
GitHubConfigSecret: "github-config",
GitHubServerTLS: &v1alpha1.TLSConfig{CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: "ca"}, Key: "ca.crt"},
}},
},
objects: []client.Object{configSecret},
notFound: true,
},
"vault proxy credential secret missing": {
spec: v1alpha1.AutoscalingRunnerSetSpec{
GitHubConfigSecret: "github-config",
VaultConfig: &v1alpha1.VaultConfig{Type: vault.VaultTypeAzureKeyVault, Proxy: proxyWithCredentials},
},
notFound: true,
},
"forbidden is not missing": {
spec: v1alpha1.AutoscalingRunnerSetSpec{GitHubConfigSecret: "github-config"},
interceptor: &forbidden,
notFound: false,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
builder := fake.NewClientBuilder().WithScheme(clientgoscheme.Scheme).WithObjects(tt.objects...)
if tt.interceptor != nil {
builder = builder.WithInterceptorFuncs(*tt.interceptor)
}
ars := &v1alpha1.AutoscalingRunnerSet{ObjectMeta: metav1.ObjectMeta{Name: "ars", Namespace: ns}, Spec: tt.spec}
_, err := New(builder.Build(), nil).GetActionsService(context.Background(), ars)
if err == nil {
t.Fatal("expected an error")
}
if got := errors.Is(err, ErrNotFound); got != tt.notFound {
t.Fatalf("errors.Is(err, ErrNotFound) = %v, want %v: %v", got, tt.notFound, err)
}
})
}
}