Azure Key Vault integration to resolve secrets (#4090)

This commit is contained in:
Nikola Jokic
2025-06-11 15:53:33 +02:00
committed by GitHub
parent d4af75d82e
commit e46c929241
48 changed files with 2013 additions and 599 deletions

View File

@@ -32,6 +32,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
v1alpha1 "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
"github.com/actions/actions-runner-controller/controllers/actions.github.com/metrics"
"github.com/actions/actions-runner-controller/github/actions"
hash "github.com/actions/actions-runner-controller/hash"
@@ -128,36 +129,19 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl.
return ctrl.Result{}, err
}
// Check if the GitHub config secret exists
secret := new(corev1.Secret)
if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, Name: autoscalingListener.Spec.GitHubConfigSecret}, secret); err != nil {
log.Error(err, "Failed to find GitHub config secret.",
"namespace", autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
"name", autoscalingListener.Spec.GitHubConfigSecret)
appConfig, err := r.GetAppConfig(ctx, &autoscalingRunnerSet)
if err != nil {
log.Error(
err,
"Failed to get app config for AutoscalingRunnerSet.",
"namespace",
autoscalingRunnerSet.Namespace,
"name",
autoscalingRunnerSet.GitHubConfigSecret,
)
return ctrl.Result{}, err
}
// Create a mirror secret in the same namespace as the AutoscalingListener
mirrorSecret := new(corev1.Secret)
if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: autoscalingListener.Name}, mirrorSecret); err != nil {
if !kerrors.IsNotFound(err) {
log.Error(err, "Unable to get listener secret mirror", "namespace", autoscalingListener.Namespace, "name", autoscalingListener.Name)
return ctrl.Result{}, err
}
// Create a mirror secret for the listener pod in the Controller namespace for listener pod to use
log.Info("Creating a mirror listener secret for the listener pod")
return r.createSecretsForListener(ctx, autoscalingListener, secret, log)
}
// make sure the mirror secret is up to date
mirrorSecretDataHash := mirrorSecret.Labels["secret-data-hash"]
secretDataHash := hash.ComputeTemplateHash(secret.Data)
if mirrorSecretDataHash != secretDataHash {
log.Info("Updating mirror listener secret for the listener pod", "mirrorSecretDataHash", mirrorSecretDataHash, "secretDataHash", secretDataHash)
return r.updateSecretsForListener(ctx, secret, mirrorSecret, log)
}
// Make sure the runner scale set listener service account is created for the listener pod in the controller namespace
serviceAccount := new(corev1.ServiceAccount)
if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: autoscalingListener.Name}, serviceAccount); err != nil {
@@ -239,7 +223,7 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl.
// Create a listener pod in the controller namespace
log.Info("Creating a listener pod")
return r.createListenerPod(ctx, &autoscalingRunnerSet, autoscalingListener, serviceAccount, mirrorSecret, log)
return r.createListenerPod(ctx, &autoscalingRunnerSet, autoscalingListener, serviceAccount, appConfig, log)
}
cs := listenerContainerStatus(listenerPod)
@@ -411,7 +395,7 @@ func (r *AutoscalingListenerReconciler) createServiceAccountForListener(ctx cont
return ctrl.Result{}, nil
}
func (r *AutoscalingListenerReconciler) createListenerPod(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, autoscalingListener *v1alpha1.AutoscalingListener, serviceAccount *corev1.ServiceAccount, secret *corev1.Secret, logger logr.Logger) (ctrl.Result, error) {
func (r *AutoscalingListenerReconciler) createListenerPod(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, autoscalingListener *v1alpha1.AutoscalingListener, serviceAccount *corev1.ServiceAccount, appConfig *appconfig.AppConfig, logger logr.Logger) (ctrl.Result, error) {
var envs []corev1.EnvVar
if autoscalingListener.Spec.Proxy != nil {
httpURL := corev1.EnvVar{
@@ -480,7 +464,7 @@ func (r *AutoscalingListenerReconciler) createListenerPod(ctx context.Context, a
logger.Info("Creating listener config secret")
podConfig, err := r.newScaleSetListenerConfig(autoscalingListener, secret, metricsConfig, cert)
podConfig, err := r.newScaleSetListenerConfig(autoscalingListener, appConfig, metricsConfig, cert)
if err != nil {
logger.Error(err, "Failed to build listener config secret")
return ctrl.Result{}, err
@@ -499,7 +483,7 @@ func (r *AutoscalingListenerReconciler) createListenerPod(ctx context.Context, a
return ctrl.Result{Requeue: true}, nil
}
newPod, err := r.newScaleSetListenerPod(autoscalingListener, &podConfig, serviceAccount, secret, metricsConfig, envs...)
newPod, err := r.newScaleSetListenerPod(autoscalingListener, &podConfig, serviceAccount, metricsConfig, envs...)
if err != nil {
logger.Error(err, "Failed to build listener pod")
return ctrl.Result{}, err
@@ -558,23 +542,6 @@ func (r *AutoscalingListenerReconciler) certificate(ctx context.Context, autosca
return certificate, nil
}
func (r *AutoscalingListenerReconciler) createSecretsForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, secret *corev1.Secret, logger logr.Logger) (ctrl.Result, error) {
newListenerSecret := r.newScaleSetListenerSecretMirror(autoscalingListener, secret)
if err := ctrl.SetControllerReference(autoscalingListener, newListenerSecret, r.Scheme); err != nil {
return ctrl.Result{}, err
}
logger.Info("Creating listener secret", "namespace", newListenerSecret.Namespace, "name", newListenerSecret.Name)
if err := r.Create(ctx, newListenerSecret); err != nil {
logger.Error(err, "Unable to create listener secret", "namespace", newListenerSecret.Namespace, "name", newListenerSecret.Name)
return ctrl.Result{}, err
}
logger.Info("Created listener secret", "namespace", newListenerSecret.Namespace, "name", newListenerSecret.Name)
return ctrl.Result{Requeue: true}, nil
}
func (r *AutoscalingListenerReconciler) createProxySecret(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, logger logr.Logger) (ctrl.Result, error) {
data, err := autoscalingListener.Spec.Proxy.ToSecretData(func(s string) (*corev1.Secret, error) {
var secret corev1.Secret
@@ -614,22 +581,6 @@ func (r *AutoscalingListenerReconciler) createProxySecret(ctx context.Context, a
return ctrl.Result{Requeue: true}, nil
}
func (r *AutoscalingListenerReconciler) updateSecretsForListener(ctx context.Context, secret *corev1.Secret, mirrorSecret *corev1.Secret, logger logr.Logger) (ctrl.Result, error) {
dataHash := hash.ComputeTemplateHash(secret.Data)
updatedMirrorSecret := mirrorSecret.DeepCopy()
updatedMirrorSecret.Labels["secret-data-hash"] = dataHash
updatedMirrorSecret.Data = secret.Data
logger.Info("Updating listener mirror secret", "namespace", updatedMirrorSecret.Namespace, "name", updatedMirrorSecret.Name, "hash", dataHash)
if err := r.Update(ctx, updatedMirrorSecret); err != nil {
logger.Error(err, "Unable to update listener mirror secret", "namespace", updatedMirrorSecret.Namespace, "name", updatedMirrorSecret.Name)
return ctrl.Result{}, err
}
logger.Info("Updated listener mirror secret", "namespace", updatedMirrorSecret.Namespace, "name", updatedMirrorSecret.Name, "hash", dataHash)
return ctrl.Result{Requeue: true}, nil
}
func (r *AutoscalingListenerReconciler) createRoleForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, logger logr.Logger) (ctrl.Result, error) {
newRole := r.newScaleSetListenerRole(autoscalingListener)

View File

@@ -14,7 +14,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
listenerconfig "github.com/actions/actions-runner-controller/cmd/ghalistener/config"
ghalistenerconfig "github.com/actions/actions-runner-controller/cmd/ghalistener/config"
"github.com/actions/actions-runner-controller/github/actions/fake"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
kerrors "k8s.io/apimachinery/pkg/api/errors"
@@ -43,10 +44,17 @@ var _ = Describe("Test AutoScalingListener controller", func() {
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
secretResolver := NewSecretResolver(mgr.GetClient(), fake.NewMultiClient())
rb := ResourceBuilder{
SecretResolver: secretResolver,
}
controller := &AutoscalingListenerReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ResourceBuilder: rb,
}
err := controller.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
@@ -452,10 +460,17 @@ var _ = Describe("Test AutoScalingListener customization", func() {
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
secretResolver := NewSecretResolver(mgr.GetClient(), fake.NewMultiClient())
rb := ResourceBuilder{
SecretResolver: secretResolver,
}
controller := &AutoscalingListenerReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ResourceBuilder: rb,
}
err := controller.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
@@ -725,11 +740,17 @@ var _ = Describe("Test AutoScalingListener controller with proxy", func() {
ctx = context.Background()
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
secretResolver := NewSecretResolver(mgr.GetClient(), fake.NewMultiClient())
rb := ResourceBuilder{
SecretResolver: secretResolver,
}
controller := &AutoscalingListenerReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ResourceBuilder: rb,
}
err := controller.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
@@ -922,10 +943,17 @@ var _ = Describe("Test AutoScalingListener controller with template modification
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
secretResolver := NewSecretResolver(mgr.GetClient(), fake.NewMultiClient())
rb := ResourceBuilder{
SecretResolver: secretResolver,
}
controller := &AutoscalingListenerReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ResourceBuilder: rb,
}
err := controller.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
@@ -1018,6 +1046,12 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
secretResolver := NewSecretResolver(mgr.GetClient(), fake.NewMultiClient())
rb := ResourceBuilder{
SecretResolver: secretResolver,
}
cert, err := os.ReadFile(filepath.Join(
"../../",
"github",
@@ -1039,9 +1073,10 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
Expect(err).NotTo(HaveOccurred(), "failed to create configmap with root CAs")
controller := &AutoscalingListenerReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ResourceBuilder: rb,
}
err = controller.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
@@ -1056,7 +1091,7 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
Spec: v1alpha1.AutoscalingRunnerSetSpec{
GitHubConfigUrl: "https://github.com/owner/repo",
GitHubConfigSecret: configSecret.Name,
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
GitHubServerTLS: &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
@@ -1092,7 +1127,7 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
Spec: v1alpha1.AutoscalingListenerSpec{
GitHubConfigUrl: "https://github.com/owner/repo",
GitHubConfigSecret: configSecret.Name,
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
GitHubServerTLS: &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
@@ -1136,7 +1171,7 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
g.Expect(config.Data["config.json"]).ToNot(BeEmpty(), "listener configuration file should not be empty")
var listenerConfig listenerconfig.Config
var listenerConfig ghalistenerconfig.Config
err = json.Unmarshal(config.Data["config.json"], &listenerConfig)
g.Expect(err).NotTo(HaveOccurred(), "failed to parse listener configuration file")

View File

@@ -207,14 +207,6 @@ func (r *AutoscalingRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl
return r.updateRunnerScaleSetName(ctx, autoscalingRunnerSet, log)
}
secret := new(corev1.Secret)
if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingRunnerSet.Namespace, Name: autoscalingRunnerSet.Spec.GitHubConfigSecret}, secret); err != nil {
log.Error(err, "Failed to find GitHub config secret.",
"namespace", autoscalingRunnerSet.Namespace,
"name", autoscalingRunnerSet.Spec.GitHubConfigSecret)
return ctrl.Result{}, err
}
existingRunnerSets, err := r.listEphemeralRunnerSets(ctx, autoscalingRunnerSet)
if err != nil {
log.Error(err, "Failed to list existing ephemeral runner sets")
@@ -402,12 +394,12 @@ func (r *AutoscalingRunnerSetReconciler) removeFinalizersFromDependentResources(
func (r *AutoscalingRunnerSetReconciler) createRunnerScaleSet(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, logger logr.Logger) (ctrl.Result, error) {
logger.Info("Creating a new runner scale set")
actionsClient, err := r.actionsClientFor(ctx, autoscalingRunnerSet)
actionsClient, err := r.GetActionsService(ctx, autoscalingRunnerSet)
if len(autoscalingRunnerSet.Spec.RunnerScaleSetName) == 0 {
autoscalingRunnerSet.Spec.RunnerScaleSetName = autoscalingRunnerSet.Name
}
if err != nil {
logger.Error(err, "Failed to initialize Actions service client for creating a new runner scale set")
logger.Error(err, "Failed to initialize Actions service client for creating a new runner scale set", "error", err.Error())
return ctrl.Result{}, err
}
@@ -498,7 +490,7 @@ func (r *AutoscalingRunnerSetReconciler) updateRunnerScaleSetRunnerGroup(ctx con
return ctrl.Result{}, err
}
actionsClient, err := r.actionsClientFor(ctx, autoscalingRunnerSet)
actionsClient, err := r.GetActionsService(ctx, autoscalingRunnerSet)
if err != nil {
logger.Error(err, "Failed to initialize Actions service client for updating a existing runner scale set")
return ctrl.Result{}, err
@@ -546,7 +538,7 @@ func (r *AutoscalingRunnerSetReconciler) updateRunnerScaleSetName(ctx context.Co
return ctrl.Result{}, nil
}
actionsClient, err := r.actionsClientFor(ctx, autoscalingRunnerSet)
actionsClient, err := r.GetActionsService(ctx, autoscalingRunnerSet)
if err != nil {
logger.Error(err, "Failed to initialize Actions service client for updating a existing runner scale set")
return ctrl.Result{}, err
@@ -597,7 +589,7 @@ func (r *AutoscalingRunnerSetReconciler) deleteRunnerScaleSet(ctx context.Contex
return nil
}
actionsClient, err := r.actionsClientFor(ctx, autoscalingRunnerSet)
actionsClient, err := r.GetActionsService(ctx, autoscalingRunnerSet)
if err != nil {
logger.Error(err, "Failed to initialize Actions service client for updating a existing runner scale set")
return err
@@ -676,74 +668,6 @@ func (r *AutoscalingRunnerSetReconciler) listEphemeralRunnerSets(ctx context.Con
return &EphemeralRunnerSets{list: list}, nil
}
func (r *AutoscalingRunnerSetReconciler) actionsClientFor(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet) (actions.ActionsService, error) {
var configSecret corev1.Secret
if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingRunnerSet.Namespace, Name: autoscalingRunnerSet.Spec.GitHubConfigSecret}, &configSecret); err != nil {
return nil, fmt.Errorf("failed to find GitHub config secret: %w", err)
}
opts, err := r.actionsClientOptionsFor(ctx, autoscalingRunnerSet)
if err != nil {
return nil, fmt.Errorf("failed to get actions client options: %w", err)
}
return r.ActionsClient.GetClientFromSecret(
ctx,
autoscalingRunnerSet.Spec.GitHubConfigUrl,
autoscalingRunnerSet.Namespace,
configSecret.Data,
opts...,
)
}
func (r *AutoscalingRunnerSetReconciler) actionsClientOptionsFor(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet) ([]actions.ClientOption, error) {
var options []actions.ClientOption
if autoscalingRunnerSet.Spec.Proxy != nil {
proxyFunc, err := autoscalingRunnerSet.Spec.Proxy.ProxyFunc(func(s string) (*corev1.Secret, error) {
var secret corev1.Secret
err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingRunnerSet.Namespace, Name: s}, &secret)
if err != nil {
return nil, fmt.Errorf("failed to get proxy secret %s: %w", s, err)
}
return &secret, nil
})
if err != nil {
return nil, fmt.Errorf("failed to get proxy func: %w", err)
}
options = append(options, actions.WithProxy(proxyFunc))
}
tlsConfig := autoscalingRunnerSet.Spec.GitHubServerTLS
if tlsConfig != nil {
pool, err := tlsConfig.ToCertPool(func(name, key string) ([]byte, error) {
var configmap corev1.ConfigMap
err := r.Get(
ctx,
types.NamespacedName{
Namespace: autoscalingRunnerSet.Namespace,
Name: name,
},
&configmap,
)
if err != nil {
return nil, fmt.Errorf("failed to get configmap %s: %w", name, err)
}
return []byte(configmap.Data[key]), nil
})
if err != nil {
return nil, fmt.Errorf("failed to get tls config: %w", err)
}
options = append(options, actions.WithRootCAs(pool))
}
return options, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *AutoscalingRunnerSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).

View File

@@ -70,7 +70,12 @@ var _ = Describe("Test AutoScalingRunnerSet controller", Ordered, func() {
Log: logf.Log,
ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ActionsClient: fake.NewMultiClient(),
ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: k8sClient,
multiClient: fake.NewMultiClient(),
},
},
}
err := controller.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
@@ -677,33 +682,40 @@ var _ = Describe("Test AutoScalingController updates", Ordered, func() {
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
multiClient := fake.NewMultiClient(
fake.WithDefaultClient(
fake.NewFakeClient(
fake.WithUpdateRunnerScaleSet(
&actions.RunnerScaleSet{
Id: 1,
Name: "testset_update",
RunnerGroupId: 1,
RunnerGroupName: "testgroup",
Labels: []actions.Label{{Type: "test", Name: "test"}},
RunnerSetting: actions.RunnerSetting{},
CreatedOn: time.Now(),
RunnerJitConfigUrl: "test.test.test",
Statistics: nil,
},
nil,
),
),
nil,
),
)
controller := &AutoscalingRunnerSetReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ActionsClient: fake.NewMultiClient(
fake.WithDefaultClient(
fake.NewFakeClient(
fake.WithUpdateRunnerScaleSet(
&actions.RunnerScaleSet{
Id: 1,
Name: "testset_update",
RunnerGroupId: 1,
RunnerGroupName: "testgroup",
Labels: []actions.Label{{Type: "test", Name: "test"}},
RunnerSetting: actions.RunnerSetting{},
CreatedOn: time.Now(),
RunnerJitConfigUrl: "test.test.test",
Statistics: nil,
},
nil,
),
),
nil,
),
),
ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: k8sClient,
multiClient: multiClient,
},
},
}
err := controller.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
@@ -818,7 +830,12 @@ var _ = Describe("Test AutoscalingController creation failures", Ordered, func()
Log: logf.Log,
ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ActionsClient: fake.NewMultiClient(),
ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: k8sClient,
multiClient: fake.NewMultiClient(),
},
},
}
err := controller.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
@@ -937,14 +954,19 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
ctx = context.Background()
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
multiClient := actions.NewMultiClient(logr.Discard())
controller = &AutoscalingRunnerSetReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ActionsClient: actions.NewMultiClient(logr.Discard()),
ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: k8sClient,
multiClient: multiClient,
},
},
}
err := controller.SetupWithManager(mgr)
@@ -1127,7 +1149,12 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
Log: logf.Log,
ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ActionsClient: fake.NewMultiClient(),
ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: k8sClient,
multiClient: fake.NewMultiClient(),
},
},
}
err = controller.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
@@ -1136,7 +1163,10 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
})
It("should be able to make requests to a server using root CAs", func() {
controller.ActionsClient = actions.NewMultiClient(logr.Discard())
controller.SecretResolver = &SecretResolver{
k8sClient: k8sClient,
multiClient: actions.NewMultiClient(logr.Discard()),
}
certsFolder := filepath.Join(
"../../",
@@ -1171,7 +1201,7 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
Spec: v1alpha1.AutoscalingRunnerSetSpec{
GitHubConfigUrl: server.ConfigURLForOrg("my-org"),
GitHubConfigSecret: configSecret.Name,
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
GitHubServerTLS: &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
@@ -1224,7 +1254,7 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
Spec: v1alpha1.AutoscalingRunnerSetSpec{
GitHubConfigUrl: "https://github.com/owner/repo",
GitHubConfigSecret: configSecret.Name,
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
GitHubServerTLS: &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
@@ -1288,7 +1318,7 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
Spec: v1alpha1.AutoscalingRunnerSetSpec{
GitHubConfigUrl: "https://github.com/owner/repo",
GitHubConfigSecret: configSecret.Name,
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
GitHubServerTLS: &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
@@ -1361,7 +1391,12 @@ var _ = Describe("Test external permissions cleanup", Ordered, func() {
Log: logf.Log,
ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ActionsClient: fake.NewMultiClient(),
ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: k8sClient,
multiClient: fake.NewMultiClient(),
},
},
}
err := controller.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
@@ -1519,7 +1554,12 @@ var _ = Describe("Test external permissions cleanup", Ordered, func() {
Log: logf.Log,
ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ActionsClient: fake.NewMultiClient(),
ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: k8sClient,
multiClient: fake.NewMultiClient(),
},
},
}
err := controller.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
@@ -1727,7 +1767,12 @@ var _ = Describe("Test resource version and build version mismatch", func() {
Log: logf.Log,
ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ActionsClient: fake.NewMultiClient(),
ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: k8sClient,
multiClient: fake.NewMultiClient(),
},
},
}
err := controller.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")

View File

@@ -45,9 +45,8 @@ const (
// EphemeralRunnerReconciler reconciles a EphemeralRunner object
type EphemeralRunnerReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
ActionsClient actions.MultiClient
Log logr.Logger
Scheme *runtime.Scheme
ResourceBuilder
}
@@ -529,7 +528,7 @@ func (r *EphemeralRunnerReconciler) deletePodAsFailed(ctx context.Context, ephem
func (r *EphemeralRunnerReconciler) updateStatusWithRunnerConfig(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, log logr.Logger) (*ctrl.Result, error) {
// Runner is not registered with the service. We need to register it first
log.Info("Creating ephemeral runner JIT config")
actionsClient, err := r.actionsClientFor(ctx, ephemeralRunner)
actionsClient, err := r.GetActionsService(ctx, ephemeralRunner)
if err != nil {
return &ctrl.Result{}, fmt.Errorf("failed to get actions client for generating JIT config: %w", err)
}
@@ -753,77 +752,10 @@ func (r *EphemeralRunnerReconciler) updateRunStatusFromPod(ctx context.Context,
return nil
}
func (r *EphemeralRunnerReconciler) actionsClientFor(ctx context.Context, runner *v1alpha1.EphemeralRunner) (actions.ActionsService, error) {
secret := new(corev1.Secret)
if err := r.Get(ctx, types.NamespacedName{Namespace: runner.Namespace, Name: runner.Spec.GitHubConfigSecret}, secret); err != nil {
return nil, fmt.Errorf("failed to get secret: %w", err)
}
opts, err := r.actionsClientOptionsFor(ctx, runner)
if err != nil {
return nil, fmt.Errorf("failed to get actions client options: %w", err)
}
return r.ActionsClient.GetClientFromSecret(
ctx,
runner.Spec.GitHubConfigUrl,
runner.Namespace,
secret.Data,
opts...,
)
}
func (r *EphemeralRunnerReconciler) actionsClientOptionsFor(ctx context.Context, runner *v1alpha1.EphemeralRunner) ([]actions.ClientOption, error) {
var opts []actions.ClientOption
if runner.Spec.Proxy != nil {
proxyFunc, err := runner.Spec.Proxy.ProxyFunc(func(s string) (*corev1.Secret, error) {
var secret corev1.Secret
err := r.Get(ctx, types.NamespacedName{Namespace: runner.Namespace, Name: s}, &secret)
if err != nil {
return nil, fmt.Errorf("failed to get proxy secret %s: %w", s, err)
}
return &secret, nil
})
if err != nil {
return nil, fmt.Errorf("failed to get proxy func: %w", err)
}
opts = append(opts, actions.WithProxy(proxyFunc))
}
tlsConfig := runner.Spec.GitHubServerTLS
if tlsConfig != nil {
pool, err := tlsConfig.ToCertPool(func(name, key string) ([]byte, error) {
var configmap corev1.ConfigMap
err := r.Get(
ctx,
types.NamespacedName{
Namespace: runner.Namespace,
Name: name,
},
&configmap,
)
if err != nil {
return nil, fmt.Errorf("failed to get configmap %s: %w", name, err)
}
return []byte(configmap.Data[key]), nil
})
if err != nil {
return nil, fmt.Errorf("failed to get tls config: %w", err)
}
opts = append(opts, actions.WithRootCAs(pool))
}
return opts, nil
}
// runnerRegisteredWithService checks if the runner is still registered with the service
// Returns found=false and err=nil if ephemeral runner does not exist in GitHub service and should be deleted
func (r EphemeralRunnerReconciler) runnerRegisteredWithService(ctx context.Context, runner *v1alpha1.EphemeralRunner, log logr.Logger) (found bool, err error) {
actionsClient, err := r.actionsClientFor(ctx, runner)
actionsClient, err := r.GetActionsService(ctx, runner)
if err != nil {
return false, fmt.Errorf("failed to get Actions client for ScaleSet: %w", err)
}
@@ -850,7 +782,7 @@ func (r EphemeralRunnerReconciler) runnerRegisteredWithService(ctx context.Conte
}
func (r *EphemeralRunnerReconciler) deleteRunnerFromService(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, log logr.Logger) error {
client, err := r.actionsClientFor(ctx, ephemeralRunner)
client, err := r.GetActionsService(ctx, ephemeralRunner)
if err != nil {
return fmt.Errorf("failed to get actions client for runner: %w", err)
}

View File

@@ -107,10 +107,15 @@ var _ = Describe("EphemeralRunner", func() {
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
controller = &EphemeralRunnerReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ActionsClient: fake.NewMultiClient(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: mgr.GetClient(),
multiClient: fake.NewMultiClient(),
},
},
}
err := controller.SetupWithManager(mgr)
@@ -789,22 +794,27 @@ var _ = Describe("EphemeralRunner", func() {
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ActionsClient: fake.NewMultiClient(
fake.WithDefaultClient(
fake.NewFakeClient(
fake.WithGetRunner(
ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: mgr.GetClient(),
multiClient: fake.NewMultiClient(
fake.WithDefaultClient(
fake.NewFakeClient(
fake.WithGetRunner(
nil,
&actions.ActionsError{
StatusCode: http.StatusNotFound,
Err: &actions.ActionsExceptionError{
ExceptionName: "AgentNotFoundException",
},
},
),
),
nil,
&actions.ActionsError{
StatusCode: http.StatusNotFound,
Err: &actions.ActionsExceptionError{
ExceptionName: "AgentNotFoundException",
},
},
),
),
nil,
),
),
},
},
}
err := controller.SetupWithManager(mgr)
Expect(err).To(BeNil(), "failed to setup controller")
@@ -861,10 +871,15 @@ var _ = Describe("EphemeralRunner", func() {
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoScalingNS.Name)
controller = &EphemeralRunnerReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ActionsClient: fake.NewMultiClient(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: mgr.GetClient(),
multiClient: fake.NewMultiClient(),
},
},
}
err := controller.SetupWithManager(mgr)
Expect(err).To(BeNil(), "failed to setup controller")
@@ -874,7 +889,12 @@ var _ = Describe("EphemeralRunner", func() {
It("uses an actions client with proxy transport", func() {
// Use an actual client
controller.ActionsClient = actions.NewMultiClient(logr.Discard())
controller.ResourceBuilder = ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: mgr.GetClient(),
multiClient: actions.NewMultiClient(logr.Discard()),
},
}
proxySuccessfulllyCalled := false
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -1025,10 +1045,15 @@ var _ = Describe("EphemeralRunner", func() {
Expect(err).NotTo(HaveOccurred(), "failed to create configmap with root CAs")
controller = &EphemeralRunnerReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ActionsClient: fake.NewMultiClient(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: mgr.GetClient(),
multiClient: fake.NewMultiClient(),
},
},
}
err = controller.SetupWithManager(mgr)
@@ -1059,11 +1084,16 @@ var _ = Describe("EphemeralRunner", func() {
server.StartTLS()
// Use an actual client
controller.ActionsClient = actions.NewMultiClient(logr.Discard())
controller.ResourceBuilder = ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: mgr.GetClient(),
multiClient: actions.NewMultiClient(logr.Discard()),
},
}
ephemeralRunner := newExampleRunner("test-runner", autoScalingNS.Name, configSecret.Name)
ephemeralRunner.Spec.GitHubConfigUrl = server.ConfigURLForOrg("my-org")
ephemeralRunner.Spec.GitHubServerTLS = &v1alpha1.GitHubServerTLSConfig{
ephemeralRunner.Spec.GitHubServerTLS = &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{

View File

@@ -331,7 +331,7 @@ func (r *EphemeralRunnerSetReconciler) cleanUpEphemeralRunners(ctx context.Conte
return false, nil
}
actionsClient, err := r.actionsClientFor(ctx, ephemeralRunnerSet)
actionsClient, err := r.GetActionsService(ctx, ephemeralRunnerSet)
if err != nil {
return false, err
}
@@ -439,7 +439,7 @@ func (r *EphemeralRunnerSetReconciler) deleteIdleEphemeralRunners(ctx context.Co
log.Info("No pending or running ephemeral runners running at this time for scale down")
return nil
}
actionsClient, err := r.actionsClientFor(ctx, ephemeralRunnerSet)
actionsClient, err := r.GetActionsService(ctx, ephemeralRunnerSet)
if err != nil {
return fmt.Errorf("failed to create actions client for ephemeral runner replica set: %w", err)
}
@@ -502,73 +502,6 @@ func (r *EphemeralRunnerSetReconciler) deleteEphemeralRunnerWithActionsClient(ct
return true, nil
}
func (r *EphemeralRunnerSetReconciler) actionsClientFor(ctx context.Context, rs *v1alpha1.EphemeralRunnerSet) (actions.ActionsService, error) {
secret := new(corev1.Secret)
if err := r.Get(ctx, types.NamespacedName{Namespace: rs.Namespace, Name: rs.Spec.EphemeralRunnerSpec.GitHubConfigSecret}, secret); err != nil {
return nil, fmt.Errorf("failed to get secret: %w", err)
}
opts, err := r.actionsClientOptionsFor(ctx, rs)
if err != nil {
return nil, fmt.Errorf("failed to get actions client options: %w", err)
}
return r.ActionsClient.GetClientFromSecret(
ctx,
rs.Spec.EphemeralRunnerSpec.GitHubConfigUrl,
rs.Namespace,
secret.Data,
opts...,
)
}
func (r *EphemeralRunnerSetReconciler) actionsClientOptionsFor(ctx context.Context, rs *v1alpha1.EphemeralRunnerSet) ([]actions.ClientOption, error) {
var opts []actions.ClientOption
if rs.Spec.EphemeralRunnerSpec.Proxy != nil {
proxyFunc, err := rs.Spec.EphemeralRunnerSpec.Proxy.ProxyFunc(func(s string) (*corev1.Secret, error) {
var secret corev1.Secret
err := r.Get(ctx, types.NamespacedName{Namespace: rs.Namespace, Name: s}, &secret)
if err != nil {
return nil, fmt.Errorf("failed to get secret %s: %w", s, err)
}
return &secret, nil
})
if err != nil {
return nil, fmt.Errorf("failed to get proxy func: %w", err)
}
opts = append(opts, actions.WithProxy(proxyFunc))
}
tlsConfig := rs.Spec.EphemeralRunnerSpec.GitHubServerTLS
if tlsConfig != nil {
pool, err := tlsConfig.ToCertPool(func(name, key string) ([]byte, error) {
var configmap corev1.ConfigMap
err := r.Get(
ctx,
types.NamespacedName{
Namespace: rs.Namespace,
Name: name,
},
&configmap,
)
if err != nil {
return nil, fmt.Errorf("failed to get configmap %s: %w", name, err)
}
return []byte(configmap.Data[key]), nil
})
if err != nil {
return nil, fmt.Errorf("failed to get tls config: %w", err)
}
opts = append(opts, actions.WithRootCAs(pool))
}
return opts, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *EphemeralRunnerSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).

View File

@@ -54,10 +54,15 @@ var _ = Describe("Test EphemeralRunnerSet controller", func() {
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
controller := &EphemeralRunnerSetReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ActionsClient: fake.NewMultiClient(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: mgr.GetClient(),
multiClient: fake.NewMultiClient(),
},
},
}
err := controller.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
@@ -1104,10 +1109,15 @@ var _ = Describe("Test EphemeralRunnerSet controller with proxy settings", func(
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
controller := &EphemeralRunnerSetReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ActionsClient: actions.NewMultiClient(logr.Discard()),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: mgr.GetClient(),
multiClient: actions.NewMultiClient(logr.Discard()),
},
},
}
err := controller.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
@@ -1403,10 +1413,15 @@ var _ = Describe("Test EphemeralRunnerSet controller with custom root CA", func(
Expect(err).NotTo(HaveOccurred(), "failed to create configmap with root CAs")
controller := &EphemeralRunnerSetReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ActionsClient: actions.NewMultiClient(logr.Discard()),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logf.Log,
ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{
k8sClient: mgr.GetClient(),
multiClient: actions.NewMultiClient(logr.Discard()),
},
},
}
err = controller.SetupWithManager(mgr)
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
@@ -1445,7 +1460,7 @@ var _ = Describe("Test EphemeralRunnerSet controller with custom root CA", func(
EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{
GitHubConfigUrl: server.ConfigURLForOrg("my-org"),
GitHubConfigSecret: configSecret.Name,
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
GitHubServerTLS: &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{

View File

@@ -12,11 +12,13 @@ import (
"strings"
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
"github.com/actions/actions-runner-controller/build"
listenerconfig "github.com/actions/actions-runner-controller/cmd/ghalistener/config"
ghalistenerconfig "github.com/actions/actions-runner-controller/cmd/ghalistener/config"
"github.com/actions/actions-runner-controller/github/actions"
"github.com/actions/actions-runner-controller/hash"
"github.com/actions/actions-runner-controller/logging"
"github.com/actions/actions-runner-controller/vault/azurekeyvault"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -72,6 +74,7 @@ func SetListenerEntrypoint(entrypoint string) {
type ResourceBuilder struct {
ExcludeLabelPropagationPrefixes []string
*SecretResolver
}
// boolPtr returns a pointer to a bool value
@@ -121,6 +124,7 @@ func (b *ResourceBuilder) newAutoScalingListener(autoscalingRunnerSet *v1alpha1.
Spec: v1alpha1.AutoscalingListenerSpec{
GitHubConfigUrl: autoscalingRunnerSet.Spec.GitHubConfigUrl,
GitHubConfigSecret: autoscalingRunnerSet.Spec.GitHubConfigSecret,
VaultConfig: autoscalingRunnerSet.VaultConfig(),
RunnerScaleSetId: runnerScaleSetId,
AutoscalingRunnerSetNamespace: autoscalingRunnerSet.Namespace,
AutoscalingRunnerSetName: autoscalingRunnerSet.Name,
@@ -160,7 +164,7 @@ func (lm *listenerMetricsServerConfig) containerPort() (corev1.ContainerPort, er
}, nil
}
func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha1.AutoscalingListener, secret *corev1.Secret, metricsConfig *listenerMetricsServerConfig, cert string) (*corev1.Secret, error) {
func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha1.AutoscalingListener, appConfig *appconfig.AppConfig, metricsConfig *listenerMetricsServerConfig, cert string) (*corev1.Secret, error) {
var (
metricsAddr = ""
metricsEndpoint = ""
@@ -170,21 +174,8 @@ func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha
metricsEndpoint = metricsConfig.endpoint
}
var appInstallationID int64
if id, ok := secret.Data["github_app_installation_id"]; ok {
var err error
appInstallationID, err = strconv.ParseInt(string(id), 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to convert github_app_installation_id to int: %v", err)
}
}
config := listenerconfig.Config{
config := ghalistenerconfig.Config{
ConfigureUrl: autoscalingListener.Spec.GitHubConfigUrl,
AppID: string(secret.Data["github_app_id"]),
AppInstallationID: appInstallationID,
AppPrivateKey: string(secret.Data["github_app_private_key"]),
Token: string(secret.Data["github_token"]),
EphemeralRunnerSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
EphemeralRunnerSetName: autoscalingListener.Spec.EphemeralRunnerSetName,
MaxRunners: autoscalingListener.Spec.MaxRunners,
@@ -199,6 +190,20 @@ func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha
Metrics: autoscalingListener.Spec.Metrics,
}
vault := autoscalingListener.Spec.VaultConfig
if vault == nil {
config.AppConfig = appConfig
} else {
config.VaultType = vault.Type
config.VaultLookupKey = autoscalingListener.Spec.GitHubConfigSecret
config.AzureKeyVaultConfig = &azurekeyvault.Config{
TenantID: vault.AzureKeyVault.TenantID,
ClientID: vault.AzureKeyVault.ClientID,
URL: vault.AzureKeyVault.URL,
CertificatePath: vault.AzureKeyVault.CertificatePath,
}
}
if err := config.Validate(); err != nil {
return nil, fmt.Errorf("invalid listener config: %w", err)
}
@@ -219,7 +224,7 @@ func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha
}, nil
}
func (b *ResourceBuilder) newScaleSetListenerPod(autoscalingListener *v1alpha1.AutoscalingListener, podConfig *corev1.Secret, serviceAccount *corev1.ServiceAccount, secret *corev1.Secret, metricsConfig *listenerMetricsServerConfig, envs ...corev1.EnvVar) (*corev1.Pod, error) {
func (b *ResourceBuilder) newScaleSetListenerPod(autoscalingListener *v1alpha1.AutoscalingListener, podConfig *corev1.Secret, serviceAccount *corev1.ServiceAccount, metricsConfig *listenerMetricsServerConfig, envs ...corev1.EnvVar) (*corev1.Pod, error) {
listenerEnv := []corev1.EnvVar{
{
Name: "LISTENER_CONFIG_PATH",
@@ -490,25 +495,6 @@ func (b *ResourceBuilder) newScaleSetListenerRoleBinding(autoscalingListener *v1
return newRoleBinding
}
func (b *ResourceBuilder) newScaleSetListenerSecretMirror(autoscalingListener *v1alpha1.AutoscalingListener, secret *corev1.Secret) *corev1.Secret {
dataHash := hash.ComputeTemplateHash(&secret.Data)
newListenerSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: autoscalingListener.Name,
Namespace: autoscalingListener.Namespace,
Labels: b.mergeLabels(autoscalingListener.Labels, map[string]string{
LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
LabelKeyGitHubScaleSetName: autoscalingListener.Spec.AutoscalingRunnerSetName,
"secret-data-hash": dataHash,
}),
},
Data: secret.DeepCopy().Data,
}
return newListenerSecret
}
func (b *ResourceBuilder) newEphemeralRunnerSet(autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet) (*v1alpha1.EphemeralRunnerSet, error) {
runnerScaleSetId, err := strconv.Atoi(autoscalingRunnerSet.Annotations[runnerScaleSetIdAnnotationKey])
if err != nil {
@@ -561,6 +547,7 @@ func (b *ResourceBuilder) newEphemeralRunnerSet(autoscalingRunnerSet *v1alpha1.A
Proxy: autoscalingRunnerSet.Spec.Proxy,
GitHubServerTLS: autoscalingRunnerSet.Spec.GitHubServerTLS,
PodTemplateSpec: autoscalingRunnerSet.Spec.Template,
VaultConfig: autoscalingRunnerSet.VaultConfig(),
},
},
}
@@ -582,6 +569,7 @@ func (b *ResourceBuilder) newEphemeralRunner(ephemeralRunnerSet *v1alpha1.Epheme
for key, val := range ephemeralRunnerSet.Annotations {
annotations[key] = val
}
annotations[AnnotationKeyPatchID] = strconv.Itoa(ephemeralRunnerSet.Spec.PatchID)
return &v1alpha1.EphemeralRunner{
TypeMeta: metav1.TypeMeta{},

View File

@@ -82,12 +82,7 @@ func TestLabelPropagation(t *testing.T) {
Name: "test",
},
}
listenerSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
}
listenerPod, err := b.newScaleSetListenerPod(listener, &corev1.Secret{}, listenerServiceAccount, listenerSecret, nil)
listenerPod, err := b.newScaleSetListenerPod(listener, &corev1.Secret{}, listenerServiceAccount, nil)
require.NoError(t, err)
assert.Equal(t, listenerPod.Labels, listener.Labels)

View File

@@ -0,0 +1,280 @@
package actionsgithubcom
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
"github.com/actions/actions-runner-controller/github/actions"
"github.com/actions/actions-runner-controller/vault"
"github.com/actions/actions-runner-controller/vault/azurekeyvault"
"golang.org/x/net/http/httpproxy"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type SecretResolver struct {
k8sClient client.Client
multiClient actions.MultiClient
}
type SecretResolverOption func(*SecretResolver)
func NewSecretResolver(k8sClient client.Client, multiClient actions.MultiClient, opts ...SecretResolverOption) *SecretResolver {
if k8sClient == nil {
panic("k8sClient must not be nil")
}
secretResolver := &SecretResolver{
k8sClient: k8sClient,
multiClient: multiClient,
}
for _, opt := range opts {
opt(secretResolver)
}
return secretResolver
}
type ActionsGitHubObject interface {
client.Object
GitHubConfigUrl() string
GitHubConfigSecret() string
GitHubProxy() *v1alpha1.ProxyConfig
GitHubServerTLS() *v1alpha1.TLSConfig
VaultConfig() *v1alpha1.VaultConfig
VaultProxy() *v1alpha1.ProxyConfig
}
func (sr *SecretResolver) GetAppConfig(ctx context.Context, obj 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)
}
appConfig, err := resolver.appConfig(ctx, obj.GitHubConfigSecret())
if err != nil {
return nil, fmt.Errorf("failed to resolve app config: %v", err)
}
return appConfig, nil
}
func (sr *SecretResolver) GetActionsService(ctx context.Context, obj ActionsGitHubObject) (actions.ActionsService, error) {
resolver, err := sr.resolverForObject(ctx, obj)
if err != nil {
return nil, fmt.Errorf("failed to get resolver for object: %v", err)
}
appConfig, err := resolver.appConfig(ctx, obj.GitHubConfigSecret())
if err != nil {
return nil, fmt.Errorf("failed to resolve app config: %v", err)
}
var clientOptions []actions.ClientOption
if proxy := obj.GitHubProxy(); proxy != nil {
config := &httpproxy.Config{
NoProxy: strings.Join(proxy.NoProxy, ","),
}
if proxy.HTTP != nil {
u, err := url.Parse(proxy.HTTP.Url)
if err != nil {
return nil, fmt.Errorf("failed to parse proxy http url %q: %w", proxy.HTTP.Url, err)
}
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)
}
}
config.HTTPProxy = u.String()
}
if proxy.HTTPS != nil {
u, err := url.Parse(proxy.HTTPS.Url)
if err != nil {
return nil, fmt.Errorf("failed to parse proxy https url %q: %w", proxy.HTTPS.Url, err)
}
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)
}
}
config.HTTPSProxy = u.String()
}
proxyFunc := func(req *http.Request) (*url.URL, error) {
return config.ProxyFunc()(req.URL)
}
clientOptions = append(clientOptions, actions.WithProxy(proxyFunc))
}
tlsConfig := obj.GitHubServerTLS()
if tlsConfig != nil {
pool, err := tlsConfig.ToCertPool(func(name, key string) ([]byte, error) {
var configmap corev1.ConfigMap
err := sr.k8sClient.Get(
ctx,
types.NamespacedName{
Namespace: obj.GetNamespace(),
Name: name,
},
&configmap,
)
if err != nil {
return nil, fmt.Errorf("failed to get configmap %s: %w", name, err)
}
return []byte(configmap.Data[key]), nil
})
if err != nil {
return nil, fmt.Errorf("failed to get tls config: %w", err)
}
clientOptions = append(clientOptions, actions.WithRootCAs(pool))
}
return sr.multiClient.GetClientFor(
ctx,
obj.GitHubConfigUrl(),
appConfig,
obj.GetNamespace(),
clientOptions...,
)
}
func (sr *SecretResolver) resolverForObject(ctx context.Context, obj ActionsGitHubObject) (resolver, error) {
vaultConfig := obj.VaultConfig()
if vaultConfig == nil || vaultConfig.Type == "" {
return &k8sResolver{
namespace: obj.GetNamespace(),
client: sr.k8sClient,
}, nil
}
var proxy *httpproxy.Config
if vaultProxy := obj.VaultProxy(); vaultProxy != nil {
p, err := vaultProxy.ToHTTPProxyConfig(func(s string) (*corev1.Secret, error) {
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 &secret, nil
})
if err != nil {
return nil, fmt.Errorf("failed to create proxy config: %v", err)
}
proxy = p
}
switch vaultConfig.Type {
case vault.VaultTypeAzureKeyVault:
akv, err := azurekeyvault.New(azurekeyvault.Config{
TenantID: vaultConfig.AzureKeyVault.TenantID,
ClientID: vaultConfig.AzureKeyVault.ClientID,
URL: vaultConfig.AzureKeyVault.URL,
CertificatePath: vaultConfig.AzureKeyVault.CertificatePath,
Proxy: proxy,
})
if err != nil {
return nil, fmt.Errorf("failed to create Azure Key Vault client: %v", err)
}
return &vaultResolver{
vault: akv,
}, nil
default:
return nil, fmt.Errorf("unknown vault type %q", vaultConfig.Type)
}
}
type resolver interface {
appConfig(ctx context.Context, key string) (*appconfig.AppConfig, error)
proxyCredentials(ctx context.Context, key string) (*url.Userinfo, error)
}
type k8sResolver struct {
namespace string
client client.Client
}
func (r *k8sResolver) appConfig(ctx context.Context, key string) (*appconfig.AppConfig, error) {
nsName := types.NamespacedName{
Namespace: r.namespace,
Name: key,
}
secret := new(corev1.Secret)
if err := r.client.Get(
ctx,
nsName,
secret,
); err != nil {
return nil, fmt.Errorf("failed to get kubernetes secret: %q", nsName.String())
}
return appconfig.FromSecret(secret)
}
func (r *k8sResolver) proxyCredentials(ctx context.Context, key string) (*url.Userinfo, error) {
nsName := types.NamespacedName{Namespace: r.namespace, Name: key}
secret := new(corev1.Secret)
if err := r.client.Get(
ctx,
nsName,
secret,
); err != nil {
return nil, fmt.Errorf("failed to get kubernetes secret: %q", nsName.String())
}
return url.UserPassword(
string(secret.Data["username"]),
string(secret.Data["password"]),
), nil
}
type vaultResolver struct {
vault vault.Vault
}
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 appconfig.FromJSONString(val)
}
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)
}
type info struct {
Username string `json:"username"`
Password string `json:"password"`
}
var i info
if err := json.Unmarshal([]byte(val), &i); err != nil {
return nil, fmt.Errorf("failed to unmarshal info: %v", err)
}
return url.UserPassword(i.Username, i.Password), nil
}

View File

@@ -130,7 +130,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByQueuedAndInProgr
jobs, resp, err := ghc.Actions.ListWorkflowJobs(context.TODO(), user, repoName, runID, &opt)
if err != nil {
r.Log.Error(err, "Error listing workflow jobs")
return //err
return // err
}
allJobs = append(allJobs, jobs.Jobs...)
if resp.NextPage == 0 {