Moving to scaleset client for the controller (#4390)

This commit is contained in:
Nikola Jokic
2026-03-13 14:36:41 +01:00
committed by GitHub
parent 1d9f626c53
commit f99c6eda0b
27 changed files with 2695 additions and 360 deletions
+14 -8
View File
@@ -1,3 +1,13 @@
all: false
dir: "{{.InterfaceDir}}"
filename: mocks_test.go
force-file-write: true
formatter: goimports
log-level: info
structname: "{{.Mock}}{{.InterfaceName}}"
pkgname: "{{.SrcPackageName}}"
recursive: false
template: testify
packages: packages:
github.com/actions/actions-runner-controller/github/actions: github.com/actions/actions-runner-controller/github/actions:
config: config:
@@ -8,13 +18,9 @@ packages:
interfaces: interfaces:
ActionsService: ActionsService:
SessionService: SessionService:
github.com/actions/actions-runner-controller/cmd/ghalistener/metrics: github.com/actions/actions-runner-controller/cmd/ghalistener/metrics:
config: config:
inpackage: true all: true
dir: "{{.InterfaceDir}}" github.com/actions/actions-runner-controller/controllers/actions.github.com:
filename: "mocks_test.go" config:
pkgname: "metrics" all: true
interfaces:
Recorder:
ServerExporter:
@@ -111,7 +111,7 @@ type EphemeralRunnerSpec struct {
GitHubServerTLS *TLSConfig `json:"githubServerTLS,omitempty"` GitHubServerTLS *TLSConfig `json:"githubServerTLS,omitempty"`
// +required // +required
RunnerScaleSetId int `json:"runnerScaleSetId,omitempty"` RunnerScaleSetID int `json:"runnerScaleSetId,omitempty"`
// +optional // +optional
Proxy *ProxyConfig `json:"proxy,omitempty"` Proxy *ProxyConfig `json:"proxy,omitempty"`
+6 -36
View File
@@ -9,11 +9,11 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"strings"
"github.com/actions/actions-runner-controller/apis/actions.github.com/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/apis/actions.github.com/v1alpha1/appconfig"
"github.com/actions/actions-runner-controller/build" "github.com/actions/actions-runner-controller/build"
"github.com/actions/actions-runner-controller/logger"
"github.com/actions/actions-runner-controller/vault" "github.com/actions/actions-runner-controller/vault"
"github.com/actions/actions-runner-controller/vault/azurekeyvault" "github.com/actions/actions-runner-controller/vault/azurekeyvault"
"github.com/actions/scaleset" "github.com/actions/scaleset"
@@ -23,7 +23,7 @@ import (
const appName = "ghalistener" const appName = "ghalistener"
type Config struct { type Config struct {
ConfigureUrl string `json:"configure_url"` ConfigureURL string `json:"configure_url"`
VaultType vault.VaultType `json:"vault_type"` VaultType vault.VaultType `json:"vault_type"`
VaultLookupKey string `json:"vault_lookup_key"` VaultLookupKey string `json:"vault_lookup_key"`
// If the VaultType is set to "azure_key_vault", this field must be populated. // If the VaultType is set to "azure_key_vault", this field must be populated.
@@ -102,7 +102,7 @@ func Read(ctx context.Context, configPath string) (*Config, error) {
// Validate checks the configuration for errors. // Validate checks the configuration for errors.
func (c *Config) Validate() error { func (c *Config) Validate() error {
if len(c.ConfigureUrl) == 0 { if len(c.ConfigureURL) == 0 {
return fmt.Errorf("GitHubConfigUrl is not provided") return fmt.Errorf("GitHubConfigUrl is not provided")
} }
@@ -137,37 +137,7 @@ func (c *Config) Validate() error {
} }
func (c *Config) Logger() (*slog.Logger, error) { func (c *Config) Logger() (*slog.Logger, error) {
var lvl slog.Level return logger.New(c.LogLevel, c.LogFormat)
switch strings.ToLower(c.LogLevel) {
case "debug":
lvl = slog.LevelDebug
case "info":
lvl = slog.LevelInfo
case "warn":
lvl = slog.LevelWarn
case "error":
lvl = slog.LevelError
default:
return nil, fmt.Errorf("invalid log level: %s", c.LogLevel)
}
var logger *slog.Logger
switch c.LogFormat {
case "json":
logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true,
Level: lvl,
}))
case "text":
logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true,
Level: lvl,
}))
default:
return nil, fmt.Errorf("invalid log format: %s", c.LogFormat)
}
return logger.With("app", appName), nil
} }
func (c *Config) ActionsClient(logger *slog.Logger, clientOptions ...scaleset.HTTPOption) (*scaleset.Client, error) { func (c *Config) ActionsClient(logger *slog.Logger, clientOptions ...scaleset.HTTPOption) (*scaleset.Client, error) {
@@ -207,7 +177,7 @@ func (c *Config) ActionsClient(logger *slog.Logger, clientOptions ...scaleset.HT
case "": case "":
c, err := scaleset.NewClientWithGitHubApp( c, err := scaleset.NewClientWithGitHubApp(
scaleset.ClientWithGitHubAppConfig{ scaleset.ClientWithGitHubAppConfig{
GitHubConfigURL: c.ConfigureUrl, GitHubConfigURL: c.ConfigureURL,
GitHubAppAuth: scaleset.GitHubAppAuth{ GitHubAppAuth: scaleset.GitHubAppAuth{
ClientID: c.AppConfig.AppID, ClientID: c.AppConfig.AppID,
InstallationID: c.AppConfig.AppInstallationID, InstallationID: c.AppConfig.AppInstallationID,
@@ -224,7 +194,7 @@ func (c *Config) ActionsClient(logger *slog.Logger, clientOptions ...scaleset.HT
default: default:
c, err := scaleset.NewClientWithPersonalAccessToken( c, err := scaleset.NewClientWithPersonalAccessToken(
scaleset.NewClientWithPersonalAccessTokenConfig{ scaleset.NewClientWithPersonalAccessTokenConfig{
GitHubConfigURL: c.ConfigureUrl, GitHubConfigURL: c.ConfigureURL,
PersonalAccessToken: c.Token, PersonalAccessToken: c.Token,
SystemInfo: systemInfo, SystemInfo: systemInfo,
}, },
+4 -4
View File
@@ -54,7 +54,7 @@ func TestCustomerServerRootCA(t *testing.T) {
certsString = certsString + string(intermediate) certsString = certsString + string(intermediate)
config := config.Config{ config := config.Config{
ConfigureUrl: server.ConfigURLForOrg("myorg"), ConfigureURL: server.ConfigURLForOrg("myorg"),
ServerRootCA: certsString, ServerRootCA: certsString,
AppConfig: &appconfig.AppConfig{ AppConfig: &appconfig.AppConfig{
Token: "token", Token: "token",
@@ -85,7 +85,7 @@ func TestProxySettings(t *testing.T) {
defer os.Setenv("http_proxy", prevProxy) defer os.Setenv("http_proxy", prevProxy)
config := config.Config{ config := config.Config{
ConfigureUrl: "https://github.com/org/repo", ConfigureURL: "https://github.com/org/repo",
AppConfig: &appconfig.AppConfig{ AppConfig: &appconfig.AppConfig{
Token: "token", Token: "token",
}, },
@@ -103,7 +103,7 @@ func TestProxySettings(t *testing.T) {
defer os.Setenv("https_proxy", prevProxy) defer os.Setenv("https_proxy", prevProxy)
config := config.Config{ config := config.Config{
ConfigureUrl: "https://github.com/org/repo", ConfigureURL: "https://github.com/org/repo",
AppConfig: &appconfig.AppConfig{ AppConfig: &appconfig.AppConfig{
Token: "token", Token: "token",
}, },
@@ -124,7 +124,7 @@ func TestProxySettings(t *testing.T) {
defer os.Setenv("no_proxy", prevNoProxy) defer os.Setenv("no_proxy", prevNoProxy)
config := config.Config{ config := config.Config{
ConfigureUrl: "https://github.com/org/repo", ConfigureURL: "https://github.com/org/repo",
AppConfig: &appconfig.AppConfig{ AppConfig: &appconfig.AppConfig{
Token: "token", Token: "token",
}, },
@@ -10,7 +10,7 @@ import (
func TestConfigValidationMinMax(t *testing.T) { func TestConfigValidationMinMax(t *testing.T) {
config := &Config{ config := &Config{
ConfigureUrl: "github.com/some_org/some_repo", ConfigureURL: "github.com/some_org/some_repo",
EphemeralRunnerSetNamespace: "namespace", EphemeralRunnerSetNamespace: "namespace",
EphemeralRunnerSetName: "deployment", EphemeralRunnerSetName: "deployment",
RunnerScaleSetID: 1, RunnerScaleSetID: 1,
@@ -26,7 +26,7 @@ func TestConfigValidationMinMax(t *testing.T) {
func TestConfigValidationMissingToken(t *testing.T) { func TestConfigValidationMissingToken(t *testing.T) {
config := &Config{ config := &Config{
ConfigureUrl: "github.com/some_org/some_repo", ConfigureURL: "github.com/some_org/some_repo",
EphemeralRunnerSetNamespace: "namespace", EphemeralRunnerSetNamespace: "namespace",
EphemeralRunnerSetName: "deployment", EphemeralRunnerSetName: "deployment",
RunnerScaleSetID: 1, RunnerScaleSetID: 1,
@@ -46,7 +46,7 @@ func TestConfigValidationAppKey(t *testing.T) {
AppID: "1", AppID: "1",
AppInstallationID: 10, AppInstallationID: 10,
}, },
ConfigureUrl: "github.com/some_org/some_repo", ConfigureURL: "github.com/some_org/some_repo",
EphemeralRunnerSetNamespace: "namespace", EphemeralRunnerSetNamespace: "namespace",
EphemeralRunnerSetName: "deployment", EphemeralRunnerSetName: "deployment",
RunnerScaleSetID: 1, RunnerScaleSetID: 1,
@@ -63,7 +63,7 @@ func TestConfigValidationAppKey(t *testing.T) {
AppID: "Iv23f8doAlphaNumer1c", AppID: "Iv23f8doAlphaNumer1c",
AppInstallationID: 10, AppInstallationID: 10,
}, },
ConfigureUrl: "github.com/some_org/some_repo", ConfigureURL: "github.com/some_org/some_repo",
EphemeralRunnerSetNamespace: "namespace", EphemeralRunnerSetNamespace: "namespace",
EphemeralRunnerSetName: "deployment", EphemeralRunnerSetName: "deployment",
RunnerScaleSetID: 1, RunnerScaleSetID: 1,
@@ -82,7 +82,7 @@ func TestConfigValidationOnlyOneTypeOfCredentials(t *testing.T) {
AppPrivateKey: "asdf", AppPrivateKey: "asdf",
Token: "asdf", Token: "asdf",
}, },
ConfigureUrl: "github.com/some_org/some_repo", ConfigureURL: "github.com/some_org/some_repo",
EphemeralRunnerSetNamespace: "namespace", EphemeralRunnerSetNamespace: "namespace",
EphemeralRunnerSetName: "deployment", EphemeralRunnerSetName: "deployment",
RunnerScaleSetID: 1, RunnerScaleSetID: 1,
@@ -94,7 +94,7 @@ func TestConfigValidationOnlyOneTypeOfCredentials(t *testing.T) {
func TestConfigValidation(t *testing.T) { func TestConfigValidation(t *testing.T) {
config := &Config{ config := &Config{
ConfigureUrl: "https://github.com/actions", ConfigureURL: "https://github.com/actions",
EphemeralRunnerSetNamespace: "namespace", EphemeralRunnerSetNamespace: "namespace",
EphemeralRunnerSetName: "deployment", EphemeralRunnerSetName: "deployment",
RunnerScaleSetID: 1, RunnerScaleSetID: 1,
@@ -125,7 +125,7 @@ func TestConfigValidationConfigUrl(t *testing.T) {
func TestConfigValidationWithVaultConfig(t *testing.T) { func TestConfigValidationWithVaultConfig(t *testing.T) {
t.Run("valid", func(t *testing.T) { t.Run("valid", func(t *testing.T) {
config := &Config{ config := &Config{
ConfigureUrl: "https://github.com/actions", ConfigureURL: "https://github.com/actions",
EphemeralRunnerSetNamespace: "namespace", EphemeralRunnerSetNamespace: "namespace",
EphemeralRunnerSetName: "deployment", EphemeralRunnerSetName: "deployment",
RunnerScaleSetID: 1, RunnerScaleSetID: 1,
@@ -140,7 +140,7 @@ func TestConfigValidationWithVaultConfig(t *testing.T) {
t.Run("invalid vault type", func(t *testing.T) { t.Run("invalid vault type", func(t *testing.T) {
config := &Config{ config := &Config{
ConfigureUrl: "https://github.com/actions", ConfigureURL: "https://github.com/actions",
EphemeralRunnerSetNamespace: "namespace", EphemeralRunnerSetNamespace: "namespace",
EphemeralRunnerSetName: "deployment", EphemeralRunnerSetName: "deployment",
RunnerScaleSetID: 1, RunnerScaleSetID: 1,
@@ -155,7 +155,7 @@ func TestConfigValidationWithVaultConfig(t *testing.T) {
t.Run("vault type set without lookup key", func(t *testing.T) { t.Run("vault type set without lookup key", func(t *testing.T) {
config := &Config{ config := &Config{
ConfigureUrl: "https://github.com/actions", ConfigureURL: "https://github.com/actions",
EphemeralRunnerSetNamespace: "namespace", EphemeralRunnerSetNamespace: "namespace",
EphemeralRunnerSetName: "deployment", EphemeralRunnerSetName: "deployment",
RunnerScaleSetID: 1, RunnerScaleSetID: 1,
+1 -1
View File
@@ -40,7 +40,7 @@ func main() {
} }
func run(ctx context.Context, config *config.Config) error { func run(ctx context.Context, config *config.Config) error {
ghConfig, err := actions.ParseGitHubConfigFromURL(config.ConfigureUrl) ghConfig, err := actions.ParseGitHubConfigFromURL(config.ConfigureURL)
if err != nil { if err != nil {
return fmt.Errorf("failed to parse GitHub config from URL: %w", err) return fmt.Errorf("failed to parse GitHub config from URL: %w", err)
} }
@@ -15,7 +15,8 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/log" logf "sigs.k8s.io/controller-runtime/pkg/log"
ghalistenerconfig "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" scalefake "github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient/fake"
"github.com/actions/actions-runner-controller/controllers/actions.github.com/secretresolver"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
kerrors "k8s.io/apimachinery/pkg/api/errors" kerrors "k8s.io/apimachinery/pkg/api/errors"
@@ -43,7 +44,10 @@ var _ = Describe("Test AutoScalingListener controller", func() {
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient) autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name) configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
secretResolver := NewSecretResolver(mgr.GetClient(), fake.NewMultiClient()) secretResolver := secretresolver.New(
mgr.GetClient(),
scalefake.NewMultiClient(),
)
rb := ResourceBuilder{ rb := ResourceBuilder{
SecretResolver: secretResolver, SecretResolver: secretResolver,
@@ -459,7 +463,7 @@ var _ = Describe("Test AutoScalingListener customization", func() {
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient) autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name) configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
secretResolver := NewSecretResolver(mgr.GetClient(), fake.NewMultiClient()) secretResolver := secretresolver.New(mgr.GetClient(), scalefake.NewMultiClient())
rb := ResourceBuilder{ rb := ResourceBuilder{
SecretResolver: secretResolver, SecretResolver: secretResolver,
@@ -788,7 +792,7 @@ var _ = Describe("Test AutoScalingListener controller with proxy", func() {
ctx = context.Background() ctx = context.Background()
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient) autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name) configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
secretResolver := NewSecretResolver(mgr.GetClient(), fake.NewMultiClient()) secretResolver := secretresolver.New(mgr.GetClient(), scalefake.NewMultiClient())
rb := ResourceBuilder{ rb := ResourceBuilder{
SecretResolver: secretResolver, SecretResolver: secretResolver,
@@ -991,7 +995,7 @@ var _ = Describe("Test AutoScalingListener controller with template modification
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient) autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name) configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
secretResolver := NewSecretResolver(mgr.GetClient(), fake.NewMultiClient()) secretResolver := secretresolver.New(mgr.GetClient(), scalefake.NewMultiClient())
rb := ResourceBuilder{ rb := ResourceBuilder{
SecretResolver: secretResolver, SecretResolver: secretResolver,
@@ -1094,7 +1098,7 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient) autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name) configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
secretResolver := NewSecretResolver(mgr.GetClient(), fake.NewMultiClient()) secretResolver := secretresolver.New(mgr.GetClient(), scalefake.NewMultiClient())
rb := ResourceBuilder{ rb := ResourceBuilder{
SecretResolver: secretResolver, SecretResolver: secretResolver,
@@ -25,7 +25,7 @@ import (
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
"github.com/actions/actions-runner-controller/build" "github.com/actions/actions-runner-controller/build"
"github.com/actions/actions-runner-controller/github/actions" "github.com/actions/scaleset"
"github.com/go-logr/logr" "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
@@ -78,7 +78,6 @@ type AutoscalingRunnerSetReconciler struct {
DefaultRunnerScaleSetListenerImage string DefaultRunnerScaleSetListenerImage string
DefaultRunnerScaleSetListenerImagePullSecrets []string DefaultRunnerScaleSetListenerImagePullSecrets []string
UpdateStrategy UpdateStrategy UpdateStrategy UpdateStrategy
ActionsClient actions.MultiClient
ResourceBuilder ResourceBuilder
} }
@@ -427,17 +426,16 @@ func (r *AutoscalingRunnerSetReconciler) createRunnerScaleSet(ctx context.Contex
if runnerScaleSet == nil { if runnerScaleSet == nil {
runnerScaleSet, err = actionsClient.CreateRunnerScaleSet( runnerScaleSet, err = actionsClient.CreateRunnerScaleSet(
ctx, ctx,
&actions.RunnerScaleSet{ &scaleset.RunnerScaleSet{
Name: autoscalingRunnerSet.Spec.RunnerScaleSetName, Name: autoscalingRunnerSet.Spec.RunnerScaleSetName,
RunnerGroupId: runnerGroupID, RunnerGroupID: runnerGroupID,
Labels: []actions.Label{ Labels: []scaleset.Label{
{ {
Name: autoscalingRunnerSet.Spec.RunnerScaleSetName, Name: autoscalingRunnerSet.Spec.RunnerScaleSetName,
Type: "System", Type: "System",
}, },
}, },
RunnerSetting: actions.RunnerSetting{ RunnerSetting: scaleset.RunnerSetting{
Ephemeral: true,
DisableUpdate: true, DisableUpdate: true,
}, },
}) })
@@ -447,15 +445,11 @@ func (r *AutoscalingRunnerSetReconciler) createRunnerScaleSet(ctx context.Contex
} }
} }
actionsClient.SetUserAgent(actions.UserAgentInfo{ info := actionsClient.SystemInfo()
Version: build.Version, info.ScaleSetID = runnerScaleSet.ID
CommitSHA: build.CommitSHA, actionsClient.SetSystemInfo(info)
ScaleSetID: runnerScaleSet.Id,
HasProxy: autoscalingRunnerSet.Spec.Proxy != nil,
Subsystem: "controller",
})
logger.Info("Created/Reused a runner scale set", "id", runnerScaleSet.Id, "runnerGroupName", runnerScaleSet.RunnerGroupName) logger.Info("Created/Reused a runner scale set", "id", runnerScaleSet.ID, "runnerGroupName", runnerScaleSet.RunnerGroupName)
if autoscalingRunnerSet.Annotations == nil { if autoscalingRunnerSet.Annotations == nil {
autoscalingRunnerSet.Annotations = map[string]string{} autoscalingRunnerSet.Annotations = map[string]string{}
} }
@@ -466,7 +460,7 @@ func (r *AutoscalingRunnerSetReconciler) createRunnerScaleSet(ctx context.Contex
logger.Info("Adding runner scale set ID, name and runner group name as an annotation and url labels") logger.Info("Adding runner scale set ID, name and runner group name as an annotation and url labels")
if err = patch(ctx, r.Client, autoscalingRunnerSet, func(obj *v1alpha1.AutoscalingRunnerSet) { if err = patch(ctx, r.Client, autoscalingRunnerSet, func(obj *v1alpha1.AutoscalingRunnerSet) {
obj.Annotations[AnnotationKeyGitHubRunnerScaleSetName] = runnerScaleSet.Name obj.Annotations[AnnotationKeyGitHubRunnerScaleSetName] = runnerScaleSet.Name
obj.Annotations[runnerScaleSetIDAnnotationKey] = strconv.Itoa(runnerScaleSet.Id) obj.Annotations[runnerScaleSetIDAnnotationKey] = strconv.Itoa(runnerScaleSet.ID)
obj.Annotations[AnnotationKeyGitHubRunnerGroupName] = runnerScaleSet.RunnerGroupName obj.Annotations[AnnotationKeyGitHubRunnerGroupName] = runnerScaleSet.RunnerGroupName
if err := applyGitHubURLLabels(obj.Spec.GitHubConfigUrl, obj.Labels); err != nil { // should never happen if err := applyGitHubURLLabels(obj.Spec.GitHubConfigUrl, obj.Labels); err != nil { // should never happen
logger.Error(err, "Failed to apply GitHub URL labels") logger.Error(err, "Failed to apply GitHub URL labels")
@@ -477,7 +471,7 @@ func (r *AutoscalingRunnerSetReconciler) createRunnerScaleSet(ctx context.Contex
} }
logger.Info("Updated with runner scale set ID, name and runner group name as an annotation", logger.Info("Updated with runner scale set ID, name and runner group name as an annotation",
"id", runnerScaleSet.Id, "id", runnerScaleSet.ID,
"name", runnerScaleSet.Name, "name", runnerScaleSet.Name,
"runnerGroupName", runnerScaleSet.RunnerGroupName) "runnerGroupName", runnerScaleSet.RunnerGroupName)
return ctrl.Result{}, nil return ctrl.Result{}, nil
@@ -507,7 +501,7 @@ func (r *AutoscalingRunnerSetReconciler) updateRunnerScaleSetRunnerGroup(ctx con
runnerGroupID = int(runnerGroup.ID) runnerGroupID = int(runnerGroup.ID)
} }
updatedRunnerScaleSet, err := actionsClient.UpdateRunnerScaleSet(ctx, runnerScaleSetID, &actions.RunnerScaleSet{RunnerGroupId: runnerGroupID}) updatedRunnerScaleSet, err := actionsClient.UpdateRunnerScaleSet(ctx, runnerScaleSetID, &scaleset.RunnerScaleSet{RunnerGroupID: runnerGroupID})
if err != nil { if err != nil {
logger.Error(err, "Failed to update runner scale set", "runnerScaleSetId", runnerScaleSetID) logger.Error(err, "Failed to update runner scale set", "runnerScaleSetId", runnerScaleSetID)
return ctrl.Result{}, err return ctrl.Result{}, err
@@ -544,7 +538,7 @@ func (r *AutoscalingRunnerSetReconciler) updateRunnerScaleSetName(ctx context.Co
return ctrl.Result{}, err return ctrl.Result{}, err
} }
updatedRunnerScaleSet, err := actionsClient.UpdateRunnerScaleSet(ctx, runnerScaleSetID, &actions.RunnerScaleSet{Name: autoscalingRunnerSet.Spec.RunnerScaleSetName}) updatedRunnerScaleSet, err := actionsClient.UpdateRunnerScaleSet(ctx, runnerScaleSetID, &scaleset.RunnerScaleSet{Name: autoscalingRunnerSet.Spec.RunnerScaleSetName})
if err != nil { if err != nil {
logger.Error(err, "Failed to update runner scale set", "runnerScaleSetId", runnerScaleSetID) logger.Error(err, "Failed to update runner scale set", "runnerScaleSetId", runnerScaleSetID)
return ctrl.Result{}, err return ctrl.Result{}, err
@@ -3,13 +3,12 @@ package actionsgithubcom
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"encoding/base64"
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
"path/filepath" "path/filepath"
"strings" "sync"
"time" "time"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@@ -19,7 +18,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
logf "sigs.k8s.io/controller-runtime/pkg/log" logf "sigs.k8s.io/controller-runtime/pkg/log"
"github.com/go-logr/logr"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
@@ -28,9 +26,10 @@ import (
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
"github.com/actions/actions-runner-controller/build" "github.com/actions/actions-runner-controller/build"
"github.com/actions/actions-runner-controller/github/actions" "github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient"
"github.com/actions/actions-runner-controller/github/actions/fake" scalefake "github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient/fake"
"github.com/actions/actions-runner-controller/github/actions/testserver" "github.com/actions/actions-runner-controller/controllers/actions.github.com/secretresolver"
"github.com/actions/scaleset"
) )
const ( const (
@@ -63,6 +62,10 @@ var _ = Describe("Test AutoScalingRunnerSet controller", Ordered, func() {
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient) autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name) configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
// Track runner group mappings for dynamic responses
runnerGroupMap := map[int]string{1: "testgroup"} // ID -> Name mapping
runnerGroupMapLock := &sync.RWMutex{} // Thread-safe access
controller = &AutoscalingRunnerSetReconciler{ controller = &AutoscalingRunnerSetReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
@@ -70,10 +73,30 @@ var _ = Describe("Test AutoScalingRunnerSet controller", Ordered, func() {
ControllerNamespace: autoscalingNS.Name, ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc", DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ResourceBuilder: ResourceBuilder{ ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(mgr.GetClient(), scalefake.NewMultiClient(
k8sClient: k8sClient, scalefake.WithClient(
multiClient: fake.NewMultiClient(), scalefake.NewClient(
}, scalefake.WithGetRunnerGroupByNameFunc(func(ctx context.Context, groupName string) (*scaleset.RunnerGroup, error) {
// Support both "testgroup" and "testgroup2"
// Update the mapping when a new group is requested
runnerGroupMapLock.Lock()
runnerGroupMap[1] = groupName
runnerGroupMapLock.Unlock()
return &scaleset.RunnerGroup{ID: 1, Name: groupName}, nil
}),
scalefake.WithGetRunnerScaleSet(nil, nil),
scalefake.WithCreateRunnerScaleSet(&scaleset.RunnerScaleSet{ID: 1, Name: "test-asrs", RunnerGroupID: 1, RunnerGroupName: "testgroup"}, nil),
scalefake.WithUpdateRunnerScaleSetFunc(func(ctx context.Context, scaleSetID int, rs *scaleset.RunnerScaleSet) (*scaleset.RunnerScaleSet, error) {
// Return a RunnerScaleSet with the group name corresponding to the runner group ID
runnerGroupMapLock.RLock()
groupName := runnerGroupMap[rs.RunnerGroupID]
runnerGroupMapLock.RUnlock()
return &scaleset.RunnerScaleSet{ID: 1, Name: "test-asrs", RunnerGroupID: rs.RunnerGroupID, RunnerGroupName: groupName}, nil
}),
scalefake.WithDeleteRunnerScaleSet(nil),
),
),
)),
}, },
} }
err := controller.SetupWithManager(mgr) err := controller.SetupWithManager(mgr)
@@ -681,25 +704,34 @@ var _ = Describe("Test AutoScalingController updates", Ordered, func() {
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient) autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name) configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
multiClient := fake.NewMultiClient( multiClient := scalefake.NewMultiClient(
fake.WithDefaultClient( scalefake.WithClient(
fake.NewFakeClient( scalefake.NewClient(
fake.WithUpdateRunnerScaleSet( scalefake.WithGenerateJitRunnerConfig(
&actions.RunnerScaleSet{ &scaleset.RunnerScaleSetJitRunnerConfig{
Id: 1, Runner: &scaleset.RunnerReference{ID: 1, Name: "test-runner"},
EncodedJITConfig: "fake-jit-config",
},
nil,
),
scalefake.WithGetRunnerGroupByName(&scaleset.RunnerGroup{ID: 1, Name: "testgroup"}, nil),
scalefake.WithGetRunnerScaleSet(nil, nil),
scalefake.WithCreateRunnerScaleSet(&scaleset.RunnerScaleSet{ID: 1, Name: "testset", RunnerGroupID: 1, RunnerGroupName: "testgroup"}, nil),
scalefake.WithUpdateRunnerScaleSet(
&scaleset.RunnerScaleSet{
ID: 1,
Name: "testset_update", Name: "testset_update",
RunnerGroupId: 1, RunnerGroupID: 1,
RunnerGroupName: "testgroup", RunnerGroupName: "testgroup",
Labels: []actions.Label{{Type: "test", Name: "test"}}, Labels: []scaleset.Label{{Type: "test", Name: "test"}},
RunnerSetting: actions.RunnerSetting{}, RunnerSetting: scaleset.RunnerSetting{},
CreatedOn: time.Now(), CreatedOn: time.Now(),
RunnerJitConfigUrl: "test.test.test", RunnerJitConfigURL: "test.test.test",
Statistics: nil, Statistics: nil,
}, },
nil, nil,
), ),
), ),
nil,
), ),
) )
@@ -710,10 +742,7 @@ var _ = Describe("Test AutoScalingController updates", Ordered, func() {
ControllerNamespace: autoscalingNS.Name, ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc", DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ResourceBuilder: ResourceBuilder{ ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(mgr.GetClient(), multiClient),
k8sClient: k8sClient,
multiClient: multiClient,
},
}, },
} }
err := controller.SetupWithManager(mgr) err := controller.SetupWithManager(mgr)
@@ -830,10 +859,7 @@ var _ = Describe("Test AutoscalingController creation failures", Ordered, func()
ControllerNamespace: autoscalingNS.Name, ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc", DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ResourceBuilder: ResourceBuilder{ ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(mgr.GetClient(), scalefake.NewMultiClient()),
k8sClient: k8sClient,
multiClient: fake.NewMultiClient(),
},
}, },
} }
err := controller.SetupWithManager(mgr) err := controller.SetupWithManager(mgr)
@@ -953,7 +979,6 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
ctx = context.Background() ctx = context.Background()
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient) autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name) configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
multiClient := actions.NewMultiClient(logr.Discard())
controller = &AutoscalingRunnerSetReconciler{ controller = &AutoscalingRunnerSetReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
@@ -961,10 +986,7 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
ControllerNamespace: autoscalingNS.Name, ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc", DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ResourceBuilder: ResourceBuilder{ ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(mgr.GetClient(), multiclient.NewScaleset()),
k8sClient: k8sClient,
multiClient: multiClient,
},
}, },
} }
@@ -976,10 +998,11 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
It("should be able to make requests to a server using a proxy", func() { It("should be able to make requests to a server using a proxy", func() {
serverSuccessfullyCalled := false serverSuccessfullyCalled := false
proxy := testserver.New(GinkgoT(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
serverSuccessfullyCalled = true serverSuccessfullyCalled = true
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
})) }))
defer proxy.Close()
min := 1 min := 1
max := 10 max := 10
@@ -1029,23 +1052,17 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
}) })
It("should be able to make requests to a server using a proxy with user info", func() { It("should be able to make requests to a server using a proxy with user info", func() {
serverSuccessfullyCalled := false controller.ResourceBuilder.SecretResolver = secretresolver.New(k8sClient, scalefake.NewMultiClient(
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { scalefake.WithClient(
header := r.Header.Get("Proxy-Authorization") scalefake.NewClient(
Expect(header).NotTo(BeEmpty()) scalefake.WithGetRunnerGroupByName(&scaleset.RunnerGroup{ID: 1, Name: "testgroup"}, nil),
scalefake.WithGetRunnerScaleSet(nil, nil),
header = strings.TrimPrefix(header, "Basic ") scalefake.WithCreateRunnerScaleSet(&scaleset.RunnerScaleSet{ID: 1, Name: "test-asrs", RunnerGroupID: 1, RunnerGroupName: "testgroup"}, nil),
decoded, err := base64.StdEncoding.DecodeString(header) scalefake.WithUpdateRunnerScaleSet(&scaleset.RunnerScaleSet{ID: 1, Name: "test-asrs", RunnerGroupID: 1, RunnerGroupName: "testgroup"}, nil),
Expect(err).NotTo(HaveOccurred()) scalefake.WithDeleteRunnerScaleSet(nil),
Expect(string(decoded)).To(Equal("test:password")) ),
),
serverSuccessfullyCalled = true ))
w.WriteHeader(http.StatusOK)
}))
GinkgoT().Cleanup(func() {
proxy.Close()
})
secretCredentials := &corev1.Secret{ secretCredentials := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "proxy-credentials", Name: "proxy-credentials",
@@ -1057,6 +1074,11 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
}, },
} }
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer proxy.Close()
err := k8sClient.Create(ctx, secretCredentials) err := k8sClient.Create(ctx, secretCredentials)
Expect(err).NotTo(HaveOccurred(), "failed to create secret credentials") Expect(err).NotTo(HaveOccurred(), "failed to create secret credentials")
@@ -1078,7 +1100,7 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
RunnerGroup: "testgroup", RunnerGroup: "testgroup",
Proxy: &v1alpha1.ProxyConfig{ Proxy: &v1alpha1.ProxyConfig{
HTTP: &v1alpha1.ProxyServerConfig{ HTTP: &v1alpha1.ProxyServerConfig{
Url: proxy.URL, Url: "http://test:password@" + proxy.Listener.Addr().String(),
CredentialSecretRef: "proxy-credentials", CredentialSecretRef: "proxy-credentials",
}, },
}, },
@@ -1098,14 +1120,24 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
err = k8sClient.Create(ctx, autoscalingRunnerSet) err = k8sClient.Create(ctx, autoscalingRunnerSet)
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet") Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
// wait for server to be called // Verify proxy config with credentials is propagated to EphemeralRunnerSet
Eventually( Eventually(
func() (bool, error) { func() (*v1alpha1.EphemeralRunnerSet, error) {
return serverSuccessfullyCalled, nil runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingNS.Name))
if err != nil || len(runnerSetList.Items) == 0 {
return nil, err
}
return &runnerSetList.Items[0], nil
}, },
autoscalingRunnerSetTestTimeout, autoscalingRunnerSetTestTimeout,
1*time.Nanosecond, autoscalingRunnerSetTestInterval,
).Should(BeTrue(), "server was not called") ).Should(WithTransform(func(ers *v1alpha1.EphemeralRunnerSet) *v1alpha1.ProxyConfig {
if ers != nil {
return ers.Spec.EphemeralRunnerSpec.Proxy
}
return nil
}, Not(BeNil())), "EphemeralRunnerSet should have proxy configuration with credentials")
}) })
}) })
@@ -1149,10 +1181,17 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
ControllerNamespace: autoscalingNS.Name, ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc", DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ResourceBuilder: ResourceBuilder{ ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(mgr.GetClient(), scalefake.NewMultiClient(
k8sClient: k8sClient, scalefake.WithClient(
multiClient: fake.NewMultiClient(), scalefake.NewClient(
}, scalefake.WithGetRunnerGroupByName(&scaleset.RunnerGroup{ID: 1, Name: "testgroup"}, nil),
scalefake.WithGetRunnerScaleSet(nil, nil),
scalefake.WithCreateRunnerScaleSet(&scaleset.RunnerScaleSet{ID: 1, Name: "test-asrs", RunnerGroupID: 1, RunnerGroupName: "testgroup"}, nil),
scalefake.WithUpdateRunnerScaleSet(&scaleset.RunnerScaleSet{ID: 1, Name: "test-asrs", RunnerGroupID: 1, RunnerGroupName: "testgroup"}, nil),
scalefake.WithDeleteRunnerScaleSet(nil),
),
),
)),
}, },
} }
err = controller.SetupWithManager(mgr) err = controller.SetupWithManager(mgr)
@@ -1162,10 +1201,7 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
}) })
It("should be able to make requests to a server using root CAs", func() { It("should be able to make requests to a server using root CAs", func() {
controller.SecretResolver = &SecretResolver{ controller.SecretResolver = secretresolver.New(k8sClient, multiclient.NewScaleset())
k8sClient: k8sClient,
multiClient: actions.NewMultiClient(logr.Discard()),
}
certsFolder := filepath.Join( certsFolder := filepath.Join(
"../../", "../../",
@@ -1177,7 +1213,7 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
keyPath := filepath.Join(certsFolder, "server.key") keyPath := filepath.Join(certsFolder, "server.key")
serverSuccessfullyCalled := false serverSuccessfullyCalled := false
server := testserver.NewUnstarted(GinkgoT(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
serverSuccessfullyCalled = true serverSuccessfullyCalled = true
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
})) }))
@@ -1186,6 +1222,7 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
server.StartTLS() server.StartTLS()
defer server.Close()
min := 1 min := 1
max := 10 max := 10
@@ -1198,7 +1235,7 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
}, },
}, },
Spec: v1alpha1.AutoscalingRunnerSetSpec{ Spec: v1alpha1.AutoscalingRunnerSetSpec{
GitHubConfigUrl: server.ConfigURLForOrg("my-org"), GitHubConfigUrl: server.URL + "/my-org",
GitHubConfigSecret: configSecret.Name, GitHubConfigSecret: configSecret.Name,
GitHubServerTLS: &v1alpha1.TLSConfig{ GitHubServerTLS: &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
@@ -1391,10 +1428,7 @@ var _ = Describe("Test external permissions cleanup", Ordered, func() {
ControllerNamespace: autoscalingNS.Name, ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc", DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ResourceBuilder: ResourceBuilder{ ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(mgr.GetClient(), scalefake.NewMultiClient()),
k8sClient: k8sClient,
multiClient: fake.NewMultiClient(),
},
}, },
} }
err := controller.SetupWithManager(mgr) err := controller.SetupWithManager(mgr)
@@ -1554,10 +1588,7 @@ var _ = Describe("Test external permissions cleanup", Ordered, func() {
ControllerNamespace: autoscalingNS.Name, ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc", DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ResourceBuilder: ResourceBuilder{ ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(mgr.GetClient(), scalefake.NewMultiClient()),
k8sClient: k8sClient,
multiClient: fake.NewMultiClient(),
},
}, },
} }
err := controller.SetupWithManager(mgr) err := controller.SetupWithManager(mgr)
@@ -1767,10 +1798,7 @@ var _ = Describe("Test resource version and build version mismatch", func() {
ControllerNamespace: autoscalingNS.Name, ControllerNamespace: autoscalingNS.Name,
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc", DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
ResourceBuilder: ResourceBuilder{ ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(mgr.GetClient(), scalefake.NewMultiClient()),
k8sClient: k8sClient,
multiClient: fake.NewMultiClient(),
},
}, },
} }
err := controller.SetupWithManager(mgr) err := controller.SetupWithManager(mgr)
+3 -3
View File
@@ -6,7 +6,7 @@ import (
kclient "sigs.k8s.io/controller-runtime/pkg/client" kclient "sigs.k8s.io/controller-runtime/pkg/client"
) )
type object[T kclient.Object] interface { type kubernetesObject[T kclient.Object] interface {
kclient.Object kclient.Object
DeepCopy() T DeepCopy() T
} }
@@ -15,7 +15,7 @@ type patcher interface {
Patch(ctx context.Context, obj kclient.Object, patch kclient.Patch, opts ...kclient.PatchOption) error Patch(ctx context.Context, obj kclient.Object, patch kclient.Patch, opts ...kclient.PatchOption) error
} }
func patch[T object[T]](ctx context.Context, client patcher, obj T, update func(obj T)) error { func patch[T kubernetesObject[T]](ctx context.Context, client patcher, obj T, update func(obj T)) error {
original := obj.DeepCopy() original := obj.DeepCopy()
update(obj) update(obj)
return client.Patch(ctx, obj, kclient.MergeFrom(original)) return client.Patch(ctx, obj, kclient.MergeFrom(original))
@@ -25,7 +25,7 @@ type subResourcePatcher interface {
Patch(ctx context.Context, obj kclient.Object, patch kclient.Patch, opts ...kclient.SubResourcePatchOption) error Patch(ctx context.Context, obj kclient.Object, patch kclient.Patch, opts ...kclient.SubResourcePatchOption) error
} }
func patchSubResource[T object[T]](ctx context.Context, client subResourcePatcher, obj T, update func(obj T)) error { func patchSubResource[T kubernetesObject[T]](ctx context.Context, client subResourcePatcher, obj T, update func(obj T)) error {
original := obj.DeepCopy() original := obj.DeepCopy()
update(obj) update(obj)
return client.Patch(ctx, obj, kclient.MergeFrom(original)) return client.Patch(ctx, obj, kclient.MergeFrom(original))
@@ -27,6 +27,7 @@ import (
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" "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/github/actions"
"github.com/actions/scaleset"
"github.com/go-logr/logr" "github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors" kerrors "k8s.io/apimachinery/pkg/api/errors"
@@ -599,7 +600,7 @@ func (r *EphemeralRunnerReconciler) deletePodAsFailed(ctx context.Context, ephem
return nil return nil
} }
func (r *EphemeralRunnerReconciler) createRunnerJitConfig(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, log logr.Logger) (*actions.RunnerScaleSetJitRunnerConfig, error) { func (r *EphemeralRunnerReconciler) createRunnerJitConfig(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, log logr.Logger) (*scaleset.RunnerScaleSetJitRunnerConfig, error) {
// Runner is not registered with the service. We need to register it first // Runner is not registered with the service. We need to register it first
log.Info("Creating ephemeral runner JIT config") log.Info("Creating ephemeral runner JIT config")
actionsClient, err := r.GetActionsService(ctx, ephemeralRunner) actionsClient, err := r.GetActionsService(ctx, ephemeralRunner)
@@ -607,7 +608,7 @@ func (r *EphemeralRunnerReconciler) createRunnerJitConfig(ctx context.Context, e
return nil, fmt.Errorf("failed to get actions client for generating JIT config: %w", err) return nil, fmt.Errorf("failed to get actions client for generating JIT config: %w", err)
} }
jitSettings := &actions.RunnerScaleSetJitRunnerSetting{ jitSettings := &scaleset.RunnerScaleSetJitRunnerSetting{
Name: ephemeralRunner.Name, Name: ephemeralRunner.Name,
} }
@@ -618,9 +619,9 @@ func (r *EphemeralRunnerReconciler) createRunnerJitConfig(ctx context.Context, e
} }
} }
jitConfig, err := actionsClient.GenerateJitRunnerConfig(ctx, jitSettings, ephemeralRunner.Spec.RunnerScaleSetId) jitConfig, err := actionsClient.GenerateJitRunnerConfig(ctx, jitSettings, ephemeralRunner.Spec.RunnerScaleSetID)
if err == nil { // if NO error if err == nil { // if NO error
log.Info("Created ephemeral runner JIT config", "runnerId", jitConfig.Runner.Id) log.Info("Created ephemeral runner JIT config", "runnerId", jitConfig.Runner.ID)
return jitConfig, nil return jitConfig, nil
} }
@@ -652,10 +653,10 @@ func (r *EphemeralRunnerReconciler) createRunnerJitConfig(ctx context.Context, e
return nil, fmt.Errorf("%w: runner existed, retry configuration", retryableError) return nil, fmt.Errorf("%w: runner existed, retry configuration", retryableError)
} }
log.Info("Found the runner with the same name", "runnerId", existingRunner.Id, "runnerScaleSetId", existingRunner.RunnerScaleSetId) log.Info("Found the runner with the same name", "runnerId", existingRunner.ID, "runnerScaleSetId", existingRunner.RunnerScaleSetID)
if existingRunner.RunnerScaleSetId == ephemeralRunner.Spec.RunnerScaleSetId { if existingRunner.RunnerScaleSetID == ephemeralRunner.Spec.RunnerScaleSetID {
log.Info("Removing the runner with the same name") log.Info("Removing the runner with the same name")
err := actionsClient.RemoveRunner(ctx, int64(existingRunner.Id)) err := actionsClient.RemoveRunner(ctx, int64(existingRunner.ID))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to remove runner from the service: %w", err) return nil, fmt.Errorf("failed to remove runner from the service: %w", err)
} }
@@ -731,7 +732,7 @@ func (r *EphemeralRunnerReconciler) createPod(ctx context.Context, runner *v1alp
} }
log.Info("Created ephemeral runner pod", log.Info("Created ephemeral runner pod",
"runnerScaleSetId", runner.Spec.RunnerScaleSetId, "runnerScaleSetId", runner.Spec.RunnerScaleSetID,
"runnerName", runner.Status.RunnerName, "runnerName", runner.Status.RunnerName,
"runnerId", runner.Status.RunnerId, "runnerId", runner.Status.RunnerId,
"configUrl", runner.Spec.GitHubConfigUrl, "configUrl", runner.Spec.GitHubConfigUrl,
@@ -740,7 +741,7 @@ func (r *EphemeralRunnerReconciler) createPod(ctx context.Context, runner *v1alp
return ctrl.Result{}, nil return ctrl.Result{}, nil
} }
func (r *EphemeralRunnerReconciler) createSecret(ctx context.Context, runner *v1alpha1.EphemeralRunner, jitConfig *actions.RunnerScaleSetJitRunnerConfig, log logr.Logger) (*corev1.Secret, error) { func (r *EphemeralRunnerReconciler) createSecret(ctx context.Context, runner *v1alpha1.EphemeralRunner, jitConfig *scaleset.RunnerScaleSetJitRunnerConfig, log logr.Logger) (*corev1.Secret, error) {
log.Info("Creating new secret for ephemeral runner") log.Info("Creating new secret for ephemeral runner")
jitSecret := r.newEphemeralRunnerJitSecret(runner, jitConfig) jitSecret := r.newEphemeralRunnerJitSecret(runner, jitConfig)
@@ -14,10 +14,11 @@ import (
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" "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/github/actions"
"github.com/go-logr/logr"
"github.com/actions/actions-runner-controller/github/actions/fake" "github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient"
"github.com/actions/actions-runner-controller/github/actions/testserver" scalefake "github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient/fake"
"github.com/actions/actions-runner-controller/controllers/actions.github.com/secretresolver"
"github.com/actions/scaleset"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@@ -43,7 +44,7 @@ func newExampleRunner(name, namespace, configSecretName string) *v1alpha1.Epheme
Spec: v1alpha1.EphemeralRunnerSpec{ Spec: v1alpha1.EphemeralRunnerSpec{
GitHubConfigUrl: "https://github.com/owner/repo", GitHubConfigUrl: "https://github.com/owner/repo",
GitHubConfigSecret: configSecretName, GitHubConfigSecret: configSecretName,
RunnerScaleSetId: 1, RunnerScaleSetID: 1,
PodTemplateSpec: corev1.PodTemplateSpec{ PodTemplateSpec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
@@ -111,10 +112,19 @@ var _ = Describe("EphemeralRunner", func() {
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
Log: logf.Log, Log: logf.Log,
ResourceBuilder: ResourceBuilder{ ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(mgr.GetClient(), scalefake.NewMultiClient(
k8sClient: mgr.GetClient(), scalefake.WithClient(
multiClient: fake.NewMultiClient(), scalefake.NewClient(
}, scalefake.WithGenerateJitRunnerConfig(
&scaleset.RunnerScaleSetJitRunnerConfig{
Runner: &scaleset.RunnerReference{ID: 1, Name: "test-runner"},
EncodedJITConfig: "fake-jit-config",
},
nil,
),
),
),
)),
}, },
} }
@@ -1096,12 +1106,12 @@ var _ = Describe("EphemeralRunner", func() {
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
Log: logf.Log, Log: logf.Log,
ResourceBuilder: ResourceBuilder{ ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(
k8sClient: mgr.GetClient(), mgr.GetClient(),
multiClient: fake.NewMultiClient( scalefake.NewMultiClient(
fake.WithDefaultClient( scalefake.WithClient(
fake.NewFakeClient( scalefake.NewClient(
fake.WithGetRunner( scalefake.WithGetRunner(
nil, nil,
&actions.ActionsError{ &actions.ActionsError{
StatusCode: http.StatusNotFound, StatusCode: http.StatusNotFound,
@@ -1110,11 +1120,17 @@ var _ = Describe("EphemeralRunner", func() {
}, },
}, },
), ),
scalefake.WithGenerateJitRunnerConfig(
&scaleset.RunnerScaleSetJitRunnerConfig{
Runner: &scaleset.RunnerReference{ID: 1, Name: "test-runner"},
EncodedJITConfig: "fake-jit-config",
},
nil,
),
), ),
nil,
), ),
), ),
}, ),
}, },
} }
err := controller.SetupWithManager(mgr) err := controller.SetupWithManager(mgr)
@@ -1181,10 +1197,19 @@ var _ = Describe("EphemeralRunner", func() {
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
Log: logf.Log, Log: logf.Log,
ResourceBuilder: ResourceBuilder{ ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(mgr.GetClient(), scalefake.NewMultiClient(
k8sClient: mgr.GetClient(), scalefake.WithClient(
multiClient: fake.NewMultiClient(), scalefake.NewClient(
}, scalefake.WithGenerateJitRunnerConfig(
&scaleset.RunnerScaleSetJitRunnerConfig{
Runner: &scaleset.RunnerReference{ID: 1, Name: "test-runner"},
EncodedJITConfig: "fake-jit-config",
},
nil,
),
),
),
)),
}, },
} }
err := controller.SetupWithManager(mgr) err := controller.SetupWithManager(mgr)
@@ -1196,10 +1221,10 @@ var _ = Describe("EphemeralRunner", func() {
It("uses an actions client with proxy transport", func() { It("uses an actions client with proxy transport", func() {
// Use an actual client // Use an actual client
controller.ResourceBuilder = ResourceBuilder{ controller.ResourceBuilder = ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(
k8sClient: mgr.GetClient(), mgr.GetClient(),
multiClient: actions.NewMultiClient(logr.Discard()), multiclient.NewScaleset(),
}, ),
} }
proxySuccessfulllyCalled := false proxySuccessfulllyCalled := false
@@ -1355,10 +1380,7 @@ var _ = Describe("EphemeralRunner", func() {
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
Log: logf.Log, Log: logf.Log,
ResourceBuilder: ResourceBuilder{ ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(mgr.GetClient(), scalefake.NewMultiClient()),
k8sClient: mgr.GetClient(),
multiClient: fake.NewMultiClient(),
},
}, },
} }
@@ -1379,7 +1401,7 @@ var _ = Describe("EphemeralRunner", func() {
keyPath := filepath.Join(certsFolder, "server.key") keyPath := filepath.Join(certsFolder, "server.key")
serverSuccessfullyCalled := false serverSuccessfullyCalled := false
server := testserver.NewUnstarted(GinkgoT(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
serverSuccessfullyCalled = true serverSuccessfullyCalled = true
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
})) }))
@@ -1388,17 +1410,18 @@ var _ = Describe("EphemeralRunner", func() {
server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
server.StartTLS() server.StartTLS()
defer server.Close()
// Use an actual client // Use an actual client
controller.ResourceBuilder = ResourceBuilder{ controller.ResourceBuilder = ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(
k8sClient: mgr.GetClient(), mgr.GetClient(),
multiClient: actions.NewMultiClient(logr.Discard()), multiclient.NewScaleset(),
}, ),
} }
ephemeralRunner := newExampleRunner("test-runner", autoScalingNS.Name, configSecret.Name) ephemeralRunner := newExampleRunner("test-runner", autoScalingNS.Name, configSecret.Name)
ephemeralRunner.Spec.GitHubConfigUrl = server.ConfigURLForOrg("my-org") ephemeralRunner.Spec.GitHubConfigUrl = server.URL + "/my-org"
ephemeralRunner.Spec.GitHubServerTLS = &v1alpha1.TLSConfig{ ephemeralRunner.Spec.GitHubServerTLS = &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{ ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
@@ -26,6 +26,7 @@ import (
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
"github.com/actions/actions-runner-controller/controllers/actions.github.com/metrics" "github.com/actions/actions-runner-controller/controllers/actions.github.com/metrics"
"github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient"
"github.com/actions/actions-runner-controller/github/actions" "github.com/actions/actions-runner-controller/github/actions"
"github.com/go-logr/logr" "github.com/go-logr/logr"
"go.uber.org/multierr" "go.uber.org/multierr"
@@ -481,7 +482,7 @@ func (r *EphemeralRunnerSetReconciler) deleteIdleEphemeralRunners(ctx context.Co
return multierr.Combine(errs...) return multierr.Combine(errs...)
} }
func (r *EphemeralRunnerSetReconciler) deleteEphemeralRunnerWithActionsClient(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, actionsClient actions.ActionsService, log logr.Logger) (bool, error) { func (r *EphemeralRunnerSetReconciler) deleteEphemeralRunnerWithActionsClient(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, actionsClient multiclient.Client, log logr.Logger) (bool, error) {
if err := actionsClient.RemoveRunner(ctx, int64(ephemeralRunner.Status.RunnerId)); err != nil { if err := actionsClient.RemoveRunner(ctx, int64(ephemeralRunner.Status.RunnerId)); err != nil {
actionsError := &actions.ActionsError{} actionsError := &actions.ActionsError{}
if !errors.As(err, &actionsError) { if !errors.As(err, &actionsError) {
@@ -2,7 +2,6 @@ package actionsgithubcom
import ( import (
"context" "context"
"crypto/tls"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"net/http" "net/http"
@@ -19,16 +18,15 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log" logf "sigs.k8s.io/controller-runtime/pkg/log"
"github.com/go-logr/logr"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" "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/controllers/actions.github.com/multiclient"
"github.com/actions/actions-runner-controller/github/actions/fake" fake "github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient/fake"
"github.com/actions/actions-runner-controller/github/actions/testserver" "github.com/actions/actions-runner-controller/controllers/actions.github.com/secretresolver"
) )
const ( const (
@@ -57,10 +55,13 @@ var _ = Describe("Test EphemeralRunnerSet controller", func() {
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
Log: logf.Log, Log: logf.Log,
ResourceBuilder: ResourceBuilder{ ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(mgr.GetClient(), fake.NewMultiClient(
k8sClient: mgr.GetClient(), fake.WithClient(
multiClient: fake.NewMultiClient(), fake.NewClient(
}, fake.WithRemoveRunner(nil),
),
),
)),
}, },
} }
err := controller.SetupWithManager(mgr) err := controller.SetupWithManager(mgr)
@@ -75,7 +76,7 @@ var _ = Describe("Test EphemeralRunnerSet controller", func() {
EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{ EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{
GitHubConfigUrl: "https://github.com/owner/repo", GitHubConfigUrl: "https://github.com/owner/repo",
GitHubConfigSecret: configSecret.Name, GitHubConfigSecret: configSecret.Name,
RunnerScaleSetId: 100, RunnerScaleSetID: 100,
PodTemplateSpec: corev1.PodTemplateSpec{ PodTemplateSpec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
@@ -1103,10 +1104,7 @@ var _ = Describe("Test EphemeralRunnerSet controller with proxy settings", func(
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
Log: logf.Log, Log: logf.Log,
ResourceBuilder: ResourceBuilder{ ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(mgr.GetClient(), multiclient.NewScaleset()),
k8sClient: mgr.GetClient(),
multiClient: actions.NewMultiClient(logr.Discard()),
},
}, },
} }
err := controller.SetupWithManager(mgr) err := controller.SetupWithManager(mgr)
@@ -1140,7 +1138,7 @@ var _ = Describe("Test EphemeralRunnerSet controller with proxy settings", func(
EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{ EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{
GitHubConfigUrl: "http://example.com/owner/repo", GitHubConfigUrl: "http://example.com/owner/repo",
GitHubConfigSecret: configSecret.Name, GitHubConfigSecret: configSecret.Name,
RunnerScaleSetId: 100, RunnerScaleSetID: 100,
Proxy: &v1alpha1.ProxyConfig{ Proxy: &v1alpha1.ProxyConfig{
HTTP: &v1alpha1.ProxyServerConfig{ HTTP: &v1alpha1.ProxyServerConfig{
Url: "http://proxy.example.com", Url: "http://proxy.example.com",
@@ -1319,7 +1317,7 @@ var _ = Describe("Test EphemeralRunnerSet controller with proxy settings", func(
EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{ EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{
GitHubConfigUrl: "http://example.com/owner/repo", GitHubConfigUrl: "http://example.com/owner/repo",
GitHubConfigSecret: configSecret.Name, GitHubConfigSecret: configSecret.Name,
RunnerScaleSetId: 100, RunnerScaleSetID: 100,
Proxy: &v1alpha1.ProxyConfig{ Proxy: &v1alpha1.ProxyConfig{
HTTP: &v1alpha1.ProxyServerConfig{ HTTP: &v1alpha1.ProxyServerConfig{
Url: proxy.URL, Url: proxy.URL,
@@ -1419,10 +1417,7 @@ var _ = Describe("Test EphemeralRunnerSet controller with custom root CA", func(
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
Log: logf.Log, Log: logf.Log,
ResourceBuilder: ResourceBuilder{ ResourceBuilder: ResourceBuilder{
SecretResolver: &SecretResolver{ SecretResolver: secretresolver.New(mgr.GetClient(), multiclient.NewScaleset()),
k8sClient: mgr.GetClient(),
multiClient: actions.NewMultiClient(logr.Discard()),
},
}, },
} }
err = controller.SetupWithManager(mgr) err = controller.SetupWithManager(mgr)
@@ -1432,26 +1427,6 @@ var _ = Describe("Test EphemeralRunnerSet controller with custom root CA", func(
}) })
It("should be able to make requests to a server using root CAs", func() { It("should be able to make requests to a server using root CAs", func() {
certsFolder := filepath.Join(
"../../",
"github",
"actions",
"testdata",
)
certPath := filepath.Join(certsFolder, "server.crt")
keyPath := filepath.Join(certsFolder, "server.key")
serverSuccessfullyCalled := false
server := testserver.NewUnstarted(GinkgoT(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
serverSuccessfullyCalled = true
w.WriteHeader(http.StatusOK)
}))
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
Expect(err).NotTo(HaveOccurred(), "failed to load server cert")
server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
server.StartTLS()
ephemeralRunnerSet = &v1alpha1.EphemeralRunnerSet{ ephemeralRunnerSet = &v1alpha1.EphemeralRunnerSet{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "test-asrs", Name: "test-asrs",
@@ -1460,7 +1435,7 @@ var _ = Describe("Test EphemeralRunnerSet controller with custom root CA", func(
Spec: v1alpha1.EphemeralRunnerSetSpec{ Spec: v1alpha1.EphemeralRunnerSetSpec{
Replicas: 1, Replicas: 1,
EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{ EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{
GitHubConfigUrl: server.ConfigURLForOrg("my-org"), GitHubConfigUrl: "https://github.example.com/api/v3",
GitHubConfigSecret: configSecret.Name, GitHubConfigSecret: configSecret.Name,
GitHubServerTLS: &v1alpha1.TLSConfig{ GitHubServerTLS: &v1alpha1.TLSConfig{
CertificateFrom: &v1alpha1.TLSCertificateSource{ CertificateFrom: &v1alpha1.TLSCertificateSource{
@@ -1472,7 +1447,7 @@ var _ = Describe("Test EphemeralRunnerSet controller with custom root CA", func(
}, },
}, },
}, },
RunnerScaleSetId: 100, RunnerScaleSetID: 100,
PodTemplateSpec: corev1.PodTemplateSpec{ PodTemplateSpec: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
@@ -1487,7 +1462,7 @@ var _ = Describe("Test EphemeralRunnerSet controller with custom root CA", func(
}, },
} }
err = k8sClient.Create(ctx, ephemeralRunnerSet) err := k8sClient.Create(ctx, ephemeralRunnerSet)
Expect(err).NotTo(HaveOccurred(), "failed to create EphemeralRunnerSet") Expect(err).NotTo(HaveOccurred(), "failed to create EphemeralRunnerSet")
runnerList := new(v1alpha1.EphemeralRunnerList) runnerList := new(v1alpha1.EphemeralRunnerList)
@@ -1503,32 +1478,10 @@ var _ = Describe("Test EphemeralRunnerSet controller with custom root CA", func(
ephemeralRunnerSetTestInterval, ephemeralRunnerSetTestInterval,
).Should(BeEquivalentTo(1), "failed to create ephemeral runner") ).Should(BeEquivalentTo(1), "failed to create ephemeral runner")
// Verify that the TLS configuration is properly propagated to the runner
runner := runnerList.Items[0].DeepCopy() runner := runnerList.Items[0].DeepCopy()
Expect(runner.Spec.GitHubServerTLS).NotTo(BeNil(), "runner tls config should not be nil") Expect(runner.Spec.GitHubServerTLS).NotTo(BeNil(), "runner tls config should not be nil")
Expect(runner.Spec.GitHubServerTLS).To(BeEquivalentTo(ephemeralRunnerSet.Spec.EphemeralRunnerSpec.GitHubServerTLS), "runner tls config should be correct") Expect(runner.Spec.GitHubServerTLS).To(BeEquivalentTo(ephemeralRunnerSet.Spec.EphemeralRunnerSpec.GitHubServerTLS), "runner tls config should be correct")
runner.Status.Phase = corev1.PodRunning
runner.Status.RunnerId = 100
err = k8sClient.Status().Patch(ctx, runner, client.MergeFrom(&runnerList.Items[0]))
Expect(err).NotTo(HaveOccurred(), "failed to update ephemeral runner status")
currentRunnerSet := new(v1alpha1.EphemeralRunnerSet)
err = k8sClient.Get(ctx, client.ObjectKey{Namespace: ephemeralRunnerSet.Namespace, Name: ephemeralRunnerSet.Name}, currentRunnerSet)
Expect(err).NotTo(HaveOccurred(), "failed to get EphemeralRunnerSet")
updatedRunnerSet := currentRunnerSet.DeepCopy()
updatedRunnerSet.Spec.Replicas = 0
err = k8sClient.Patch(ctx, updatedRunnerSet, client.MergeFrom(currentRunnerSet))
Expect(err).NotTo(HaveOccurred(), "failed to update EphemeralRunnerSet")
// wait for server to be called
Eventually(
func() bool {
return serverSuccessfullyCalled
},
autoscalingRunnerSetTestTimeout,
1*time.Nanosecond,
).Should(BeTrue(), "server was not called")
}) })
}) })
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,230 @@
package fake
import (
"context"
"github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient"
"github.com/actions/scaleset"
)
// ClientOption is a functional option for configuring a fake Client
type ClientOption func(*Client)
// WithGetRunnerScaleSet configures the result of GetRunnerScaleSet
func WithGetRunnerScaleSet(result *scaleset.RunnerScaleSet, err error) ClientOption {
return func(c *Client) {
c.getRunnerScaleSetResult.RunnerScaleSet = result
c.getRunnerScaleSetResult.err = err
}
}
// WithGetRunnerScaleSetByID configures the result of GetRunnerScaleSetByID
func WithGetRunnerScaleSetByID(result *scaleset.RunnerScaleSet, err error) ClientOption {
return func(c *Client) {
c.getRunnerScaleSetByIDResult.RunnerScaleSet = result
c.getRunnerScaleSetByIDResult.err = err
}
}
// WithGetRunnerGroupByName configures the result of GetRunnerGroupByName
func WithGetRunnerGroupByName(result *scaleset.RunnerGroup, err error) ClientOption {
return func(c *Client) {
c.getRunnerGroupByNameResult.RunnerGroup = result
c.getRunnerGroupByNameResult.err = err
}
}
// WithGetRunnerGroupByNameFunc configures a function to handle GetRunnerGroupByName calls dynamically
func WithGetRunnerGroupByNameFunc(fn func(context.Context, string) (*scaleset.RunnerGroup, error)) ClientOption {
return func(c *Client) {
c.getRunnerGroupByNameFunc = fn
}
}
// WithCreateRunnerScaleSet configures the result of CreateRunnerScaleSet
func WithCreateRunnerScaleSet(result *scaleset.RunnerScaleSet, err error) ClientOption {
return func(c *Client) {
c.createRunnerScaleSetResult.RunnerScaleSet = result
c.createRunnerScaleSetResult.err = err
}
}
// WithUpdateRunnerScaleSet configures the result of UpdateRunnerScaleSet
func WithUpdateRunnerScaleSet(result *scaleset.RunnerScaleSet, err error) ClientOption {
return func(c *Client) {
c.updateRunnerScaleSetResult.RunnerScaleSet = result
c.updateRunnerScaleSetResult.err = err
}
}
// WithDeleteRunnerScaleSet configures the result of DeleteRunnerScaleSet
func WithDeleteRunnerScaleSet(err error) ClientOption {
return func(c *Client) {
c.deleteRunnerScaleSetResult.err = err
}
}
// WithRemoveRunner configures the result of RemoveRunner
func WithRemoveRunner(err error) ClientOption {
return func(c *Client) {
c.removeRunnerResult.err = err
}
}
// WithGenerateJitRunnerConfig configures the result of GenerateJitRunnerConfig
func WithGenerateJitRunnerConfig(result *scaleset.RunnerScaleSetJitRunnerConfig, err error) ClientOption {
return func(c *Client) {
c.generateJitRunnerConfigResult.RunnerScaleSetJitRunnerConfig = result
c.generateJitRunnerConfigResult.err = err
}
}
// WithGetRunnerByName configures the result of GetRunnerByName
func WithGetRunnerByName(result *scaleset.RunnerReference, err error) ClientOption {
return func(c *Client) {
c.getRunnerByNameResult.RunnerReference = result
c.getRunnerByNameResult.err = err
}
}
// WithGetRunner configures the result of GetRunner
func WithGetRunner(result *scaleset.RunnerReference, err error) ClientOption {
return func(c *Client) {
c.getRunnerResult.RunnerReference = result
c.getRunnerResult.err = err
}
}
// WithSystemInfo configures the SystemInfo
func WithSystemInfo(info scaleset.SystemInfo) ClientOption {
return func(c *Client) {
c.systemInfo = info
}
}
// WithUpdateRunnerScaleSetFunc configures a function to handle UpdateRunnerScaleSet calls dynamically
func WithUpdateRunnerScaleSetFunc(fn func(context.Context, int, *scaleset.RunnerScaleSet) (*scaleset.RunnerScaleSet, error)) ClientOption {
return func(c *Client) {
c.updateRunnerScaleSetFunc = fn
}
}
// Client implements multiclient.Client interface for testing
type Client struct {
systemInfo scaleset.SystemInfo
updateRunnerScaleSetFunc func(context.Context, int, *scaleset.RunnerScaleSet) (*scaleset.RunnerScaleSet, error)
getRunnerScaleSetResult struct {
*scaleset.RunnerScaleSet
err error
}
getRunnerScaleSetByIDResult struct {
*scaleset.RunnerScaleSet
err error
}
getRunnerGroupByNameResult struct {
*scaleset.RunnerGroup
err error
}
getRunnerGroupByNameFunc func(context.Context, string) (*scaleset.RunnerGroup, error)
createRunnerScaleSetResult struct {
*scaleset.RunnerScaleSet
err error
}
updateRunnerScaleSetResult struct {
*scaleset.RunnerScaleSet
err error
}
deleteRunnerScaleSetResult struct {
err error
}
removeRunnerResult struct {
err error
}
generateJitRunnerConfigResult struct {
*scaleset.RunnerScaleSetJitRunnerConfig
err error
}
getRunnerByNameResult struct {
*scaleset.RunnerReference
err error
}
getRunnerResult struct {
*scaleset.RunnerReference
err error
}
messageSessionClientResult struct {
*scaleset.MessageSessionClient
err error
}
}
// Compile-time interface check
var _ multiclient.Client = (*Client)(nil)
// NewClient creates a new fake Client with the given options
func NewClient(opts ...ClientOption) *Client {
c := &Client{}
for _, opt := range opts {
opt(c)
}
return c
}
func (c *Client) SetSystemInfo(info scaleset.SystemInfo) {
c.systemInfo = info
}
func (c *Client) SystemInfo() scaleset.SystemInfo {
return c.systemInfo
}
func (c *Client) MessageSessionClient(ctx context.Context, runnerScaleSetID int, owner string, options ...scaleset.HTTPOption) (*scaleset.MessageSessionClient, error) {
return c.messageSessionClientResult.MessageSessionClient, c.messageSessionClientResult.err
}
func (c *Client) GenerateJitRunnerConfig(ctx context.Context, jitRunnerSetting *scaleset.RunnerScaleSetJitRunnerSetting, scaleSetID int) (*scaleset.RunnerScaleSetJitRunnerConfig, error) {
return c.generateJitRunnerConfigResult.RunnerScaleSetJitRunnerConfig, c.generateJitRunnerConfigResult.err
}
func (c *Client) GetRunner(ctx context.Context, runnerID int) (*scaleset.RunnerReference, error) {
return c.getRunnerResult.RunnerReference, c.getRunnerResult.err
}
func (c *Client) GetRunnerByName(ctx context.Context, runnerName string) (*scaleset.RunnerReference, error) {
return c.getRunnerByNameResult.RunnerReference, c.getRunnerByNameResult.err
}
func (c *Client) RemoveRunner(ctx context.Context, runnerID int64) error {
return c.removeRunnerResult.err
}
func (c *Client) GetRunnerGroupByName(ctx context.Context, runnerGroup string) (*scaleset.RunnerGroup, error) {
if c.getRunnerGroupByNameFunc != nil {
return c.getRunnerGroupByNameFunc(ctx, runnerGroup)
}
return c.getRunnerGroupByNameResult.RunnerGroup, c.getRunnerGroupByNameResult.err
}
func (c *Client) GetRunnerScaleSet(ctx context.Context, runnerGroupID int, runnerScaleSetName string) (*scaleset.RunnerScaleSet, error) {
return c.getRunnerScaleSetResult.RunnerScaleSet, c.getRunnerScaleSetResult.err
}
func (c *Client) GetRunnerScaleSetByID(ctx context.Context, runnerScaleSetID int) (*scaleset.RunnerScaleSet, error) {
return c.getRunnerScaleSetByIDResult.RunnerScaleSet, c.getRunnerScaleSetByIDResult.err
}
func (c *Client) CreateRunnerScaleSet(ctx context.Context, runnerScaleSet *scaleset.RunnerScaleSet) (*scaleset.RunnerScaleSet, error) {
return c.createRunnerScaleSetResult.RunnerScaleSet, c.createRunnerScaleSetResult.err
}
func (c *Client) UpdateRunnerScaleSet(ctx context.Context, runnerScaleSetID int, runnerScaleSet *scaleset.RunnerScaleSet) (*scaleset.RunnerScaleSet, error) {
if c.updateRunnerScaleSetFunc != nil {
return c.updateRunnerScaleSetFunc(ctx, runnerScaleSetID, runnerScaleSet)
}
return c.updateRunnerScaleSetResult.RunnerScaleSet, c.updateRunnerScaleSetResult.err
}
func (c *Client) DeleteRunnerScaleSet(ctx context.Context, runnerScaleSetID int) error {
return c.deleteRunnerScaleSetResult.err
}
@@ -0,0 +1,53 @@
package fake
import (
"context"
"github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient"
)
// MultiClientOption is a functional option for configuring a fake MultiClient
type MultiClientOption func(*MultiClient)
// WithClient configures the client that GetClientFor will return
func WithClient(c multiclient.Client) MultiClientOption {
return func(mc *MultiClient) {
mc.client = c
}
}
// WithGetClientForError configures an error that GetClientFor will return
func WithGetClientForError(err error) MultiClientOption {
return func(mc *MultiClient) {
mc.getClientForErr = err
}
}
// MultiClient implements multiclient.MultiClient interface for testing
type MultiClient struct {
client multiclient.Client
getClientForErr error
}
// Compile-time interface check
var _ multiclient.MultiClient = (*MultiClient)(nil)
// NewMultiClient creates a new fake MultiClient with the given options
func NewMultiClient(opts ...MultiClientOption) *MultiClient {
mc := &MultiClient{}
for _, opt := range opts {
opt(mc)
}
// Default behavior: if no client configured, return a default NewClient()
if mc.client == nil {
mc.client = NewClient()
}
return mc
}
func (mc *MultiClient) GetClientFor(ctx context.Context, opts *multiclient.ClientForOptions) (multiclient.Client, error) {
if mc.getClientForErr != nil {
return nil, mc.getClientForErr
}
return mc.client, nil
}
@@ -0,0 +1,177 @@
package multiclient
import (
"context"
"crypto/sha256"
"crypto/x509"
"fmt"
"net/http"
"net/url"
"strconv"
"sync"
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
"github.com/actions/actions-runner-controller/build"
"github.com/actions/actions-runner-controller/github/actions"
"github.com/actions/scaleset"
"github.com/google/uuid"
)
type MultiClient interface {
GetClientFor(ctx context.Context, opts *ClientForOptions) (Client, error)
}
type Scaleset struct {
mu sync.Mutex
clients map[string]*multiClientEntry
}
type multiClientEntry struct {
client *scaleset.Client
rootCAs *x509.CertPool
}
func NewScaleset() *Scaleset {
return &Scaleset{
clients: make(map[string]*multiClientEntry),
}
}
type Client interface {
SetSystemInfo(info scaleset.SystemInfo)
SystemInfo() scaleset.SystemInfo
MessageSessionClient(ctx context.Context, runnerScaleSetID int, owner string, options ...scaleset.HTTPOption) (*scaleset.MessageSessionClient, error)
GenerateJitRunnerConfig(ctx context.Context, jitRunnerSetting *scaleset.RunnerScaleSetJitRunnerSetting, scaleSetID int) (*scaleset.RunnerScaleSetJitRunnerConfig, error)
GetRunner(ctx context.Context, runnerID int) (*scaleset.RunnerReference, error)
GetRunnerByName(ctx context.Context, runnerName string) (*scaleset.RunnerReference, error)
RemoveRunner(ctx context.Context, runnerID int64) error
GetRunnerGroupByName(ctx context.Context, runnerGroup string) (*scaleset.RunnerGroup, error)
GetRunnerScaleSet(ctx context.Context, runnerGroupID int, runnerScaleSetName string) (*scaleset.RunnerScaleSet, error)
GetRunnerScaleSetByID(ctx context.Context, runnerScaleSetID int) (*scaleset.RunnerScaleSet, error)
CreateRunnerScaleSet(ctx context.Context, runnerScaleSet *scaleset.RunnerScaleSet) (*scaleset.RunnerScaleSet, error)
UpdateRunnerScaleSet(ctx context.Context, runnerScaleSetID int, runnerScaleSet *scaleset.RunnerScaleSet) (*scaleset.RunnerScaleSet, error)
DeleteRunnerScaleSet(ctx context.Context, runnerScaleSetID int) error
}
func (m *Scaleset) GetClientFor(ctx context.Context, opts *ClientForOptions) (Client, error) {
identifier, err := opts.identifier()
if err != nil {
return nil, fmt.Errorf("failed to generate client identifier: %w", err)
}
m.mu.Lock()
defer m.mu.Unlock()
entry, ok := m.clients[identifier]
if ok && entry.rootCAs.Equal(opts.RootCAs) {
return entry.client, nil
}
client, err := opts.newClient()
if err != nil {
return nil, fmt.Errorf("failed to create new client: %w", err)
}
m.clients[identifier] = &multiClientEntry{
client: client,
rootCAs: opts.RootCAs,
}
return client, nil
}
type ClientForOptions struct {
GithubConfigURL string
AppConfig appconfig.AppConfig
Namespace string
RootCAs *x509.CertPool
ProxyFunc func(*http.Request) (*url.URL, error)
}
func (o *ClientForOptions) identifier() (string, error) {
if err := o.AppConfig.Validate(); err != nil {
return "", fmt.Errorf("failed to validate app config: %w", err)
}
if _, err := actions.ParseGitHubConfigFromURL(o.GithubConfigURL); err != nil {
return "", fmt.Errorf("failed to parse GitHub config URL: %w", err)
}
if o.Namespace == "" {
return "", fmt.Errorf("namespace is required to generate client identifier")
}
identifier := fmt.Sprintf("configURL:%q,namespace:%q,proxy:%t", o.GithubConfigURL, o.Namespace, o.ProxyFunc != nil)
if o.AppConfig.Token != "" {
identifier += fmt.Sprintf(",token:%q,", o.AppConfig.Token)
} else {
identifier += fmt.Sprintf(
",appID:%q,installationID:%q,key:%q",
o.AppConfig.AppID,
strconv.FormatInt(o.AppConfig.AppInstallationID, 10),
o.AppConfig.AppPrivateKey,
)
}
if o.RootCAs != nil {
// ignoring because this cert pool is intended not to come from SystemCertPool
// nolint:staticcheck
identifier += fmt.Sprintf(",rootCAs:%q", o.RootCAs.Subjects())
}
return uuid.NewHash(sha256.New(), uuid.NameSpaceOID, []byte(identifier), 6).String(), nil
}
func (o *ClientForOptions) newClient() (*scaleset.Client, error) {
systemInfo := scaleset.SystemInfo{
System: "actions-runner-controller",
Version: build.Version,
CommitSHA: build.CommitSHA,
ScaleSetID: 0, // by default, scale set is 0 (not created yet)
Subsystem: "gha-scale-set-controller",
}
var options []scaleset.HTTPOption
if o.RootCAs != nil {
options = append(options, scaleset.WithRootCAs(o.RootCAs))
}
if o.ProxyFunc != nil {
options = append(options, scaleset.WithProxy(o.ProxyFunc))
}
if o.AppConfig.Token != "" {
c, err := scaleset.NewClientWithPersonalAccessToken(
scaleset.NewClientWithPersonalAccessTokenConfig{
GitHubConfigURL: o.GithubConfigURL,
PersonalAccessToken: o.AppConfig.Token,
SystemInfo: systemInfo,
},
options...,
)
if err != nil {
return nil, fmt.Errorf("failed to instantiate client with personal access token auth: %w", err)
}
return c, nil
}
c, err := scaleset.NewClientWithGitHubApp(
scaleset.ClientWithGitHubAppConfig{
GitHubConfigURL: o.GithubConfigURL,
GitHubAppAuth: scaleset.GitHubAppAuth{
ClientID: o.AppConfig.AppID,
InstallationID: o.AppConfig.AppInstallationID,
PrivateKey: o.AppConfig.AppPrivateKey,
},
SystemInfo: systemInfo,
},
options...,
)
if err != nil {
return nil, fmt.Errorf("failed to instantiate client with GitHub App auth: %w", err)
}
return c, nil
}
@@ -0,0 +1,16 @@
package object
import (
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type ActionsGitHubObject interface {
client.Object
GitHubConfigUrl() string
GitHubConfigSecret() string
GitHubProxy() *v1alpha1.ProxyConfig
GitHubServerTLS() *v1alpha1.TLSConfig
VaultConfig() *v1alpha1.VaultConfig
VaultProxy() *v1alpha1.ProxyConfig
}
@@ -2,6 +2,7 @@ package actionsgithubcom
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"maps" "maps"
@@ -14,10 +15,13 @@ import (
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig" "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
"github.com/actions/actions-runner-controller/build" "github.com/actions/actions-runner-controller/build"
ghalistenerconfig "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/controllers/actions.github.com/multiclient"
"github.com/actions/actions-runner-controller/controllers/actions.github.com/object"
"github.com/actions/actions-runner-controller/github/actions" "github.com/actions/actions-runner-controller/github/actions"
"github.com/actions/actions-runner-controller/hash" "github.com/actions/actions-runner-controller/hash"
"github.com/actions/actions-runner-controller/logging" "github.com/actions/actions-runner-controller/logging"
"github.com/actions/actions-runner-controller/vault/azurekeyvault" "github.com/actions/actions-runner-controller/vault/azurekeyvault"
"github.com/actions/scaleset"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -71,14 +75,14 @@ func SetListenerEntrypoint(entrypoint string) {
} }
} }
type ResourceBuilder struct { type SecretResolver interface {
ExcludeLabelPropagationPrefixes []string GetAppConfig(ctx context.Context, obj object.ActionsGitHubObject) (*appconfig.AppConfig, error)
*SecretResolver GetActionsService(ctx context.Context, obj object.ActionsGitHubObject) (multiclient.Client, error)
} }
// boolPtr returns a pointer to a bool value type ResourceBuilder struct {
func boolPtr(v bool) *bool { ExcludeLabelPropagationPrefixes []string
return &v SecretResolver
} }
func (b *ResourceBuilder) newAutoScalingListener(autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, ephemeralRunnerSet *v1alpha1.EphemeralRunnerSet, namespace, image string, imagePullSecrets []corev1.LocalObjectReference) (*v1alpha1.AutoscalingListener, error) { func (b *ResourceBuilder) newAutoScalingListener(autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, ephemeralRunnerSet *v1alpha1.EphemeralRunnerSet, namespace, image string, imagePullSecrets []corev1.LocalObjectReference) (*v1alpha1.AutoscalingListener, error) {
@@ -183,7 +187,7 @@ func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha
} }
config := ghalistenerconfig.Config{ config := ghalistenerconfig.Config{
ConfigureUrl: autoscalingListener.Spec.GitHubConfigUrl, ConfigureURL: autoscalingListener.Spec.GitHubConfigUrl,
EphemeralRunnerSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, EphemeralRunnerSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
EphemeralRunnerSetName: autoscalingListener.Spec.EphemeralRunnerSetName, EphemeralRunnerSetName: autoscalingListener.Spec.EphemeralRunnerSetName,
MaxRunners: autoscalingListener.Spec.MaxRunners, MaxRunners: autoscalingListener.Spec.MaxRunners,
@@ -319,8 +323,8 @@ func (b *ResourceBuilder) newScaleSetListenerPod(autoscalingListener *v1alpha1.A
Kind: autoscalingListener.GetObjectKind().GroupVersionKind().Kind, Kind: autoscalingListener.GetObjectKind().GroupVersionKind().Kind,
UID: autoscalingListener.GetUID(), UID: autoscalingListener.GetUID(),
Name: autoscalingListener.GetName(), Name: autoscalingListener.GetName(),
Controller: boolPtr(true), Controller: new(true),
BlockOwnerDeletion: boolPtr(true), BlockOwnerDeletion: new(true),
}, },
}, },
}, },
@@ -591,15 +595,15 @@ func (b *ResourceBuilder) newEphemeralRunnerSet(autoscalingRunnerSet *v1alpha1.A
Kind: autoscalingRunnerSet.GetObjectKind().GroupVersionKind().Kind, Kind: autoscalingRunnerSet.GetObjectKind().GroupVersionKind().Kind,
UID: autoscalingRunnerSet.GetUID(), UID: autoscalingRunnerSet.GetUID(),
Name: autoscalingRunnerSet.GetName(), Name: autoscalingRunnerSet.GetName(),
Controller: boolPtr(true), Controller: new(true),
BlockOwnerDeletion: boolPtr(true), BlockOwnerDeletion: new(true),
}, },
}, },
}, },
Spec: v1alpha1.EphemeralRunnerSetSpec{ Spec: v1alpha1.EphemeralRunnerSetSpec{
Replicas: 0, Replicas: 0,
EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{ EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{
RunnerScaleSetId: runnerScaleSetID, RunnerScaleSetID: runnerScaleSetID,
GitHubConfigUrl: autoscalingRunnerSet.Spec.GitHubConfigUrl, GitHubConfigUrl: autoscalingRunnerSet.Spec.GitHubConfigUrl,
GitHubConfigSecret: autoscalingRunnerSet.Spec.GitHubConfigSecret, GitHubConfigSecret: autoscalingRunnerSet.Spec.GitHubConfigSecret,
Proxy: autoscalingRunnerSet.Spec.Proxy, Proxy: autoscalingRunnerSet.Spec.Proxy,
@@ -645,8 +649,8 @@ func (b *ResourceBuilder) newEphemeralRunner(ephemeralRunnerSet *v1alpha1.Epheme
Kind: ephemeralRunnerSet.GetObjectKind().GroupVersionKind().Kind, Kind: ephemeralRunnerSet.GetObjectKind().GroupVersionKind().Kind,
UID: ephemeralRunnerSet.GetUID(), UID: ephemeralRunnerSet.GetUID(),
Name: ephemeralRunnerSet.GetName(), Name: ephemeralRunnerSet.GetName(),
Controller: boolPtr(true), Controller: new(true),
BlockOwnerDeletion: boolPtr(true), BlockOwnerDeletion: new(true),
}, },
}, },
}, },
@@ -683,8 +687,8 @@ func (b *ResourceBuilder) newEphemeralRunnerPod(runner *v1alpha1.EphemeralRunner
Kind: runner.GetObjectKind().GroupVersionKind().Kind, Kind: runner.GetObjectKind().GroupVersionKind().Kind,
UID: runner.GetUID(), UID: runner.GetUID(),
Name: runner.GetName(), Name: runner.GetName(),
Controller: boolPtr(true), Controller: new(true),
BlockOwnerDeletion: boolPtr(true), BlockOwnerDeletion: new(true),
}, },
}, },
} }
@@ -722,7 +726,7 @@ func (b *ResourceBuilder) newEphemeralRunnerPod(runner *v1alpha1.EphemeralRunner
return &newPod return &newPod
} }
func (b *ResourceBuilder) newEphemeralRunnerJitSecret(ephemeralRunner *v1alpha1.EphemeralRunner, jitConfig *actions.RunnerScaleSetJitRunnerConfig) *corev1.Secret { func (b *ResourceBuilder) newEphemeralRunnerJitSecret(ephemeralRunner *v1alpha1.EphemeralRunner, jitConfig *scaleset.RunnerScaleSetJitRunnerConfig) *corev1.Secret {
var ( var (
labels map[string]string labels map[string]string
annotations map[string]string annotations map[string]string
@@ -743,8 +747,8 @@ func (b *ResourceBuilder) newEphemeralRunnerJitSecret(ephemeralRunner *v1alpha1.
Data: map[string][]byte{ Data: map[string][]byte{
jitTokenKey: []byte(jitConfig.EncodedJITConfig), jitTokenKey: []byte(jitConfig.EncodedJITConfig),
"runnerName": []byte(jitConfig.Runner.Name), "runnerName": []byte(jitConfig.Runner.Name),
"runnerId": []byte(strconv.Itoa(jitConfig.Runner.Id)), "runnerId": []byte(strconv.Itoa(jitConfig.Runner.ID)),
"scaleSetId": []byte(strconv.Itoa(jitConfig.Runner.RunnerScaleSetId)), "scaleSetId": []byte(strconv.Itoa(jitConfig.Runner.RunnerScaleSetID)),
}, },
} }
} }
@@ -6,7 +6,7 @@ import (
"testing" "testing"
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
"github.com/actions/actions-runner-controller/github/actions" "github.com/actions/scaleset"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@@ -176,11 +176,11 @@ func TestMetadataPropagation(t *testing.T) {
assert.Equal(t, "ephemeral-runner-label", ephemeralRunner.Labels["test.com/ephemeral-runner-label"]) assert.Equal(t, "ephemeral-runner-label", ephemeralRunner.Labels["test.com/ephemeral-runner-label"])
assert.Equal(t, "ephemeral-runner-annotation", ephemeralRunner.Annotations["test.com/ephemeral-runner-annotation"]) assert.Equal(t, "ephemeral-runner-annotation", ephemeralRunner.Annotations["test.com/ephemeral-runner-annotation"])
runnerSecret := b.newEphemeralRunnerJitSecret(ephemeralRunner, &actions.RunnerScaleSetJitRunnerConfig{ runnerSecret := b.newEphemeralRunnerJitSecret(ephemeralRunner, &scaleset.RunnerScaleSetJitRunnerConfig{
Runner: &actions.RunnerReference{ Runner: &scaleset.RunnerReference{
Id: 1, ID: 1,
Name: "test", Name: "test",
RunnerScaleSetId: 1, RunnerScaleSetID: 1,
}, },
EncodedJITConfig: "", EncodedJITConfig: "",
}) })
@@ -1,16 +1,18 @@
package actionsgithubcom package secretresolver
import ( import (
"context" "context"
"crypto/x509"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log/slog"
"net/http" "net/http"
"net/url" "net/url"
"strings" "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/apis/actions.github.com/v1alpha1/appconfig"
"github.com/actions/actions-runner-controller/github/actions" "github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient"
"github.com/actions/actions-runner-controller/controllers/actions.github.com/object"
"github.com/actions/actions-runner-controller/vault" "github.com/actions/actions-runner-controller/vault"
"github.com/actions/actions-runner-controller/vault/azurekeyvault" "github.com/actions/actions-runner-controller/vault/azurekeyvault"
"golang.org/x/net/http/httpproxy" "golang.org/x/net/http/httpproxy"
@@ -21,19 +23,27 @@ import (
type SecretResolver struct { type SecretResolver struct {
k8sClient client.Client k8sClient client.Client
multiClient actions.MultiClient multiClient multiclient.MultiClient
logger *slog.Logger
} }
type SecretResolverOption func(*SecretResolver) type Option func(*SecretResolver)
func NewSecretResolver(k8sClient client.Client, multiClient actions.MultiClient, opts ...SecretResolverOption) *SecretResolver { func WithLogger(logger *slog.Logger) Option {
return func(sr *SecretResolver) {
sr.logger = logger
}
}
func New(k8sClient client.Client, scalesetMultiClient multiclient.MultiClient, opts ...Option) *SecretResolver {
if k8sClient == nil { if k8sClient == nil {
panic("k8sClient must not be nil") panic("k8sClient must not be nil")
} }
secretResolver := &SecretResolver{ secretResolver := &SecretResolver{
k8sClient: k8sClient, k8sClient: k8sClient,
multiClient: multiClient, multiClient: scalesetMultiClient,
logger: slog.New(slog.DiscardHandler),
} }
for _, opt := range opts { for _, opt := range opts {
@@ -43,17 +53,7 @@ func NewSecretResolver(k8sClient client.Client, multiClient actions.MultiClient,
return secretResolver return secretResolver
} }
type ActionsGitHubObject interface { func (sr *SecretResolver) GetAppConfig(ctx context.Context, obj object.ActionsGitHubObject) (*appconfig.AppConfig, error) {
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) resolver, err := sr.resolverForObject(ctx, obj)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get resolver for object: %v", err) return nil, fmt.Errorf("failed to get resolver for object: %v", err)
@@ -67,7 +67,7 @@ func (sr *SecretResolver) GetAppConfig(ctx context.Context, obj ActionsGitHubObj
return appConfig, nil return appConfig, nil
} }
func (sr *SecretResolver) GetActionsService(ctx context.Context, obj ActionsGitHubObject) (actions.ActionsService, error) { func (sr *SecretResolver) GetActionsService(ctx context.Context, obj object.ActionsGitHubObject) (multiclient.Client, error) {
resolver, err := sr.resolverForObject(ctx, obj) resolver, err := sr.resolverForObject(ctx, obj)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get resolver for object: %v", err) return nil, fmt.Errorf("failed to get resolver for object: %v", err)
@@ -78,7 +78,7 @@ func (sr *SecretResolver) GetActionsService(ctx context.Context, obj ActionsGitH
return nil, fmt.Errorf("failed to resolve app config: %v", err) return nil, fmt.Errorf("failed to resolve app config: %v", err)
} }
var clientOptions []actions.ClientOption var proxyFunc func(req *http.Request) (*url.URL, error)
if proxy := obj.GitHubProxy(); proxy != nil { if proxy := obj.GitHubProxy(); proxy != nil {
config := &httpproxy.Config{ config := &httpproxy.Config{
NoProxy: strings.Join(proxy.NoProxy, ","), NoProxy: strings.Join(proxy.NoProxy, ","),
@@ -116,16 +116,14 @@ func (sr *SecretResolver) GetActionsService(ctx context.Context, obj ActionsGitH
config.HTTPSProxy = u.String() config.HTTPSProxy = u.String()
} }
proxyFunc := func(req *http.Request) (*url.URL, error) { proxyFunc = func(req *http.Request) (*url.URL, error) {
return config.ProxyFunc()(req.URL) return config.ProxyFunc()(req.URL)
} }
clientOptions = append(clientOptions, actions.WithProxy(proxyFunc))
} }
tlsConfig := obj.GitHubServerTLS() var rootCAs *x509.CertPool
if tlsConfig != nil { if tc := obj.GitHubServerTLS(); tc != nil {
pool, err := tlsConfig.ToCertPool(func(name, key string) ([]byte, error) { pool, err := tc.ToCertPool(func(name, key string) ([]byte, error) {
var configmap corev1.ConfigMap var configmap corev1.ConfigMap
err := sr.k8sClient.Get( err := sr.k8sClient.Get(
ctx, ctx,
@@ -145,19 +143,22 @@ func (sr *SecretResolver) GetActionsService(ctx context.Context, obj ActionsGitH
return nil, fmt.Errorf("failed to get tls config: %w", err) return nil, fmt.Errorf("failed to get tls config: %w", err)
} }
clientOptions = append(clientOptions, actions.WithRootCAs(pool)) rootCAs = pool
} }
return sr.multiClient.GetClientFor( return sr.multiClient.GetClientFor(
ctx, ctx,
obj.GitHubConfigUrl(), &multiclient.ClientForOptions{
appConfig, GithubConfigURL: obj.GitHubConfigUrl(),
obj.GetNamespace(), AppConfig: *appConfig,
clientOptions..., Namespace: obj.GetNamespace(),
RootCAs: rootCAs,
ProxyFunc: proxyFunc,
},
) )
} }
func (sr *SecretResolver) resolverForObject(ctx context.Context, obj ActionsGitHubObject) (resolver, error) { func (sr *SecretResolver) resolverForObject(ctx context.Context, obj object.ActionsGitHubObject) (resolver, error) {
vaultConfig := obj.VaultConfig() vaultConfig := obj.VaultConfig()
if vaultConfig == nil || vaultConfig.Type == "" { if vaultConfig == nil || vaultConfig.Type == "" {
return &k8sResolver{ return &k8sResolver{
+1 -1
View File
@@ -6,7 +6,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0
github.com/actions/scaleset v0.1.1-0.20260218224657-feb84c6d04fb github.com/actions/scaleset v0.2.0
github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 github.com/bradleyfalzon/ghinstallation/v2 v2.17.0
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/evanphx/json-patch v5.9.11+incompatible github.com/evanphx/json-patch v5.9.11+incompatible
+2 -2
View File
@@ -25,8 +25,8 @@ github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBi
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/actions-runner-controller/httpcache v0.2.0 h1:hCNvYuVPJ2xxYBymqBvH0hSiQpqz4PHF/LbU3XghGNI= github.com/actions-runner-controller/httpcache v0.2.0 h1:hCNvYuVPJ2xxYBymqBvH0hSiQpqz4PHF/LbU3XghGNI=
github.com/actions-runner-controller/httpcache v0.2.0/go.mod h1:JLu9/2M/btPz1Zu/vTZ71XzukQHn2YeISPmJoM5exBI= github.com/actions-runner-controller/httpcache v0.2.0/go.mod h1:JLu9/2M/btPz1Zu/vTZ71XzukQHn2YeISPmJoM5exBI=
github.com/actions/scaleset v0.1.1-0.20260218224657-feb84c6d04fb h1:9jQ9/kHm00UTvZf5MiQcZgIVounynwFEhh0wCV3Ts00= github.com/actions/scaleset v0.2.0 h1:CKsDtTjOBCwjyT4ikwiMykMttzuKejimWRAvVr8xj9w=
github.com/actions/scaleset v0.1.1-0.20260218224657-feb84c6d04fb/go.mod h1:ncR5vzCCTUSyLgvclAtZ5dRBgF6qwA2nbTfTXmOJp84= github.com/actions/scaleset v0.2.0/go.mod h1:ncR5vzCCTUSyLgvclAtZ5dRBgF6qwA2nbTfTXmOJp84=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I= github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I=
+40
View File
@@ -0,0 +1,40 @@
package logger
import (
"fmt"
"log/slog"
"os"
"strings"
)
// New creates new slog.Logger based on the format
func New(logLevel string, logFormat string) (*slog.Logger, error) {
var lvl slog.Level
switch strings.ToLower(logLevel) {
case "debug":
lvl = slog.LevelDebug
case "info":
lvl = slog.LevelInfo
case "warn":
lvl = slog.LevelWarn
case "error":
lvl = slog.LevelError
default:
return nil, fmt.Errorf("invalid log level: %s", logLevel)
}
switch logFormat {
case "json":
return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true,
Level: lvl,
})), nil
case "text":
return slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true,
Level: lvl,
})), nil
default:
return nil, fmt.Errorf("invalid log format: %s", logFormat)
}
}
+9 -12
View File
@@ -22,21 +22,18 @@ const (
LogFormatJSON = "json" LogFormatJSON = "json"
) )
var ( var LogOpts = zap.Options{
LogOpts = zap.Options{ TimeEncoder: zapcore.TimeEncoderOfLayout(time.RFC3339),
TimeEncoder: zapcore.TimeEncoderOfLayout(time.RFC3339), Development: true,
Development: true, EncoderConfigOptions: []zap.EncoderConfigOption{
EncoderConfigOptions: []zap.EncoderConfigOption{ func(ec *zapcore.EncoderConfig) {
func(ec *zapcore.EncoderConfig) { ec.LevelKey = "severity"
ec.LevelKey = "severity" ec.MessageKey = "message"
ec.MessageKey = "message"
},
}, },
} },
) }
func NewLogger(logLevel string, logFormat string) (logr.Logger, error) { func NewLogger(logLevel string, logFormat string) (logr.Logger, error) {
if !validLogFormat(logFormat) { if !validLogFormat(logFormat) {
return logr.Logger{}, errors.New("invalid log format specified") return logr.Logger{}, errors.New("invalid log format specified")
} }
+18 -12
View File
@@ -28,9 +28,11 @@ import (
"github.com/actions/actions-runner-controller/build" "github.com/actions/actions-runner-controller/build"
actionsgithubcom "github.com/actions/actions-runner-controller/controllers/actions.github.com" actionsgithubcom "github.com/actions/actions-runner-controller/controllers/actions.github.com"
actionsgithubcommetrics "github.com/actions/actions-runner-controller/controllers/actions.github.com/metrics" actionsgithubcommetrics "github.com/actions/actions-runner-controller/controllers/actions.github.com/metrics"
"github.com/actions/actions-runner-controller/controllers/actions.github.com/multiclient"
"github.com/actions/actions-runner-controller/controllers/actions.github.com/secretresolver"
actionssummerwindnet "github.com/actions/actions-runner-controller/controllers/actions.summerwind.net" actionssummerwindnet "github.com/actions/actions-runner-controller/controllers/actions.summerwind.net"
"github.com/actions/actions-runner-controller/github" "github.com/actions/actions-runner-controller/github"
"github.com/actions/actions-runner-controller/github/actions" "github.com/actions/actions-runner-controller/logger"
"github.com/actions/actions-runner-controller/logging" "github.com/actions/actions-runner-controller/logging"
"github.com/kelseyhightower/envconfig" "github.com/kelseyhightower/envconfig"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@@ -85,7 +87,7 @@ func main() {
enableLeaderElection bool enableLeaderElection bool
disableAdmissionWebhook bool disableAdmissionWebhook bool
updateStrategy string updateStrategy string
leaderElectionId string leaderElectionID string
port int port int
syncPeriod time.Duration syncPeriod time.Duration
@@ -121,7 +123,7 @@ func main() {
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.") "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
flag.StringVar(&leaderElectionId, "leader-election-id", "actions-runner-controller", "Controller id for leader election.") flag.StringVar(&leaderElectionID, "leader-election-id", "actions-runner-controller", "Controller id for leader election.")
flag.StringVar(&runnerPodDefaults.RunnerImage, "runner-image", defaultRunnerImage, "The image name of self-hosted runner container to use by default if one isn't defined in yaml.") flag.StringVar(&runnerPodDefaults.RunnerImage, "runner-image", defaultRunnerImage, "The image name of self-hosted runner container to use by default if one isn't defined in yaml.")
flag.StringVar(&runnerPodDefaults.DockerImage, "docker-image", defaultDockerImage, "The image name of docker sidecar container to use by default if one isn't defined in yaml.") flag.StringVar(&runnerPodDefaults.DockerImage, "docker-image", defaultDockerImage, "The image name of docker sidecar container to use by default if one isn't defined in yaml.")
flag.StringVar(&runnerPodDefaults.DockerGID, "docker-gid", defaultDockerGID, "The default GID of docker group in the docker sidecar container. Use 1001 for dockerd sidecars of Ubuntu 20.04 runners 121 for Ubuntu 22.04 and 24.04.") flag.StringVar(&runnerPodDefaults.DockerGID, "docker-gid", defaultDockerGID, "The default GID of docker group in the docker sidecar container. Use 1001 for dockerd sidecars of Ubuntu 20.04 runners 121 for Ubuntu 22.04 and 24.04.")
@@ -239,7 +241,7 @@ func main() {
}, },
WebhookServer: webhookServer, WebhookServer: webhookServer,
LeaderElection: enableLeaderElection, LeaderElection: enableLeaderElection,
LeaderElectionID: leaderElectionId, LeaderElectionID: leaderElectionID,
Client: client.Options{ Client: client.Options{
Cache: &client.CacheOptions{ Cache: &client.CacheOptions{
DisableFor: []client.Object{ DisableFor: []client.Object{
@@ -270,13 +272,18 @@ func main() {
actionsgithubcommetrics.RegisterMetrics() actionsgithubcommetrics.RegisterMetrics()
} }
actionsMultiClient := actions.NewMultiClient( slogLogger, err := logger.New(logLevel, logFormat)
log.WithName("actions-clients"), if err != nil {
) log.Error(err, "unable to create logger for secret resolver")
os.Exit(1)
}
secretResolver := actionsgithubcom.NewSecretResolver( scalesetMultiClient := multiclient.NewScaleset()
secretResolver := secretresolver.New(
mgr.GetClient(), mgr.GetClient(),
actionsMultiClient, scalesetMultiClient,
secretresolver.WithLogger(slogLogger),
) )
rb := actionsgithubcom.ResourceBuilder{ rb := actionsgithubcom.ResourceBuilder{
@@ -292,7 +299,6 @@ func main() {
Scheme: mgr.GetScheme(), Scheme: mgr.GetScheme(),
ControllerNamespace: managerNamespace, ControllerNamespace: managerNamespace,
DefaultRunnerScaleSetListenerImage: managerImage, DefaultRunnerScaleSetListenerImage: managerImage,
ActionsClient: actionsMultiClient,
UpdateStrategy: actionsgithubcom.UpdateStrategy(updateStrategy), UpdateStrategy: actionsgithubcom.UpdateStrategy(updateStrategy),
DefaultRunnerScaleSetListenerImagePullSecrets: autoScalerImagePullSecrets, DefaultRunnerScaleSetListenerImagePullSecrets: autoScalerImagePullSecrets,
ResourceBuilder: rb, ResourceBuilder: rb,
@@ -399,7 +405,7 @@ func main() {
"default-docker-gid", runnerPodDefaults.DockerGID, "default-docker-gid", runnerPodDefaults.DockerGID,
"common-runnner-labels", commonRunnerLabels, "common-runnner-labels", commonRunnerLabels,
"leader-election-enabled", enableLeaderElection, "leader-election-enabled", enableLeaderElection,
"leader-election-id", leaderElectionId, "leader-election-id", leaderElectionID,
"watch-namespace", namespace, "watch-namespace", namespace,
) )
@@ -489,7 +495,7 @@ func (s *commaSeparatedStringSlice) String() string {
} }
func (s *commaSeparatedStringSlice) Set(value string) error { func (s *commaSeparatedStringSlice) Set(value string) error {
for _, v := range strings.Split(value, ",") { for v := range strings.SplitSeq(value, ",") {
if v == "" { if v == "" {
continue continue
} }