diff --git a/controllers/actions.github.com/actions_client_resolver.go b/controllers/actions.github.com/actions_client_resolver.go new file mode 100644 index 00000000..5fdab8d2 --- /dev/null +++ b/controllers/actions.github.com/actions_client_resolver.go @@ -0,0 +1,248 @@ +package actionsgithubcom + +import ( + "context" + "fmt" + + "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" + "github.com/actions/actions-runner-controller/github/actions" + "github.com/actions/actions-runner-controller/vault" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type ActionsClientGetter interface { + GetActionsClientForAutoscalingRunnerSet(ctx context.Context, ars *v1alpha1.AutoscalingRunnerSet) (actions.ActionsService, error) + GetActionsClientForEphemeralRunnerSet(ctx context.Context, ers *v1alpha1.EphemeralRunnerSet) (actions.ActionsService, error) + GetActionsClientForEphemeralRunner(ctx context.Context, er *v1alpha1.EphemeralRunner) (actions.ActionsService, error) +} + +var ( + _ ActionsClientGetter = (*ActionsClientSecretResolver)(nil) + _ ActionsClientGetter = (*ActionsClientVaultResolver)(nil) +) + +type ActionsClientSecretResolver struct { + client.Client + actions.MultiClient +} + +func (r *ActionsClientSecretResolver) GetActionsClientForAutoscalingRunnerSet(ctx context.Context, ars *v1alpha1.AutoscalingRunnerSet) (actions.ActionsService, error) { + var configSecret corev1.Secret + if err := r.Get(ctx, types.NamespacedName{Namespace: ars.Namespace, Name: ars.Spec.GitHubConfigSecret}, &configSecret); err != nil { + return nil, fmt.Errorf("failed to find GitHub config secret: %w", err) + } + + opts, err := r.actionsClientOptionsForAutoscalingRunnerSet(ctx, ars) + if err != nil { + return nil, fmt.Errorf("failed to get actions client options: %w", err) + } + + return r.MultiClient.GetClientFromSecret( + ctx, + ars.Spec.GitHubConfigUrl, + ars.Namespace, + configSecret.Data, + opts..., + ) +} + +func (r *ActionsClientSecretResolver) actionsClientOptionsForAutoscalingRunnerSet(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 +} + +func (r *ActionsClientSecretResolver) GetActionsClientForEphemeralRunnerSet(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.actionsClientOptionsForEphemeralRunnerSet(ctx, rs) + if err != nil { + return nil, fmt.Errorf("failed to get actions client options: %w", err) + } + + return r.MultiClient.GetClientFromSecret( + ctx, + rs.Spec.EphemeralRunnerSpec.GitHubConfigUrl, + rs.Namespace, + secret.Data, + opts..., + ) +} + +func (r *ActionsClientSecretResolver) actionsClientOptionsForEphemeralRunnerSet(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 +} + +func (r *ActionsClientSecretResolver) GetActionsClientForEphemeralRunner(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.actionsClientOptionsForEphemeralRunner(ctx, runner) + if err != nil { + return nil, fmt.Errorf("failed to get actions client options: %w", err) + } + + return r.MultiClient.GetClientFromSecret( + ctx, + runner.Spec.GitHubConfigUrl, + runner.Namespace, + secret.Data, + opts..., + ) +} + +func (r *ActionsClientSecretResolver) actionsClientOptionsForEphemeralRunner(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 +} + +type ActionsClientVaultResolver struct { + vault.Vault + actions.MultiClient +} + +func (r *ActionsClientVaultResolver) GetActionsClientForAutoscalingRunnerSet(ctx context.Context, ars *v1alpha1.AutoscalingRunnerSet) (actions.ActionsService, error) { + panic("todo") +} + +func (r *ActionsClientVaultResolver) GetActionsClientForEphemeralRunnerSet(ctx context.Context, ers *v1alpha1.EphemeralRunnerSet) (actions.ActionsService, error) { + panic("todo") +} + +func (r *ActionsClientVaultResolver) GetActionsClientForEphemeralRunner(ctx context.Context, er *v1alpha1.EphemeralRunner) (actions.ActionsService, error) { + panic("todo") +} diff --git a/controllers/actions.github.com/autoscalingrunnerset_controller.go b/controllers/actions.github.com/autoscalingrunnerset_controller.go index 21740ff6..3003a318 100644 --- a/controllers/actions.github.com/autoscalingrunnerset_controller.go +++ b/controllers/actions.github.com/autoscalingrunnerset_controller.go @@ -402,7 +402,7 @@ 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.ActionsClientGetter.GetActionsClientForAutoscalingRunnerSet(ctx, autoscalingRunnerSet) if len(autoscalingRunnerSet.Spec.RunnerScaleSetName) == 0 { autoscalingRunnerSet.Spec.RunnerScaleSetName = autoscalingRunnerSet.Name } @@ -498,7 +498,7 @@ func (r *AutoscalingRunnerSetReconciler) updateRunnerScaleSetRunnerGroup(ctx con return ctrl.Result{}, err } - actionsClient, err := r.actionsClientFor(ctx, autoscalingRunnerSet) + actionsClient, err := r.ActionsClientGetter.GetActionsClientForAutoscalingRunnerSet(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 +546,7 @@ func (r *AutoscalingRunnerSetReconciler) updateRunnerScaleSetName(ctx context.Co return ctrl.Result{}, nil } - actionsClient, err := r.actionsClientFor(ctx, autoscalingRunnerSet) + actionsClient, err := r.ActionsClientGetter.GetActionsClientForAutoscalingRunnerSet(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 +597,7 @@ func (r *AutoscalingRunnerSetReconciler) deleteRunnerScaleSet(ctx context.Contex return nil } - actionsClient, err := r.actionsClientFor(ctx, autoscalingRunnerSet) + actionsClient, err := r.ActionsClientGetter.GetActionsClientForAutoscalingRunnerSet(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 +676,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). diff --git a/controllers/actions.github.com/autoscalingrunnerset_controller_test.go b/controllers/actions.github.com/autoscalingrunnerset_controller_test.go index b3002470..0f7c03fd 100644 --- a/controllers/actions.github.com/autoscalingrunnerset_controller_test.go +++ b/controllers/actions.github.com/autoscalingrunnerset_controller_test.go @@ -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{ + ActionsClientGetter: &ActionsClientSecretResolver{ + Client: 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{ + ActionsClientGetter: &ActionsClientSecretResolver{ + Client: 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{ + ActionsClientGetter: &ActionsClientSecretResolver{ + Client: 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{ + ActionsClientGetter: &ActionsClientSecretResolver{ + Client: 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{ + ActionsClientGetter: &ActionsClientSecretResolver{ + Client: 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.ResourceBuilder.ActionsClientGetter = &ActionsClientSecretResolver{ + Client: k8sClient, + MultiClient: actions.NewMultiClient(logr.Discard()), + } certsFolder := filepath.Join( "../../", @@ -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{ + ActionsClientGetter: &ActionsClientSecretResolver{ + Client: 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{ + ActionsClientGetter: &ActionsClientSecretResolver{ + Client: 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{ + ActionsClientGetter: &ActionsClientSecretResolver{ + Client: k8sClient, + MultiClient: fake.NewMultiClient(), + }, + }, } err := controller.SetupWithManager(mgr) Expect(err).NotTo(HaveOccurred(), "failed to setup controller") diff --git a/controllers/actions.github.com/ephemeralrunnerset_controller.go b/controllers/actions.github.com/ephemeralrunnerset_controller.go index 773e1286..d5975adc 100644 --- a/controllers/actions.github.com/ephemeralrunnerset_controller.go +++ b/controllers/actions.github.com/ephemeralrunnerset_controller.go @@ -331,7 +331,7 @@ func (r *EphemeralRunnerSetReconciler) cleanUpEphemeralRunners(ctx context.Conte return false, nil } - actionsClient, err := r.actionsClientFor(ctx, ephemeralRunnerSet) + actionsClient, err := r.ActionsClientGetter.GetActionsClientForEphemeralRunnerSet(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.ActionsClientGetter.GetActionsClientForEphemeralRunnerSet(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). diff --git a/controllers/actions.github.com/ephemeralrunnerset_controller_test.go b/controllers/actions.github.com/ephemeralrunnerset_controller_test.go index 665279e8..32e2bb0a 100644 --- a/controllers/actions.github.com/ephemeralrunnerset_controller_test.go +++ b/controllers/actions.github.com/ephemeralrunnerset_controller_test.go @@ -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{ + ActionsClientGetter: &ActionsClientSecretResolver{ + Client: 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{ + ActionsClientGetter: &ActionsClientSecretResolver{ + Client: 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{ + ActionsClientGetter: &ActionsClientSecretResolver{ + Client: mgr.GetClient(), + MultiClient: actions.NewMultiClient(logr.Discard()), + }, + }, } err = controller.SetupWithManager(mgr) Expect(err).NotTo(HaveOccurred(), "failed to setup controller") diff --git a/controllers/actions.github.com/resourcebuilder.go b/controllers/actions.github.com/resourcebuilder.go index 7fe8febf..84158d06 100644 --- a/controllers/actions.github.com/resourcebuilder.go +++ b/controllers/actions.github.com/resourcebuilder.go @@ -72,6 +72,7 @@ func SetListenerEntrypoint(entrypoint string) { type ResourceBuilder struct { ExcludeLabelPropagationPrefixes []string + ActionsClientGetter } // boolPtr returns a pointer to a bool value diff --git a/controllers/actions.summerwind.net/autoscaling.go b/controllers/actions.summerwind.net/autoscaling.go index 677041c7..1804bf28 100644 --- a/controllers/actions.summerwind.net/autoscaling.go +++ b/controllers/actions.summerwind.net/autoscaling.go @@ -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 { diff --git a/go.mod b/go.mod index 12f60832..47da2893 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,9 @@ module github.com/actions/actions-runner-controller go 1.24.3 require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 + github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0 github.com/bradleyfalzon/ghinstallation/v2 v2.14.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/evanphx/json-patch v5.9.11+incompatible @@ -39,6 +42,9 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/BurntSushi/toml v1.4.0 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect @@ -95,6 +101,7 @@ require ( github.com/go-sql-driver/mysql v1.9.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/gonvenience/bunt v1.4.0 // indirect github.com/gonvenience/idem v0.0.1 // indirect @@ -121,6 +128,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 // indirect @@ -134,6 +142,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pquerna/otp v1.4.0 // indirect diff --git a/go.sum b/go.sum index 411b620c..c3e46c6c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,22 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go v51.0.0+incompatible h1:p7blnyJSjJqf5jflHbSGhIhEpXIgIFmYZNg5uwqweso= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0 h1:xnO4sFyG8UH2fElBkcqLTOZsAajvKfnSlgBBW8dXYjw= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0/go.mod h1:XD3DIOOVgBCO03OleB1fHjgktVRFxlT++KwKgIOewdM= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= @@ -97,6 +114,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= @@ -134,6 +153,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -215,6 +236,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= @@ -269,6 +292,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU= github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -284,6 +309,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= +github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -355,6 +382,7 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= diff --git a/main.go b/main.go index 61b68a9e..f30c71b5 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,8 @@ import ( "github.com/actions/actions-runner-controller/github" "github.com/actions/actions-runner-controller/github/actions" "github.com/actions/actions-runner-controller/logging" + "github.com/actions/actions-runner-controller/vault" + "github.com/actions/actions-runner-controller/vault/azurekeyvault" "github.com/kelseyhightower/envconfig" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" @@ -274,8 +276,18 @@ func main() { log.WithName("actions-clients"), ) + actionsClientGetter, err := newActionsClientGetter( + mgr.GetClient(), + actionsMultiClient, + ) + if err != nil { + log.Error(err, "unable to create actions client resolver") + os.Exit(1) + } + rb := actionsgithubcom.ResourceBuilder{ ExcludeLabelPropagationPrefixes: excludeLabelPropagationPrefixes, + ActionsClientGetter: actionsClientGetter, } if err = (&actionsgithubcom.AutoscalingRunnerSetReconciler{ @@ -308,7 +320,6 @@ func main() { Client: mgr.GetClient(), Log: log.WithName("EphemeralRunnerSet").WithValues("version", build.Version), Scheme: mgr.GetScheme(), - ActionsClient: actionsMultiClient, PublishMetrics: metricsAddr != "0", ResourceBuilder: rb, }).SetupWithManager(mgr); err != nil { @@ -492,3 +503,32 @@ func (s *commaSeparatedStringSlice) Set(value string) error { } return nil } + +func newActionsClientGetter(k8sClient client.Client, multiClient actions.MultiClient) (actionsgithubcom.ActionsClientGetter, error) { + vaultType := os.Getenv("CONTROLLER_MANAGER_VAULT_TYPE") + if vaultType == "" { + return &actionsgithubcom.ActionsClientSecretResolver{ + Client: k8sClient, + MultiClient: multiClient, + }, nil + } + + key := os.Getenv("CONTROLLER_MANAGER_VAULT_API_KEY") + var vault vault.Vault + switch vaultType { + case "azure": + v, err := azurekeyvault.New(azurekeyvault.Config{JWT: key}) + if err != nil { + return nil, fmt.Errorf("failed to create Azure Key Vault client: %w", err) + } + + vault = v + default: + return nil, fmt.Errorf("unsupported vault type: %q", vaultType) + } + + return &actionsgithubcom.ActionsClientVaultResolver{ + Vault: vault, + MultiClient: multiClient, + }, nil +} diff --git a/vault/azurekeyvault/azurekeyvault.go b/vault/azurekeyvault/azurekeyvault.go new file mode 100644 index 00000000..02846780 --- /dev/null +++ b/vault/azurekeyvault/azurekeyvault.go @@ -0,0 +1,58 @@ +package azurekeyvault + +import ( + "context" + "fmt" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets" +) + +type AzureKeyVault struct { + client *azsecrets.Client +} + +type Config struct { + ClientID string + TenantID string + JWT string + URL string +} + +func (c *Config) getAssertion(ctx context.Context) (string, error) { + return c.JWT, nil +} + +func New(cfg Config) (*AzureKeyVault, error) { + cred, err := azidentity.NewClientAssertionCredential( + cfg.TenantID, + cfg.ClientID, + cfg.getAssertion, + &azidentity.ClientAssertionCredentialOptions{ + ClientOptions: azcore.ClientOptions{}, + }, + ) + if err != nil { + return nil, fmt.Errorf("failed to create client assertion credential: %w", err) + } + + client, err := azsecrets.NewClient(cfg.URL, cred, nil) + if err != nil { + return nil, fmt.Errorf("failed to initialize keyvault client: %w", err) + } + + return &AzureKeyVault{client: client}, nil +} + +func (v *AzureKeyVault) GetSecret(ctx context.Context, name, version string) (string, error) { + secret, err := v.client.GetSecret(context.Background(), name, version, nil) + if err != nil { + return "", fmt.Errorf("failed to get secret: %w", err) + } + if secret.Value == nil { + return "", fmt.Errorf("secret value is nil") + } + + return *secret.Value, nil +} diff --git a/vault/vault.go b/vault/vault.go new file mode 100644 index 00000000..c0fc788e --- /dev/null +++ b/vault/vault.go @@ -0,0 +1,7 @@ +package vault + +import "context" + +type Vault interface { + GetSecret(ctx context.Context, name, version string) (string, error) +}