Azure Key Vault integration to resolve secrets (#4090)
This commit is contained in:
parent
d4af75d82e
commit
e46c929241
|
|
@ -0,0 +1,89 @@
|
||||||
|
package appconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AppConfig struct {
|
||||||
|
AppID string `json:"github_app_id"`
|
||||||
|
AppInstallationID int64 `json:"github_app_installation_id"`
|
||||||
|
AppPrivateKey string `json:"github_app_private_key"`
|
||||||
|
|
||||||
|
Token string `json:"github_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AppConfig) tidy() *AppConfig {
|
||||||
|
if len(c.Token) > 0 {
|
||||||
|
return &AppConfig{
|
||||||
|
Token: c.Token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AppConfig{
|
||||||
|
AppID: c.AppID,
|
||||||
|
AppInstallationID: c.AppInstallationID,
|
||||||
|
AppPrivateKey: c.AppPrivateKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AppConfig) Validate() error {
|
||||||
|
if c == nil {
|
||||||
|
return fmt.Errorf("missing app config")
|
||||||
|
}
|
||||||
|
hasToken := len(c.Token) > 0
|
||||||
|
hasGitHubAppAuth := c.hasGitHubAppAuth()
|
||||||
|
if hasToken && hasGitHubAppAuth {
|
||||||
|
return fmt.Errorf("both PAT and GitHub App credentials provided. should only provide one")
|
||||||
|
}
|
||||||
|
if !hasToken && !hasGitHubAppAuth {
|
||||||
|
return fmt.Errorf("no credentials provided: either a PAT or GitHub App credentials should be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AppConfig) hasGitHubAppAuth() bool {
|
||||||
|
return len(c.AppID) > 0 && c.AppInstallationID > 0 && len(c.AppPrivateKey) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromSecret(secret *corev1.Secret) (*AppConfig, error) {
|
||||||
|
var appInstallationID int64
|
||||||
|
if v := string(secret.Data["github_app_installation_id"]); v != "" {
|
||||||
|
val, err := strconv.ParseInt(v, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
appInstallationID = val
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &AppConfig{
|
||||||
|
Token: string(secret.Data["github_token"]),
|
||||||
|
AppID: string(secret.Data["github_app_id"]),
|
||||||
|
AppInstallationID: appInstallationID,
|
||||||
|
AppPrivateKey: string(secret.Data["github_app_private_key"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cfg.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to validate config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg.tidy(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromJSONString(v string) (*AppConfig, error) {
|
||||||
|
var appConfig AppConfig
|
||||||
|
if err := json.NewDecoder(bytes.NewBufferString(v)).Decode(&appConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := appConfig.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to validate app config decoded from string: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return appConfig.tidy(), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
package appconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppConfigValidate_invalid(t *testing.T) {
|
||||||
|
tt := map[string]*AppConfig{
|
||||||
|
"empty": {},
|
||||||
|
"token and app config": {
|
||||||
|
AppID: "1",
|
||||||
|
AppInstallationID: 2,
|
||||||
|
AppPrivateKey: "private key",
|
||||||
|
Token: "token",
|
||||||
|
},
|
||||||
|
"app id not set": {
|
||||||
|
AppInstallationID: 2,
|
||||||
|
AppPrivateKey: "private key",
|
||||||
|
},
|
||||||
|
"app installation id not set": {
|
||||||
|
AppID: "2",
|
||||||
|
AppPrivateKey: "private key",
|
||||||
|
},
|
||||||
|
"private key empty": {
|
||||||
|
AppID: "2",
|
||||||
|
AppInstallationID: 1,
|
||||||
|
AppPrivateKey: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, cfg := range tt {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
err := cfg.Validate()
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppConfigValidate_valid(t *testing.T) {
|
||||||
|
tt := map[string]*AppConfig{
|
||||||
|
"token": {
|
||||||
|
Token: "token",
|
||||||
|
},
|
||||||
|
"app ID": {
|
||||||
|
AppID: "1",
|
||||||
|
AppInstallationID: 2,
|
||||||
|
AppPrivateKey: "private key",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, cfg := range tt {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
err := cfg.Validate()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppConfigFromSecret_invalid(t *testing.T) {
|
||||||
|
tt := map[string]map[string]string{
|
||||||
|
"empty": {},
|
||||||
|
"token and app provided": {
|
||||||
|
"github_token": "token",
|
||||||
|
"github_app_id": "2",
|
||||||
|
"githu_app_installation_id": "3",
|
||||||
|
"github_app_private_key": "private key",
|
||||||
|
},
|
||||||
|
"invalid app id": {
|
||||||
|
"github_app_id": "abc",
|
||||||
|
"githu_app_installation_id": "3",
|
||||||
|
"github_app_private_key": "private key",
|
||||||
|
},
|
||||||
|
"invalid app installation_id": {
|
||||||
|
"github_app_id": "1",
|
||||||
|
"githu_app_installation_id": "abc",
|
||||||
|
"github_app_private_key": "private key",
|
||||||
|
},
|
||||||
|
"empty private key": {
|
||||||
|
"github_app_id": "1",
|
||||||
|
"githu_app_installation_id": "2",
|
||||||
|
"github_app_private_key": "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, data := range tt {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
secret := &corev1.Secret{
|
||||||
|
StringData: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
appConfig, err := FromSecret(secret)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, appConfig)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppConfigFromSecret_valid(t *testing.T) {
|
||||||
|
tt := map[string]map[string]string{
|
||||||
|
"with token": {
|
||||||
|
"github_token": "token",
|
||||||
|
},
|
||||||
|
"app config": {
|
||||||
|
"github_app_id": "2",
|
||||||
|
"githu_app_installation_id": "3",
|
||||||
|
"github_app_private_key": "private key",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, data := range tt {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
secret := &corev1.Secret{
|
||||||
|
StringData: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
appConfig, err := FromSecret(secret)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, appConfig)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppConfigFromString_valid(t *testing.T) {
|
||||||
|
tt := map[string]*AppConfig{
|
||||||
|
"token": {
|
||||||
|
Token: "token",
|
||||||
|
},
|
||||||
|
"app ID": {
|
||||||
|
AppID: "1",
|
||||||
|
AppInstallationID: 2,
|
||||||
|
AppPrivateKey: "private key",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, cfg := range tt {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
bytes, err := json.Marshal(cfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got, err := FromJSONString(string(bytes))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
want := cfg.tidy()
|
||||||
|
assert.Equal(t, want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -59,7 +59,10 @@ type AutoscalingListenerSpec struct {
|
||||||
Proxy *ProxyConfig `json:"proxy,omitempty"`
|
Proxy *ProxyConfig `json:"proxy,omitempty"`
|
||||||
|
|
||||||
// +optional
|
// +optional
|
||||||
GitHubServerTLS *GitHubServerTLSConfig `json:"githubServerTLS,omitempty"`
|
GitHubServerTLS *TLSConfig `json:"githubServerTLS,omitempty"`
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
VaultConfig *VaultConfig `json:"vaultConfig,omitempty"`
|
||||||
|
|
||||||
// +optional
|
// +optional
|
||||||
Metrics *MetricsConfig `json:"metrics,omitempty"`
|
Metrics *MetricsConfig `json:"metrics,omitempty"`
|
||||||
|
|
@ -87,7 +90,6 @@ type AutoscalingListener struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// +kubebuilder:object:root=true
|
// +kubebuilder:object:root=true
|
||||||
|
|
||||||
// AutoscalingListenerList contains a list of AutoscalingListener
|
// AutoscalingListenerList contains a list of AutoscalingListener
|
||||||
type AutoscalingListenerList struct {
|
type AutoscalingListenerList struct {
|
||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/hash"
|
"github.com/actions/actions-runner-controller/hash"
|
||||||
|
"github.com/actions/actions-runner-controller/vault"
|
||||||
"golang.org/x/net/http/httpproxy"
|
"golang.org/x/net/http/httpproxy"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
@ -69,7 +70,10 @@ type AutoscalingRunnerSetSpec struct {
|
||||||
Proxy *ProxyConfig `json:"proxy,omitempty"`
|
Proxy *ProxyConfig `json:"proxy,omitempty"`
|
||||||
|
|
||||||
// +optional
|
// +optional
|
||||||
GitHubServerTLS *GitHubServerTLSConfig `json:"githubServerTLS,omitempty"`
|
GitHubServerTLS *TLSConfig `json:"githubServerTLS,omitempty"`
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
VaultConfig *VaultConfig `json:"vaultConfig,omitempty"`
|
||||||
|
|
||||||
// Required
|
// Required
|
||||||
Template corev1.PodTemplateSpec `json:"template,omitempty"`
|
Template corev1.PodTemplateSpec `json:"template,omitempty"`
|
||||||
|
|
@ -89,12 +93,12 @@ type AutoscalingRunnerSetSpec struct {
|
||||||
MinRunners *int `json:"minRunners,omitempty"`
|
MinRunners *int `json:"minRunners,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GitHubServerTLSConfig struct {
|
type TLSConfig struct {
|
||||||
// Required
|
// Required
|
||||||
CertificateFrom *TLSCertificateSource `json:"certificateFrom,omitempty"`
|
CertificateFrom *TLSCertificateSource `json:"certificateFrom,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitHubServerTLSConfig) ToCertPool(keyFetcher func(name, key string) ([]byte, error)) (*x509.CertPool, error) {
|
func (c *TLSConfig) ToCertPool(keyFetcher func(name, key string) ([]byte, error)) (*x509.CertPool, error) {
|
||||||
if c.CertificateFrom == nil {
|
if c.CertificateFrom == nil {
|
||||||
return nil, fmt.Errorf("certificateFrom not specified")
|
return nil, fmt.Errorf("certificateFrom not specified")
|
||||||
}
|
}
|
||||||
|
|
@ -142,7 +146,7 @@ type ProxyConfig struct {
|
||||||
NoProxy []string `json:"noProxy,omitempty"`
|
NoProxy []string `json:"noProxy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ProxyConfig) toHTTPProxyConfig(secretFetcher func(string) (*corev1.Secret, error)) (*httpproxy.Config, error) {
|
func (c *ProxyConfig) ToHTTPProxyConfig(secretFetcher func(string) (*corev1.Secret, error)) (*httpproxy.Config, error) {
|
||||||
config := &httpproxy.Config{
|
config := &httpproxy.Config{
|
||||||
NoProxy: strings.Join(c.NoProxy, ","),
|
NoProxy: strings.Join(c.NoProxy, ","),
|
||||||
}
|
}
|
||||||
|
|
@ -201,7 +205,7 @@ func (c *ProxyConfig) toHTTPProxyConfig(secretFetcher func(string) (*corev1.Secr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ProxyConfig) ToSecretData(secretFetcher func(string) (*corev1.Secret, error)) (map[string][]byte, error) {
|
func (c *ProxyConfig) ToSecretData(secretFetcher func(string) (*corev1.Secret, error)) (map[string][]byte, error) {
|
||||||
config, err := c.toHTTPProxyConfig(secretFetcher)
|
config, err := c.ToHTTPProxyConfig(secretFetcher)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -215,7 +219,7 @@ func (c *ProxyConfig) ToSecretData(secretFetcher func(string) (*corev1.Secret, e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ProxyConfig) ProxyFunc(secretFetcher func(string) (*corev1.Secret, error)) (func(*http.Request) (*url.URL, error), error) {
|
func (c *ProxyConfig) ProxyFunc(secretFetcher func(string) (*corev1.Secret, error)) (func(*http.Request) (*url.URL, error), error) {
|
||||||
config, err := c.toHTTPProxyConfig(secretFetcher)
|
config, err := c.ToHTTPProxyConfig(secretFetcher)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -235,6 +239,26 @@ type ProxyServerConfig struct {
|
||||||
CredentialSecretRef string `json:"credentialSecretRef,omitempty"`
|
CredentialSecretRef string `json:"credentialSecretRef,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VaultConfig struct {
|
||||||
|
// +optional
|
||||||
|
Type vault.VaultType `json:"type,omitempty"`
|
||||||
|
// +optional
|
||||||
|
AzureKeyVault *AzureKeyVaultConfig `json:"azureKeyVault,omitempty"`
|
||||||
|
// +optional
|
||||||
|
Proxy *ProxyConfig `json:"proxy,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AzureKeyVaultConfig struct {
|
||||||
|
// +required
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
// +required
|
||||||
|
TenantID string `json:"tenantId,omitempty"`
|
||||||
|
// +required
|
||||||
|
ClientID string `json:"clientId,omitempty"`
|
||||||
|
// +required
|
||||||
|
CertificatePath string `json:"certificatePath,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// MetricsConfig holds configuration parameters for each metric type
|
// MetricsConfig holds configuration parameters for each metric type
|
||||||
type MetricsConfig struct {
|
type MetricsConfig struct {
|
||||||
// +optional
|
// +optional
|
||||||
|
|
@ -285,6 +309,33 @@ func (ars *AutoscalingRunnerSet) ListenerSpecHash() string {
|
||||||
return hash.ComputeTemplateHash(&spec)
|
return hash.ComputeTemplateHash(&spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ars *AutoscalingRunnerSet) GitHubConfigSecret() string {
|
||||||
|
return ars.Spec.GitHubConfigSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ars *AutoscalingRunnerSet) GitHubConfigUrl() string {
|
||||||
|
return ars.Spec.GitHubConfigUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ars *AutoscalingRunnerSet) GitHubProxy() *ProxyConfig {
|
||||||
|
return ars.Spec.Proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ars *AutoscalingRunnerSet) GitHubServerTLS() *TLSConfig {
|
||||||
|
return ars.Spec.GitHubServerTLS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ars *AutoscalingRunnerSet) VaultConfig() *VaultConfig {
|
||||||
|
return ars.Spec.VaultConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ars *AutoscalingRunnerSet) VaultProxy() *ProxyConfig {
|
||||||
|
if ars.Spec.VaultConfig != nil {
|
||||||
|
return ars.Spec.VaultConfig.Proxy
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ars *AutoscalingRunnerSet) RunnerSetSpecHash() string {
|
func (ars *AutoscalingRunnerSet) RunnerSetSpecHash() string {
|
||||||
type runnerSetSpec struct {
|
type runnerSetSpec struct {
|
||||||
GitHubConfigUrl string
|
GitHubConfigUrl string
|
||||||
|
|
@ -292,7 +343,7 @@ func (ars *AutoscalingRunnerSet) RunnerSetSpecHash() string {
|
||||||
RunnerGroup string
|
RunnerGroup string
|
||||||
RunnerScaleSetName string
|
RunnerScaleSetName string
|
||||||
Proxy *ProxyConfig
|
Proxy *ProxyConfig
|
||||||
GitHubServerTLS *GitHubServerTLSConfig
|
GitHubServerTLS *TLSConfig
|
||||||
Template corev1.PodTemplateSpec
|
Template corev1.PodTemplateSpec
|
||||||
}
|
}
|
||||||
spec := &runnerSetSpec{
|
spec := &runnerSetSpec{
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,33 @@ func (er *EphemeralRunner) HasContainerHookConfigured() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (er *EphemeralRunner) GitHubConfigSecret() string {
|
||||||
|
return er.Spec.GitHubConfigSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (er *EphemeralRunner) GitHubConfigUrl() string {
|
||||||
|
return er.Spec.GitHubConfigUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (er *EphemeralRunner) GitHubProxy() *ProxyConfig {
|
||||||
|
return er.Spec.Proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (er *EphemeralRunner) GitHubServerTLS() *TLSConfig {
|
||||||
|
return er.Spec.GitHubServerTLS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (er *EphemeralRunner) VaultConfig() *VaultConfig {
|
||||||
|
return er.Spec.VaultConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (er *EphemeralRunner) VaultProxy() *ProxyConfig {
|
||||||
|
if er.Spec.VaultConfig != nil {
|
||||||
|
return er.Spec.VaultConfig.Proxy
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// EphemeralRunnerSpec defines the desired state of EphemeralRunner
|
// EphemeralRunnerSpec defines the desired state of EphemeralRunner
|
||||||
type EphemeralRunnerSpec struct {
|
type EphemeralRunnerSpec struct {
|
||||||
// +required
|
// +required
|
||||||
|
|
@ -75,6 +102,9 @@ type EphemeralRunnerSpec struct {
|
||||||
// +required
|
// +required
|
||||||
GitHubConfigSecret string `json:"githubConfigSecret,omitempty"`
|
GitHubConfigSecret string `json:"githubConfigSecret,omitempty"`
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
GitHubServerTLS *TLSConfig `json:"githubServerTLS,omitempty"`
|
||||||
|
|
||||||
// +required
|
// +required
|
||||||
RunnerScaleSetId int `json:"runnerScaleSetId,omitempty"`
|
RunnerScaleSetId int `json:"runnerScaleSetId,omitempty"`
|
||||||
|
|
||||||
|
|
@ -85,7 +115,7 @@ type EphemeralRunnerSpec struct {
|
||||||
ProxySecretRef string `json:"proxySecretRef,omitempty"`
|
ProxySecretRef string `json:"proxySecretRef,omitempty"`
|
||||||
|
|
||||||
// +optional
|
// +optional
|
||||||
GitHubServerTLS *GitHubServerTLSConfig `json:"githubServerTLS,omitempty"`
|
VaultConfig *VaultConfig `json:"vaultConfig,omitempty"`
|
||||||
|
|
||||||
corev1.PodTemplateSpec `json:",inline"`
|
corev1.PodTemplateSpec `json:",inline"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,9 +60,35 @@ type EphemeralRunnerSet struct {
|
||||||
Status EphemeralRunnerSetStatus `json:"status,omitempty"`
|
Status EphemeralRunnerSetStatus `json:"status,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +kubebuilder:object:root=true
|
func (ers *EphemeralRunnerSet) GitHubConfigSecret() string {
|
||||||
|
return ers.Spec.EphemeralRunnerSpec.GitHubConfigSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ers *EphemeralRunnerSet) GitHubConfigUrl() string {
|
||||||
|
return ers.Spec.EphemeralRunnerSpec.GitHubConfigUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ers *EphemeralRunnerSet) GitHubProxy() *ProxyConfig {
|
||||||
|
return ers.Spec.EphemeralRunnerSpec.Proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ers *EphemeralRunnerSet) GitHubServerTLS() *TLSConfig {
|
||||||
|
return ers.Spec.EphemeralRunnerSpec.GitHubServerTLS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ers *EphemeralRunnerSet) VaultConfig() *VaultConfig {
|
||||||
|
return ers.Spec.EphemeralRunnerSpec.VaultConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ers *EphemeralRunnerSet) VaultProxy() *ProxyConfig {
|
||||||
|
if ers.Spec.EphemeralRunnerSpec.VaultConfig != nil {
|
||||||
|
return ers.Spec.EphemeralRunnerSpec.VaultConfig.Proxy
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// EphemeralRunnerSetList contains a list of EphemeralRunnerSet
|
// EphemeralRunnerSetList contains a list of EphemeralRunnerSet
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
type EphemeralRunnerSetList struct {
|
type EphemeralRunnerSetList struct {
|
||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
metav1.ListMeta `json:"metadata,omitempty"`
|
metav1.ListMeta `json:"metadata,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import (
|
||||||
|
|
||||||
func TestGitHubServerTLSConfig_ToCertPool(t *testing.T) {
|
func TestGitHubServerTLSConfig_ToCertPool(t *testing.T) {
|
||||||
t.Run("returns an error if CertificateFrom not specified", func(t *testing.T) {
|
t.Run("returns an error if CertificateFrom not specified", func(t *testing.T) {
|
||||||
c := &v1alpha1.GitHubServerTLSConfig{
|
c := &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: nil,
|
CertificateFrom: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@ func TestGitHubServerTLSConfig_ToCertPool(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("returns an error if CertificateFrom.ConfigMapKeyRef not specified", func(t *testing.T) {
|
t.Run("returns an error if CertificateFrom.ConfigMapKeyRef not specified", func(t *testing.T) {
|
||||||
c := &v1alpha1.GitHubServerTLSConfig{
|
c := &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{},
|
CertificateFrom: &v1alpha1.TLSCertificateSource{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ func TestGitHubServerTLSConfig_ToCertPool(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("returns a valid cert pool with correct configuration", func(t *testing.T) {
|
t.Run("returns a valid cert pool with correct configuration", func(t *testing.T) {
|
||||||
c := &v1alpha1.GitHubServerTLSConfig{
|
c := &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: v1.LocalObjectReference{
|
LocalObjectReference: v1.LocalObjectReference{
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,12 @@ func (in *AutoscalingListenerSpec) DeepCopyInto(out *AutoscalingListenerSpec) {
|
||||||
}
|
}
|
||||||
if in.GitHubServerTLS != nil {
|
if in.GitHubServerTLS != nil {
|
||||||
in, out := &in.GitHubServerTLS, &out.GitHubServerTLS
|
in, out := &in.GitHubServerTLS, &out.GitHubServerTLS
|
||||||
*out = new(GitHubServerTLSConfig)
|
*out = new(TLSConfig)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.VaultConfig != nil {
|
||||||
|
in, out := &in.VaultConfig, &out.VaultConfig
|
||||||
|
*out = new(VaultConfig)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
if in.Metrics != nil {
|
if in.Metrics != nil {
|
||||||
|
|
@ -209,7 +214,12 @@ func (in *AutoscalingRunnerSetSpec) DeepCopyInto(out *AutoscalingRunnerSetSpec)
|
||||||
}
|
}
|
||||||
if in.GitHubServerTLS != nil {
|
if in.GitHubServerTLS != nil {
|
||||||
in, out := &in.GitHubServerTLS, &out.GitHubServerTLS
|
in, out := &in.GitHubServerTLS, &out.GitHubServerTLS
|
||||||
*out = new(GitHubServerTLSConfig)
|
*out = new(TLSConfig)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.VaultConfig != nil {
|
||||||
|
in, out := &in.VaultConfig, &out.VaultConfig
|
||||||
|
*out = new(VaultConfig)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
in.Template.DeepCopyInto(&out.Template)
|
in.Template.DeepCopyInto(&out.Template)
|
||||||
|
|
@ -260,6 +270,21 @@ func (in *AutoscalingRunnerSetStatus) DeepCopy() *AutoscalingRunnerSetStatus {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *AzureKeyVaultConfig) DeepCopyInto(out *AzureKeyVaultConfig) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureKeyVaultConfig.
|
||||||
|
func (in *AzureKeyVaultConfig) DeepCopy() *AzureKeyVaultConfig {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(AzureKeyVaultConfig)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *CounterMetric) DeepCopyInto(out *CounterMetric) {
|
func (in *CounterMetric) DeepCopyInto(out *CounterMetric) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|
@ -432,14 +457,19 @@ func (in *EphemeralRunnerSetStatus) DeepCopy() *EphemeralRunnerSetStatus {
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *EphemeralRunnerSpec) DeepCopyInto(out *EphemeralRunnerSpec) {
|
func (in *EphemeralRunnerSpec) DeepCopyInto(out *EphemeralRunnerSpec) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.GitHubServerTLS != nil {
|
||||||
|
in, out := &in.GitHubServerTLS, &out.GitHubServerTLS
|
||||||
|
*out = new(TLSConfig)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
if in.Proxy != nil {
|
if in.Proxy != nil {
|
||||||
in, out := &in.Proxy, &out.Proxy
|
in, out := &in.Proxy, &out.Proxy
|
||||||
*out = new(ProxyConfig)
|
*out = new(ProxyConfig)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
if in.GitHubServerTLS != nil {
|
if in.VaultConfig != nil {
|
||||||
in, out := &in.GitHubServerTLS, &out.GitHubServerTLS
|
in, out := &in.VaultConfig, &out.VaultConfig
|
||||||
*out = new(GitHubServerTLSConfig)
|
*out = new(VaultConfig)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
in.PodTemplateSpec.DeepCopyInto(&out.PodTemplateSpec)
|
in.PodTemplateSpec.DeepCopyInto(&out.PodTemplateSpec)
|
||||||
|
|
@ -497,26 +527,6 @@ func (in *GaugeMetric) DeepCopy() *GaugeMetric {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *GitHubServerTLSConfig) DeepCopyInto(out *GitHubServerTLSConfig) {
|
|
||||||
*out = *in
|
|
||||||
if in.CertificateFrom != nil {
|
|
||||||
in, out := &in.CertificateFrom, &out.CertificateFrom
|
|
||||||
*out = new(TLSCertificateSource)
|
|
||||||
(*in).DeepCopyInto(*out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitHubServerTLSConfig.
|
|
||||||
func (in *GitHubServerTLSConfig) DeepCopy() *GitHubServerTLSConfig {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(GitHubServerTLSConfig)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *HistogramMetric) DeepCopyInto(out *HistogramMetric) {
|
func (in *HistogramMetric) DeepCopyInto(out *HistogramMetric) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|
@ -669,3 +679,48 @@ func (in *TLSCertificateSource) DeepCopy() *TLSCertificateSource {
|
||||||
in.DeepCopyInto(out)
|
in.DeepCopyInto(out)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TLSConfig) DeepCopyInto(out *TLSConfig) {
|
||||||
|
*out = *in
|
||||||
|
if in.CertificateFrom != nil {
|
||||||
|
in, out := &in.CertificateFrom, &out.CertificateFrom
|
||||||
|
*out = new(TLSCertificateSource)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSConfig.
|
||||||
|
func (in *TLSConfig) DeepCopy() *TLSConfig {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TLSConfig)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *VaultConfig) DeepCopyInto(out *VaultConfig) {
|
||||||
|
*out = *in
|
||||||
|
if in.AzureKeyVault != nil {
|
||||||
|
in, out := &in.AzureKeyVault, &out.AzureKeyVault
|
||||||
|
*out = new(AzureKeyVaultConfig)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.Proxy != nil {
|
||||||
|
in, out := &in.Proxy, &out.Proxy
|
||||||
|
*out = new(ProxyConfig)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultConfig.
|
||||||
|
func (in *VaultConfig) DeepCopy() *VaultConfig {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(VaultConfig)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7863,6 +7863,53 @@ spec:
|
||||||
- containers
|
- containers
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
|
vaultConfig:
|
||||||
|
properties:
|
||||||
|
azureKeyVault:
|
||||||
|
properties:
|
||||||
|
certificatePath:
|
||||||
|
type: string
|
||||||
|
clientId:
|
||||||
|
type: string
|
||||||
|
tenantId:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- certificatePath
|
||||||
|
- clientId
|
||||||
|
- tenantId
|
||||||
|
- url
|
||||||
|
type: object
|
||||||
|
proxy:
|
||||||
|
properties:
|
||||||
|
http:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
https:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
noProxy:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
type:
|
||||||
|
description: |-
|
||||||
|
VaultType represents the type of vault that can be used in the application.
|
||||||
|
It is used to identify which vault integration should be used to resolve secrets.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
description: AutoscalingListenerStatus defines the observed state of AutoscalingListener
|
description: AutoscalingListenerStatus defines the observed state of AutoscalingListener
|
||||||
|
|
|
||||||
|
|
@ -15504,6 +15504,53 @@ spec:
|
||||||
- containers
|
- containers
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
|
vaultConfig:
|
||||||
|
properties:
|
||||||
|
azureKeyVault:
|
||||||
|
properties:
|
||||||
|
certificatePath:
|
||||||
|
type: string
|
||||||
|
clientId:
|
||||||
|
type: string
|
||||||
|
tenantId:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- certificatePath
|
||||||
|
- clientId
|
||||||
|
- tenantId
|
||||||
|
- url
|
||||||
|
type: object
|
||||||
|
proxy:
|
||||||
|
properties:
|
||||||
|
http:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
https:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
noProxy:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
type:
|
||||||
|
description: |-
|
||||||
|
VaultType represents the type of vault that can be used in the application.
|
||||||
|
It is used to identify which vault integration should be used to resolve secrets.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
description: AutoscalingRunnerSetStatus defines the observed state of AutoscalingRunnerSet
|
description: AutoscalingRunnerSetStatus defines the observed state of AutoscalingRunnerSet
|
||||||
|
|
|
||||||
|
|
@ -7784,6 +7784,53 @@ spec:
|
||||||
required:
|
required:
|
||||||
- containers
|
- containers
|
||||||
type: object
|
type: object
|
||||||
|
vaultConfig:
|
||||||
|
properties:
|
||||||
|
azureKeyVault:
|
||||||
|
properties:
|
||||||
|
certificatePath:
|
||||||
|
type: string
|
||||||
|
clientId:
|
||||||
|
type: string
|
||||||
|
tenantId:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- certificatePath
|
||||||
|
- clientId
|
||||||
|
- tenantId
|
||||||
|
- url
|
||||||
|
type: object
|
||||||
|
proxy:
|
||||||
|
properties:
|
||||||
|
http:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
https:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
noProxy:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
type:
|
||||||
|
description: |-
|
||||||
|
VaultType represents the type of vault that can be used in the application.
|
||||||
|
It is used to identify which vault integration should be used to resolve secrets.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
required:
|
required:
|
||||||
- githubConfigSecret
|
- githubConfigSecret
|
||||||
- githubConfigUrl
|
- githubConfigUrl
|
||||||
|
|
|
||||||
|
|
@ -7778,6 +7778,53 @@ spec:
|
||||||
required:
|
required:
|
||||||
- containers
|
- containers
|
||||||
type: object
|
type: object
|
||||||
|
vaultConfig:
|
||||||
|
properties:
|
||||||
|
azureKeyVault:
|
||||||
|
properties:
|
||||||
|
certificatePath:
|
||||||
|
type: string
|
||||||
|
clientId:
|
||||||
|
type: string
|
||||||
|
tenantId:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- certificatePath
|
||||||
|
- clientId
|
||||||
|
- tenantId
|
||||||
|
- url
|
||||||
|
type: object
|
||||||
|
proxy:
|
||||||
|
properties:
|
||||||
|
http:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
https:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
noProxy:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
type:
|
||||||
|
description: |-
|
||||||
|
VaultType represents the type of vault that can be used in the application.
|
||||||
|
It is used to identify which vault integration should be used to resolve secrets.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
required:
|
required:
|
||||||
- githubConfigSecret
|
- githubConfigSecret
|
||||||
- githubConfigUrl
|
- githubConfigUrl
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ metadata:
|
||||||
{{- if and (ne $containerMode.type "kubernetes") (not .Values.template.spec.serviceAccountName) }}
|
{{- if and (ne $containerMode.type "kubernetes") (not .Values.template.spec.serviceAccountName) }}
|
||||||
actions.github.com/cleanup-no-permission-service-account-name: {{ include "gha-runner-scale-set.noPermissionServiceAccountName" . }}
|
actions.github.com/cleanup-no-permission-service-account-name: {{ include "gha-runner-scale-set.noPermissionServiceAccountName" . }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
spec:
|
spec:
|
||||||
githubConfigUrl: {{ required ".Values.githubConfigUrl is required" (trimSuffix "/" .Values.githubConfigUrl) }}
|
githubConfigUrl: {{ required ".Values.githubConfigUrl is required" (trimSuffix "/" .Values.githubConfigUrl) }}
|
||||||
githubConfigSecret: {{ include "gha-runner-scale-set.githubsecret" . }}
|
githubConfigSecret: {{ include "gha-runner-scale-set.githubsecret" . }}
|
||||||
|
|
@ -65,6 +66,24 @@ spec:
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if and .Values.keyVault .Values.keyVault.type }}
|
||||||
|
vaultConfig:
|
||||||
|
type: {{ .Values.keyVault.type }}
|
||||||
|
{{- if .Values.keyVault.proxy }}
|
||||||
|
proxy: {{- toYaml .Values.keyVault.proxy | nindent 6 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if eq .Values.keyVault.type "azure_key_vault" }}
|
||||||
|
azureKeyVault:
|
||||||
|
url: {{ .Values.keyVault.azureKeyVault.url }}
|
||||||
|
tenantId: {{ .Values.keyVault.azureKeyVault.tenantId }}
|
||||||
|
clientId: {{ .Values.keyVault.azureKeyVault.clientId }}
|
||||||
|
certificatePath: {{ .Values.keyVault.azureKeyVault.certificatePath }}
|
||||||
|
secretKey: {{ .Values.keyVault.azureKeyVault.secretKey }}
|
||||||
|
{{- else }}
|
||||||
|
{{- fail "Unsupported keyVault type: " .Values.keyVault.type }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
{{- if .Values.proxy }}
|
{{- if .Values.proxy }}
|
||||||
proxy:
|
proxy:
|
||||||
{{- if .Values.proxy.http }}
|
{{- if .Values.proxy.http }}
|
||||||
|
|
|
||||||
|
|
@ -1158,7 +1158,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
|
||||||
ars := render(t, options)
|
ars := render(t, options)
|
||||||
|
|
||||||
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
||||||
expected := &v1alpha1.GitHubServerTLSConfig{
|
expected := &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
|
@ -1218,7 +1218,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
|
||||||
ars := render(t, options)
|
ars := render(t, options)
|
||||||
|
|
||||||
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
||||||
expected := &v1alpha1.GitHubServerTLSConfig{
|
expected := &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
|
@ -1278,7 +1278,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
|
||||||
ars := render(t, options)
|
ars := render(t, options)
|
||||||
|
|
||||||
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
||||||
expected := &v1alpha1.GitHubServerTLSConfig{
|
expected := &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
|
@ -1338,7 +1338,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
|
||||||
ars := render(t, options)
|
ars := render(t, options)
|
||||||
|
|
||||||
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
||||||
expected := &v1alpha1.GitHubServerTLSConfig{
|
expected := &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
|
@ -1394,7 +1394,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
|
||||||
ars := render(t, options)
|
ars := render(t, options)
|
||||||
|
|
||||||
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
||||||
expected := &v1alpha1.GitHubServerTLSConfig{
|
expected := &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
|
@ -1450,7 +1450,7 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
|
||||||
ars := render(t, options)
|
ars := render(t, options)
|
||||||
|
|
||||||
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
||||||
expected := &v1alpha1.GitHubServerTLSConfig{
|
expected := &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
|
@ -2468,3 +2468,43 @@ func TestNamespaceOverride(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAutoscalingRunnerSetCustomAnnotationsAndLabelsApplied(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Path to the helm chart we will test
|
||||||
|
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
releaseName := "test-runners"
|
||||||
|
namespaceName := "test-" + strings.ToLower(random.UniqueId())
|
||||||
|
|
||||||
|
options := &helm.Options{
|
||||||
|
Logger: logger.Discard,
|
||||||
|
SetValues: map[string]string{
|
||||||
|
"githubConfigUrl": "https://github.com/actions",
|
||||||
|
"githubConfigSecret.github_token": "gh_token12345",
|
||||||
|
"controllerServiceAccount.name": "arc",
|
||||||
|
"controllerServiceAccount.namespace": "arc-system",
|
||||||
|
"annotations.actions\\.github\\.com/vault": "azure_key_vault",
|
||||||
|
"annotations.actions\\.github\\.com/cleanup-manager-role-name": "not-propagated",
|
||||||
|
"labels.custom": "custom",
|
||||||
|
"labels.app\\.kubernetes\\.io/component": "not-propagated",
|
||||||
|
},
|
||||||
|
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
|
||||||
|
}
|
||||||
|
|
||||||
|
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/autoscalingrunnerset.yaml"})
|
||||||
|
|
||||||
|
var autoscalingRunnerSet v1alpha1.AutoscalingRunnerSet
|
||||||
|
helm.UnmarshalK8SYaml(t, output, &autoscalingRunnerSet)
|
||||||
|
|
||||||
|
vault := autoscalingRunnerSet.Annotations["actions.github.com/vault"]
|
||||||
|
assert.Equal(t, "azure_key_vault", vault)
|
||||||
|
|
||||||
|
custom := autoscalingRunnerSet.Labels["custom"]
|
||||||
|
assert.Equal(t, "custom", custom)
|
||||||
|
|
||||||
|
assert.NotEqual(t, "not-propagated", autoscalingRunnerSet.Annotations["actions.github.com/cleanup-manager-role-name"])
|
||||||
|
assert.NotEqual(t, "not-propagated", autoscalingRunnerSet.Labels["app.kubernetes.io/component"])
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ githubConfigUrl: ""
|
||||||
## You can choose to supply:
|
## You can choose to supply:
|
||||||
## A) a PAT token,
|
## A) a PAT token,
|
||||||
## B) a GitHub App, or
|
## B) a GitHub App, or
|
||||||
## C) a pre-defined Kubernetes secret.
|
## C) a pre-defined secret.
|
||||||
## The syntax for each of these variations is documented below.
|
## The syntax for each of these variations is documented below.
|
||||||
## (Variation A) When using a PAT token, the syntax is as follows:
|
## (Variation A) When using a PAT token, the syntax is as follows:
|
||||||
githubConfigSecret:
|
githubConfigSecret:
|
||||||
|
|
@ -28,8 +28,11 @@ githubConfigSecret:
|
||||||
# .
|
# .
|
||||||
# private key line N
|
# private key line N
|
||||||
#
|
#
|
||||||
## (Variation C) When using a pre-defined Kubernetes secret in the same namespace that the gha-runner-scale-set is going to deploy,
|
## (Variation C) When using a pre-defined secret.
|
||||||
## the syntax is as follows:
|
## The secret can be pulled either directly from Kubernetes, or from the vault, depending on configuration.
|
||||||
|
## Kubernetes secret in the same namespace that the gha-runner-scale-set is going to deploy.
|
||||||
|
## On the other hand, if the vault is configured, secret name will be used to fetch the app configuration.
|
||||||
|
## The syntax is as follows:
|
||||||
# githubConfigSecret: pre-defined-secret
|
# githubConfigSecret: pre-defined-secret
|
||||||
## Notes on using pre-defined Kubernetes secrets:
|
## Notes on using pre-defined Kubernetes secrets:
|
||||||
## You need to make sure your predefined secret has all the required secret data set properly.
|
## You need to make sure your predefined secret has all the required secret data set properly.
|
||||||
|
|
@ -85,6 +88,26 @@ githubConfigSecret:
|
||||||
# key: ca.crt
|
# key: ca.crt
|
||||||
# runnerMountPath: /usr/local/share/ca-certificates/
|
# runnerMountPath: /usr/local/share/ca-certificates/
|
||||||
|
|
||||||
|
# keyVault:
|
||||||
|
# Available values: "azure_key_vault"
|
||||||
|
# type: ""
|
||||||
|
# Configuration related to azure key vault
|
||||||
|
# azure_key_vault:
|
||||||
|
# url: ""
|
||||||
|
# client_id: ""
|
||||||
|
# tenant_id: ""
|
||||||
|
# certificate_path: ""
|
||||||
|
# proxy:
|
||||||
|
# http:
|
||||||
|
# url: http://proxy.com:1234
|
||||||
|
# credentialSecretRef: proxy-auth # a secret with `username` and `password` keys
|
||||||
|
# https:
|
||||||
|
# url: http://proxy.com:1234
|
||||||
|
# credentialSecretRef: proxy-auth # a secret with `username` and `password` keys
|
||||||
|
# noProxy:
|
||||||
|
# - example.com
|
||||||
|
# - example.org
|
||||||
|
|
||||||
## Container mode is an object that provides out-of-box configuration
|
## Container mode is an object that provides out-of-box configuration
|
||||||
## for dind and kubernetes mode. Template will be modified as documented under the
|
## for dind and kubernetes mode. Template will be modified as documented under the
|
||||||
## template object.
|
## template object.
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import (
|
||||||
// App is responsible for initializing required components and running the app.
|
// App is responsible for initializing required components and running the app.
|
||||||
type App struct {
|
type App struct {
|
||||||
// configured fields
|
// configured fields
|
||||||
config config.Config
|
config *config.Config
|
||||||
logger logr.Logger
|
logger logr.Logger
|
||||||
|
|
||||||
// initialized fields
|
// initialized fields
|
||||||
|
|
@ -38,8 +38,12 @@ type Worker interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config config.Config) (*App, error) {
|
func New(config config.Config) (*App, error) {
|
||||||
|
if err := config.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to validate config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
app := &App{
|
app := &App{
|
||||||
config: config,
|
config: &config,
|
||||||
}
|
}
|
||||||
|
|
||||||
ghConfig, err := actions.ParseGitHubConfigFromURL(config.ConfigureUrl)
|
ghConfig, err := actions.ParseGitHubConfigFromURL(config.ConfigureUrl)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -9,20 +10,26 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"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/build"
|
"github.com/actions/actions-runner-controller/build"
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
"github.com/actions/actions-runner-controller/github/actions"
|
||||||
"github.com/actions/actions-runner-controller/logging"
|
"github.com/actions/actions-runner-controller/logging"
|
||||||
|
"github.com/actions/actions-runner-controller/vault"
|
||||||
|
"github.com/actions/actions-runner-controller/vault/azurekeyvault"
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
"golang.org/x/net/http/httpproxy"
|
"golang.org/x/net/http/httpproxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ConfigureUrl string `json:"configure_url"`
|
ConfigureUrl string `json:"configure_url"`
|
||||||
// AppID can be an ID of the app or the client ID
|
VaultType vault.VaultType `json:"vault_type"`
|
||||||
AppID string `json:"app_id"`
|
VaultLookupKey string `json:"vault_lookup_key"`
|
||||||
AppInstallationID int64 `json:"app_installation_id"`
|
// If the VaultType is set to "azure_key_vault", this field must be populated.
|
||||||
AppPrivateKey string `json:"app_private_key"`
|
AzureKeyVaultConfig *azurekeyvault.Config `json:"azure_key_vault,omitempty"`
|
||||||
Token string `json:"token"`
|
// AppConfig contains the GitHub App configuration.
|
||||||
|
// It is initially set to nil if VaultType is set.
|
||||||
|
// Otherwise, it is populated with the GitHub App credentials from the GitHub secret.
|
||||||
|
*appconfig.AppConfig
|
||||||
EphemeralRunnerSetNamespace string `json:"ephemeral_runner_set_namespace"`
|
EphemeralRunnerSetNamespace string `json:"ephemeral_runner_set_namespace"`
|
||||||
EphemeralRunnerSetName string `json:"ephemeral_runner_set_name"`
|
EphemeralRunnerSetName string `json:"ephemeral_runner_set_name"`
|
||||||
MaxRunners int `json:"max_runners"`
|
MaxRunners int `json:"max_runners"`
|
||||||
|
|
@ -37,23 +44,58 @@ type Config struct {
|
||||||
Metrics *v1alpha1.MetricsConfig `json:"metrics"`
|
Metrics *v1alpha1.MetricsConfig `json:"metrics"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func Read(path string) (Config, error) {
|
func Read(ctx context.Context, configPath string) (*Config, error) {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Config{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
var config Config
|
var config Config
|
||||||
if err := json.NewDecoder(f).Decode(&config); err != nil {
|
if err := json.NewDecoder(f).Decode(&config); err != nil {
|
||||||
return Config{}, fmt.Errorf("failed to decode config: %w", err)
|
return nil, fmt.Errorf("failed to decode config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var vault vault.Vault
|
||||||
|
switch config.VaultType {
|
||||||
|
case "":
|
||||||
|
if err := config.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to validate configuration: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config, nil
|
||||||
|
case "azure_key_vault":
|
||||||
|
akv, err := azurekeyvault.New(*config.AzureKeyVaultConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create Azure Key Vault client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vault = akv
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported vault type: %s", config.VaultType)
|
||||||
|
}
|
||||||
|
|
||||||
|
appConfigRaw, err := vault.GetSecret(ctx, config.VaultLookupKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get app config from vault: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
appConfig, err := appconfig.FromJSONString(appConfigRaw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read app config from string: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.AppConfig = appConfig
|
||||||
|
|
||||||
if err := config.Validate(); err != nil {
|
if err := config.Validate(); err != nil {
|
||||||
return Config{}, fmt.Errorf("failed to validate config: %w", err)
|
return nil, fmt.Errorf("config validation failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
if ctx.Err() != nil {
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate checks the configuration for errors.
|
// Validate checks the configuration for errors.
|
||||||
|
|
@ -74,15 +116,19 @@ func (c *Config) Validate() error {
|
||||||
return fmt.Errorf(`MinRunners "%d" cannot be greater than MaxRunners "%d"`, c.MinRunners, c.MaxRunners)
|
return fmt.Errorf(`MinRunners "%d" cannot be greater than MaxRunners "%d"`, c.MinRunners, c.MaxRunners)
|
||||||
}
|
}
|
||||||
|
|
||||||
hasToken := len(c.Token) > 0
|
if c.VaultType != "" {
|
||||||
hasPrivateKeyConfig := len(c.AppID) > 0 && c.AppPrivateKey != ""
|
if err := c.VaultType.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("VaultType validation failed: %w", err)
|
||||||
if !hasToken && !hasPrivateKeyConfig {
|
}
|
||||||
return fmt.Errorf(`GitHub auth credential is missing, token length: "%d", appId: %q, installationId: "%d", private key length: "%d"`, len(c.Token), c.AppID, c.AppInstallationID, len(c.AppPrivateKey))
|
if c.VaultLookupKey == "" {
|
||||||
|
return fmt.Errorf("VaultLookupKey is required when VaultType is set to %q", c.VaultType)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasToken && hasPrivateKeyConfig {
|
if c.VaultType == "" && c.VaultLookupKey == "" {
|
||||||
return fmt.Errorf(`only one GitHub auth method supported at a time. Have both PAT and App auth: token length: "%d", appId: %q, installationId: "%d", private key length: "%d"`, len(c.Token), c.AppID, c.AppInstallationID, len(c.AppPrivateKey))
|
if err := c.AppConfig.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("AppConfig validation failed: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
|
||||||
"github.com/actions/actions-runner-controller/cmd/ghalistener/config"
|
"github.com/actions/actions-runner-controller/cmd/ghalistener/config"
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
"github.com/actions/actions-runner-controller/github/actions"
|
||||||
"github.com/actions/actions-runner-controller/github/actions/testserver"
|
"github.com/actions/actions-runner-controller/github/actions/testserver"
|
||||||
|
|
@ -53,7 +54,9 @@ func TestCustomerServerRootCA(t *testing.T) {
|
||||||
config := config.Config{
|
config := config.Config{
|
||||||
ConfigureUrl: server.ConfigURLForOrg("myorg"),
|
ConfigureUrl: server.ConfigURLForOrg("myorg"),
|
||||||
ServerRootCA: certsString,
|
ServerRootCA: certsString,
|
||||||
Token: "token",
|
AppConfig: &appconfig.AppConfig{
|
||||||
|
Token: "token",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := config.ActionsClient(logr.Discard())
|
client, err := config.ActionsClient(logr.Discard())
|
||||||
|
|
@ -80,7 +83,9 @@ func TestProxySettings(t *testing.T) {
|
||||||
|
|
||||||
config := config.Config{
|
config := config.Config{
|
||||||
ConfigureUrl: "https://github.com/org/repo",
|
ConfigureUrl: "https://github.com/org/repo",
|
||||||
Token: "token",
|
AppConfig: &appconfig.AppConfig{
|
||||||
|
Token: "token",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := config.ActionsClient(logr.Discard())
|
client, err := config.ActionsClient(logr.Discard())
|
||||||
|
|
@ -110,7 +115,9 @@ func TestProxySettings(t *testing.T) {
|
||||||
|
|
||||||
config := config.Config{
|
config := config.Config{
|
||||||
ConfigureUrl: "https://github.com/org/repo",
|
ConfigureUrl: "https://github.com/org/repo",
|
||||||
Token: "token",
|
AppConfig: &appconfig.AppConfig{
|
||||||
|
Token: "token",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := config.ActionsClient(logr.Discard(), actions.WithRetryMax(0))
|
client, err := config.ActionsClient(logr.Discard(), actions.WithRetryMax(0))
|
||||||
|
|
@ -145,7 +152,9 @@ func TestProxySettings(t *testing.T) {
|
||||||
|
|
||||||
config := config.Config{
|
config := config.Config{
|
||||||
ConfigureUrl: "https://github.com/org/repo",
|
ConfigureUrl: "https://github.com/org/repo",
|
||||||
Token: "token",
|
AppConfig: &appconfig.AppConfig{
|
||||||
|
Token: "token",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := config.ActionsClient(logr.Discard())
|
client, err := config.ActionsClient(logr.Discard())
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
|
||||||
|
"github.com/actions/actions-runner-controller/vault"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -15,7 +16,9 @@ func TestConfigValidationMinMax(t *testing.T) {
|
||||||
RunnerScaleSetId: 1,
|
RunnerScaleSetId: 1,
|
||||||
MinRunners: 5,
|
MinRunners: 5,
|
||||||
MaxRunners: 2,
|
MaxRunners: 2,
|
||||||
Token: "token",
|
AppConfig: &appconfig.AppConfig{
|
||||||
|
Token: "token",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
err := config.Validate()
|
err := config.Validate()
|
||||||
assert.ErrorContains(t, err, `MinRunners "5" cannot be greater than MaxRunners "2"`, "Expected error about MinRunners > MaxRunners")
|
assert.ErrorContains(t, err, `MinRunners "5" cannot be greater than MaxRunners "2"`, "Expected error about MinRunners > MaxRunners")
|
||||||
|
|
@ -29,7 +32,7 @@ func TestConfigValidationMissingToken(t *testing.T) {
|
||||||
RunnerScaleSetId: 1,
|
RunnerScaleSetId: 1,
|
||||||
}
|
}
|
||||||
err := config.Validate()
|
err := config.Validate()
|
||||||
expectedError := fmt.Sprintf(`GitHub auth credential is missing, token length: "%d", appId: %q, installationId: "%d", private key length: "%d"`, len(config.Token), config.AppID, config.AppInstallationID, len(config.AppPrivateKey))
|
expectedError := "AppConfig validation failed: missing app config"
|
||||||
assert.ErrorContains(t, err, expectedError, "Expected error about missing auth")
|
assert.ErrorContains(t, err, expectedError, "Expected error about missing auth")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,47 +42,53 @@ func TestConfigValidationAppKey(t *testing.T) {
|
||||||
t.Run("app id integer", func(t *testing.T) {
|
t.Run("app id integer", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
config := &Config{
|
config := &Config{
|
||||||
AppID: "1",
|
AppConfig: &appconfig.AppConfig{
|
||||||
AppInstallationID: 10,
|
AppID: "1",
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
err := config.Validate()
|
err := config.Validate()
|
||||||
expectedError := fmt.Sprintf(`GitHub auth credential is missing, token length: "%d", appId: %q, installationId: "%d", private key length: "%d"`, len(config.Token), config.AppID, config.AppInstallationID, len(config.AppPrivateKey))
|
expectedError := "AppConfig validation failed: no credentials provided: either a PAT or GitHub App credentials should be provided"
|
||||||
assert.ErrorContains(t, err, expectedError, "Expected error about missing auth")
|
assert.ErrorContains(t, err, expectedError, "Expected error about missing auth")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("app id as client id", func(t *testing.T) {
|
t.Run("app id as client id", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
config := &Config{
|
config := &Config{
|
||||||
AppID: "Iv23f8doAlphaNumer1c",
|
AppConfig: &appconfig.AppConfig{
|
||||||
AppInstallationID: 10,
|
AppID: "Iv23f8doAlphaNumer1c",
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
err := config.Validate()
|
err := config.Validate()
|
||||||
expectedError := fmt.Sprintf(`GitHub auth credential is missing, token length: "%d", appId: %q, installationId: "%d", private key length: "%d"`, len(config.Token), config.AppID, config.AppInstallationID, len(config.AppPrivateKey))
|
expectedError := "AppConfig validation failed: no credentials provided: either a PAT or GitHub App credentials should be provided"
|
||||||
assert.ErrorContains(t, err, expectedError, "Expected error about missing auth")
|
assert.ErrorContains(t, err, expectedError, "Expected error about missing auth")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigValidationOnlyOneTypeOfCredentials(t *testing.T) {
|
func TestConfigValidationOnlyOneTypeOfCredentials(t *testing.T) {
|
||||||
config := &Config{
|
config := &Config{
|
||||||
AppID: "1",
|
AppConfig: &appconfig.AppConfig{
|
||||||
AppInstallationID: 10,
|
AppID: "1",
|
||||||
AppPrivateKey: "asdf",
|
AppInstallationID: 10,
|
||||||
Token: "asdf",
|
AppPrivateKey: "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,
|
||||||
}
|
}
|
||||||
err := config.Validate()
|
err := config.Validate()
|
||||||
expectedError := fmt.Sprintf(`only one GitHub auth method supported at a time. Have both PAT and App auth: token length: "%d", appId: %q, installationId: "%d", private key length: "%d"`, len(config.Token), config.AppID, config.AppInstallationID, len(config.AppPrivateKey))
|
expectedError := "AppConfig validation failed: both PAT and GitHub App credentials provided. should only provide one"
|
||||||
assert.ErrorContains(t, err, expectedError, "Expected error about missing auth")
|
assert.ErrorContains(t, err, expectedError, "Expected error about missing auth")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,7 +100,9 @@ func TestConfigValidation(t *testing.T) {
|
||||||
RunnerScaleSetId: 1,
|
RunnerScaleSetId: 1,
|
||||||
MinRunners: 1,
|
MinRunners: 1,
|
||||||
MaxRunners: 5,
|
MaxRunners: 5,
|
||||||
Token: "asdf",
|
AppConfig: &appconfig.AppConfig{
|
||||||
|
Token: "asdf",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := config.Validate()
|
err := config.Validate()
|
||||||
|
|
@ -110,3 +121,50 @@ func TestConfigValidationConfigUrl(t *testing.T) {
|
||||||
|
|
||||||
assert.ErrorContains(t, err, "GitHubConfigUrl is not provided", "Expected error about missing ConfigureUrl")
|
assert.ErrorContains(t, err, "GitHubConfigUrl is not provided", "Expected error about missing ConfigureUrl")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigValidationWithVaultConfig(t *testing.T) {
|
||||||
|
t.Run("valid", func(t *testing.T) {
|
||||||
|
config := &Config{
|
||||||
|
ConfigureUrl: "https://github.com/actions",
|
||||||
|
EphemeralRunnerSetNamespace: "namespace",
|
||||||
|
EphemeralRunnerSetName: "deployment",
|
||||||
|
RunnerScaleSetId: 1,
|
||||||
|
MinRunners: 1,
|
||||||
|
MaxRunners: 5,
|
||||||
|
VaultType: vault.VaultTypeAzureKeyVault,
|
||||||
|
VaultLookupKey: "testkey",
|
||||||
|
}
|
||||||
|
err := config.Validate()
|
||||||
|
assert.NoError(t, err, "Expected no error for valid vault type")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid vault type", func(t *testing.T) {
|
||||||
|
config := &Config{
|
||||||
|
ConfigureUrl: "https://github.com/actions",
|
||||||
|
EphemeralRunnerSetNamespace: "namespace",
|
||||||
|
EphemeralRunnerSetName: "deployment",
|
||||||
|
RunnerScaleSetId: 1,
|
||||||
|
MinRunners: 1,
|
||||||
|
MaxRunners: 5,
|
||||||
|
VaultType: vault.VaultType("invalid_vault_type"),
|
||||||
|
VaultLookupKey: "testkey",
|
||||||
|
}
|
||||||
|
err := config.Validate()
|
||||||
|
assert.ErrorContains(t, err, `unknown vault type: "invalid_vault_type"`, "Expected error for invalid vault type")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("vault type set without lookup key", func(t *testing.T) {
|
||||||
|
config := &Config{
|
||||||
|
ConfigureUrl: "https://github.com/actions",
|
||||||
|
EphemeralRunnerSetNamespace: "namespace",
|
||||||
|
EphemeralRunnerSetName: "deployment",
|
||||||
|
RunnerScaleSetId: 1,
|
||||||
|
MinRunners: 1,
|
||||||
|
MaxRunners: 5,
|
||||||
|
VaultType: vault.VaultTypeAzureKeyVault,
|
||||||
|
VaultLookupKey: "",
|
||||||
|
}
|
||||||
|
err := config.Validate()
|
||||||
|
assert.ErrorContains(t, err, `VaultLookupKey is required when VaultType is set to "azure_key_vault"`, "Expected error for vault type without lookup key")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -13,26 +13,27 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
configPath, ok := os.LookupEnv("LISTENER_CONFIG_PATH")
|
configPath, ok := os.LookupEnv("LISTENER_CONFIG_PATH")
|
||||||
if !ok {
|
if !ok {
|
||||||
fmt.Fprintf(os.Stderr, "Error: LISTENER_CONFIG_PATH environment variable is not set\n")
|
fmt.Fprintf(os.Stderr, "Error: LISTENER_CONFIG_PATH environment variable is not set\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
config, err := config.Read(configPath)
|
|
||||||
|
config, err := config.Read(ctx, configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to read config: %v", err)
|
log.Printf("Failed to read config: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
app, err := app.New(config)
|
app, err := app.New(*config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to initialize app: %v", err)
|
log.Printf("Failed to initialize app: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
defer stop()
|
|
||||||
|
|
||||||
if err := app.Run(ctx); err != nil {
|
if err := app.Run(ctx); err != nil {
|
||||||
log.Printf("Application returned an error: %v", err)
|
log.Printf("Application returned an error: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
||||||
|
|
@ -7863,6 +7863,53 @@ spec:
|
||||||
- containers
|
- containers
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
|
vaultConfig:
|
||||||
|
properties:
|
||||||
|
azureKeyVault:
|
||||||
|
properties:
|
||||||
|
certificatePath:
|
||||||
|
type: string
|
||||||
|
clientId:
|
||||||
|
type: string
|
||||||
|
tenantId:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- certificatePath
|
||||||
|
- clientId
|
||||||
|
- tenantId
|
||||||
|
- url
|
||||||
|
type: object
|
||||||
|
proxy:
|
||||||
|
properties:
|
||||||
|
http:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
https:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
noProxy:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
type:
|
||||||
|
description: |-
|
||||||
|
VaultType represents the type of vault that can be used in the application.
|
||||||
|
It is used to identify which vault integration should be used to resolve secrets.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
description: AutoscalingListenerStatus defines the observed state of AutoscalingListener
|
description: AutoscalingListenerStatus defines the observed state of AutoscalingListener
|
||||||
|
|
|
||||||
|
|
@ -15504,6 +15504,53 @@ spec:
|
||||||
- containers
|
- containers
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
|
vaultConfig:
|
||||||
|
properties:
|
||||||
|
azureKeyVault:
|
||||||
|
properties:
|
||||||
|
certificatePath:
|
||||||
|
type: string
|
||||||
|
clientId:
|
||||||
|
type: string
|
||||||
|
tenantId:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- certificatePath
|
||||||
|
- clientId
|
||||||
|
- tenantId
|
||||||
|
- url
|
||||||
|
type: object
|
||||||
|
proxy:
|
||||||
|
properties:
|
||||||
|
http:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
https:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
noProxy:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
type:
|
||||||
|
description: |-
|
||||||
|
VaultType represents the type of vault that can be used in the application.
|
||||||
|
It is used to identify which vault integration should be used to resolve secrets.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
description: AutoscalingRunnerSetStatus defines the observed state of AutoscalingRunnerSet
|
description: AutoscalingRunnerSetStatus defines the observed state of AutoscalingRunnerSet
|
||||||
|
|
|
||||||
|
|
@ -7784,6 +7784,53 @@ spec:
|
||||||
required:
|
required:
|
||||||
- containers
|
- containers
|
||||||
type: object
|
type: object
|
||||||
|
vaultConfig:
|
||||||
|
properties:
|
||||||
|
azureKeyVault:
|
||||||
|
properties:
|
||||||
|
certificatePath:
|
||||||
|
type: string
|
||||||
|
clientId:
|
||||||
|
type: string
|
||||||
|
tenantId:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- certificatePath
|
||||||
|
- clientId
|
||||||
|
- tenantId
|
||||||
|
- url
|
||||||
|
type: object
|
||||||
|
proxy:
|
||||||
|
properties:
|
||||||
|
http:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
https:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
noProxy:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
type:
|
||||||
|
description: |-
|
||||||
|
VaultType represents the type of vault that can be used in the application.
|
||||||
|
It is used to identify which vault integration should be used to resolve secrets.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
required:
|
required:
|
||||||
- githubConfigSecret
|
- githubConfigSecret
|
||||||
- githubConfigUrl
|
- githubConfigUrl
|
||||||
|
|
|
||||||
|
|
@ -7778,6 +7778,53 @@ spec:
|
||||||
required:
|
required:
|
||||||
- containers
|
- containers
|
||||||
type: object
|
type: object
|
||||||
|
vaultConfig:
|
||||||
|
properties:
|
||||||
|
azureKeyVault:
|
||||||
|
properties:
|
||||||
|
certificatePath:
|
||||||
|
type: string
|
||||||
|
clientId:
|
||||||
|
type: string
|
||||||
|
tenantId:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- certificatePath
|
||||||
|
- clientId
|
||||||
|
- tenantId
|
||||||
|
- url
|
||||||
|
type: object
|
||||||
|
proxy:
|
||||||
|
properties:
|
||||||
|
http:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
https:
|
||||||
|
properties:
|
||||||
|
credentialSecretRef:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Required
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
noProxy:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
type:
|
||||||
|
description: |-
|
||||||
|
VaultType represents the type of vault that can be used in the application.
|
||||||
|
It is used to identify which vault integration should be used to resolve secrets.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
required:
|
required:
|
||||||
- githubConfigSecret
|
- githubConfigSecret
|
||||||
- githubConfigUrl
|
- githubConfigUrl
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import (
|
||||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||||
|
|
||||||
v1alpha1 "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
v1alpha1 "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||||
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
|
||||||
"github.com/actions/actions-runner-controller/controllers/actions.github.com/metrics"
|
"github.com/actions/actions-runner-controller/controllers/actions.github.com/metrics"
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
"github.com/actions/actions-runner-controller/github/actions"
|
||||||
hash "github.com/actions/actions-runner-controller/hash"
|
hash "github.com/actions/actions-runner-controller/hash"
|
||||||
|
|
@ -128,36 +129,19 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl.
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the GitHub config secret exists
|
appConfig, err := r.GetAppConfig(ctx, &autoscalingRunnerSet)
|
||||||
secret := new(corev1.Secret)
|
if err != nil {
|
||||||
if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace, Name: autoscalingListener.Spec.GitHubConfigSecret}, secret); err != nil {
|
log.Error(
|
||||||
log.Error(err, "Failed to find GitHub config secret.",
|
err,
|
||||||
"namespace", autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
"Failed to get app config for AutoscalingRunnerSet.",
|
||||||
"name", autoscalingListener.Spec.GitHubConfigSecret)
|
"namespace",
|
||||||
|
autoscalingRunnerSet.Namespace,
|
||||||
|
"name",
|
||||||
|
autoscalingRunnerSet.GitHubConfigSecret,
|
||||||
|
)
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a mirror secret in the same namespace as the AutoscalingListener
|
|
||||||
mirrorSecret := new(corev1.Secret)
|
|
||||||
if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: autoscalingListener.Name}, mirrorSecret); err != nil {
|
|
||||||
if !kerrors.IsNotFound(err) {
|
|
||||||
log.Error(err, "Unable to get listener secret mirror", "namespace", autoscalingListener.Namespace, "name", autoscalingListener.Name)
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a mirror secret for the listener pod in the Controller namespace for listener pod to use
|
|
||||||
log.Info("Creating a mirror listener secret for the listener pod")
|
|
||||||
return r.createSecretsForListener(ctx, autoscalingListener, secret, log)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the mirror secret is up to date
|
|
||||||
mirrorSecretDataHash := mirrorSecret.Labels["secret-data-hash"]
|
|
||||||
secretDataHash := hash.ComputeTemplateHash(secret.Data)
|
|
||||||
if mirrorSecretDataHash != secretDataHash {
|
|
||||||
log.Info("Updating mirror listener secret for the listener pod", "mirrorSecretDataHash", mirrorSecretDataHash, "secretDataHash", secretDataHash)
|
|
||||||
return r.updateSecretsForListener(ctx, secret, mirrorSecret, log)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the runner scale set listener service account is created for the listener pod in the controller namespace
|
// Make sure the runner scale set listener service account is created for the listener pod in the controller namespace
|
||||||
serviceAccount := new(corev1.ServiceAccount)
|
serviceAccount := new(corev1.ServiceAccount)
|
||||||
if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: autoscalingListener.Name}, serviceAccount); err != nil {
|
if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: autoscalingListener.Name}, serviceAccount); err != nil {
|
||||||
|
|
@ -239,7 +223,7 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl.
|
||||||
|
|
||||||
// Create a listener pod in the controller namespace
|
// Create a listener pod in the controller namespace
|
||||||
log.Info("Creating a listener pod")
|
log.Info("Creating a listener pod")
|
||||||
return r.createListenerPod(ctx, &autoscalingRunnerSet, autoscalingListener, serviceAccount, mirrorSecret, log)
|
return r.createListenerPod(ctx, &autoscalingRunnerSet, autoscalingListener, serviceAccount, appConfig, log)
|
||||||
}
|
}
|
||||||
|
|
||||||
cs := listenerContainerStatus(listenerPod)
|
cs := listenerContainerStatus(listenerPod)
|
||||||
|
|
@ -411,7 +395,7 @@ func (r *AutoscalingListenerReconciler) createServiceAccountForListener(ctx cont
|
||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *AutoscalingListenerReconciler) createListenerPod(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, autoscalingListener *v1alpha1.AutoscalingListener, serviceAccount *corev1.ServiceAccount, secret *corev1.Secret, logger logr.Logger) (ctrl.Result, error) {
|
func (r *AutoscalingListenerReconciler) createListenerPod(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, autoscalingListener *v1alpha1.AutoscalingListener, serviceAccount *corev1.ServiceAccount, appConfig *appconfig.AppConfig, logger logr.Logger) (ctrl.Result, error) {
|
||||||
var envs []corev1.EnvVar
|
var envs []corev1.EnvVar
|
||||||
if autoscalingListener.Spec.Proxy != nil {
|
if autoscalingListener.Spec.Proxy != nil {
|
||||||
httpURL := corev1.EnvVar{
|
httpURL := corev1.EnvVar{
|
||||||
|
|
@ -480,7 +464,7 @@ func (r *AutoscalingListenerReconciler) createListenerPod(ctx context.Context, a
|
||||||
|
|
||||||
logger.Info("Creating listener config secret")
|
logger.Info("Creating listener config secret")
|
||||||
|
|
||||||
podConfig, err := r.newScaleSetListenerConfig(autoscalingListener, secret, metricsConfig, cert)
|
podConfig, err := r.newScaleSetListenerConfig(autoscalingListener, appConfig, metricsConfig, cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err, "Failed to build listener config secret")
|
logger.Error(err, "Failed to build listener config secret")
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
|
|
@ -499,7 +483,7 @@ func (r *AutoscalingListenerReconciler) createListenerPod(ctx context.Context, a
|
||||||
return ctrl.Result{Requeue: true}, nil
|
return ctrl.Result{Requeue: true}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
newPod, err := r.newScaleSetListenerPod(autoscalingListener, &podConfig, serviceAccount, secret, metricsConfig, envs...)
|
newPod, err := r.newScaleSetListenerPod(autoscalingListener, &podConfig, serviceAccount, metricsConfig, envs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err, "Failed to build listener pod")
|
logger.Error(err, "Failed to build listener pod")
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
|
|
@ -558,23 +542,6 @@ func (r *AutoscalingListenerReconciler) certificate(ctx context.Context, autosca
|
||||||
return certificate, nil
|
return certificate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *AutoscalingListenerReconciler) createSecretsForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, secret *corev1.Secret, logger logr.Logger) (ctrl.Result, error) {
|
|
||||||
newListenerSecret := r.newScaleSetListenerSecretMirror(autoscalingListener, secret)
|
|
||||||
|
|
||||||
if err := ctrl.SetControllerReference(autoscalingListener, newListenerSecret, r.Scheme); err != nil {
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Creating listener secret", "namespace", newListenerSecret.Namespace, "name", newListenerSecret.Name)
|
|
||||||
if err := r.Create(ctx, newListenerSecret); err != nil {
|
|
||||||
logger.Error(err, "Unable to create listener secret", "namespace", newListenerSecret.Namespace, "name", newListenerSecret.Name)
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Created listener secret", "namespace", newListenerSecret.Namespace, "name", newListenerSecret.Name)
|
|
||||||
return ctrl.Result{Requeue: true}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AutoscalingListenerReconciler) createProxySecret(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, logger logr.Logger) (ctrl.Result, error) {
|
func (r *AutoscalingListenerReconciler) createProxySecret(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, logger logr.Logger) (ctrl.Result, error) {
|
||||||
data, err := autoscalingListener.Spec.Proxy.ToSecretData(func(s string) (*corev1.Secret, error) {
|
data, err := autoscalingListener.Spec.Proxy.ToSecretData(func(s string) (*corev1.Secret, error) {
|
||||||
var secret corev1.Secret
|
var secret corev1.Secret
|
||||||
|
|
@ -614,22 +581,6 @@ func (r *AutoscalingListenerReconciler) createProxySecret(ctx context.Context, a
|
||||||
return ctrl.Result{Requeue: true}, nil
|
return ctrl.Result{Requeue: true}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *AutoscalingListenerReconciler) updateSecretsForListener(ctx context.Context, secret *corev1.Secret, mirrorSecret *corev1.Secret, logger logr.Logger) (ctrl.Result, error) {
|
|
||||||
dataHash := hash.ComputeTemplateHash(secret.Data)
|
|
||||||
updatedMirrorSecret := mirrorSecret.DeepCopy()
|
|
||||||
updatedMirrorSecret.Labels["secret-data-hash"] = dataHash
|
|
||||||
updatedMirrorSecret.Data = secret.Data
|
|
||||||
|
|
||||||
logger.Info("Updating listener mirror secret", "namespace", updatedMirrorSecret.Namespace, "name", updatedMirrorSecret.Name, "hash", dataHash)
|
|
||||||
if err := r.Update(ctx, updatedMirrorSecret); err != nil {
|
|
||||||
logger.Error(err, "Unable to update listener mirror secret", "namespace", updatedMirrorSecret.Namespace, "name", updatedMirrorSecret.Name)
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Updated listener mirror secret", "namespace", updatedMirrorSecret.Namespace, "name", updatedMirrorSecret.Name, "hash", dataHash)
|
|
||||||
return ctrl.Result{Requeue: true}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AutoscalingListenerReconciler) createRoleForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, logger logr.Logger) (ctrl.Result, error) {
|
func (r *AutoscalingListenerReconciler) createRoleForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, logger logr.Logger) (ctrl.Result, error) {
|
||||||
newRole := r.newScaleSetListenerRole(autoscalingListener)
|
newRole := r.newScaleSetListenerRole(autoscalingListener)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ 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"
|
||||||
|
|
||||||
listenerconfig "github.com/actions/actions-runner-controller/cmd/ghalistener/config"
|
ghalistenerconfig "github.com/actions/actions-runner-controller/cmd/ghalistener/config"
|
||||||
|
"github.com/actions/actions-runner-controller/github/actions/fake"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/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,10 +44,17 @@ 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())
|
||||||
|
|
||||||
|
rb := ResourceBuilder{
|
||||||
|
SecretResolver: secretResolver,
|
||||||
|
}
|
||||||
|
|
||||||
controller := &AutoscalingListenerReconciler{
|
controller := &AutoscalingListenerReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
|
ResourceBuilder: rb,
|
||||||
}
|
}
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
@ -452,10 +460,17 @@ 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())
|
||||||
|
|
||||||
|
rb := ResourceBuilder{
|
||||||
|
SecretResolver: secretResolver,
|
||||||
|
}
|
||||||
|
|
||||||
controller := &AutoscalingListenerReconciler{
|
controller := &AutoscalingListenerReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
|
ResourceBuilder: rb,
|
||||||
}
|
}
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
@ -725,11 +740,17 @@ 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())
|
||||||
|
|
||||||
|
rb := ResourceBuilder{
|
||||||
|
SecretResolver: secretResolver,
|
||||||
|
}
|
||||||
|
|
||||||
controller := &AutoscalingListenerReconciler{
|
controller := &AutoscalingListenerReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
|
ResourceBuilder: rb,
|
||||||
}
|
}
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
@ -922,10 +943,17 @@ var _ = Describe("Test AutoScalingListener controller with template modification
|
||||||
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
|
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
|
||||||
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
||||||
|
|
||||||
|
secretResolver := NewSecretResolver(mgr.GetClient(), fake.NewMultiClient())
|
||||||
|
|
||||||
|
rb := ResourceBuilder{
|
||||||
|
SecretResolver: secretResolver,
|
||||||
|
}
|
||||||
|
|
||||||
controller := &AutoscalingListenerReconciler{
|
controller := &AutoscalingListenerReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
|
ResourceBuilder: rb,
|
||||||
}
|
}
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
@ -1018,6 +1046,12 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
|
||||||
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
|
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
|
||||||
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
||||||
|
|
||||||
|
secretResolver := NewSecretResolver(mgr.GetClient(), fake.NewMultiClient())
|
||||||
|
|
||||||
|
rb := ResourceBuilder{
|
||||||
|
SecretResolver: secretResolver,
|
||||||
|
}
|
||||||
|
|
||||||
cert, err := os.ReadFile(filepath.Join(
|
cert, err := os.ReadFile(filepath.Join(
|
||||||
"../../",
|
"../../",
|
||||||
"github",
|
"github",
|
||||||
|
|
@ -1039,9 +1073,10 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to create configmap with root CAs")
|
Expect(err).NotTo(HaveOccurred(), "failed to create configmap with root CAs")
|
||||||
|
|
||||||
controller := &AutoscalingListenerReconciler{
|
controller := &AutoscalingListenerReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
|
ResourceBuilder: rb,
|
||||||
}
|
}
|
||||||
err = controller.SetupWithManager(mgr)
|
err = controller.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
@ -1056,7 +1091,7 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
|
||||||
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
||||||
GitHubConfigUrl: "https://github.com/owner/repo",
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
|
GitHubServerTLS: &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
|
@ -1092,7 +1127,7 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
|
||||||
Spec: v1alpha1.AutoscalingListenerSpec{
|
Spec: v1alpha1.AutoscalingListenerSpec{
|
||||||
GitHubConfigUrl: "https://github.com/owner/repo",
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
|
GitHubServerTLS: &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
|
@ -1136,7 +1171,7 @@ var _ = Describe("Test GitHub Server TLS configuration", func() {
|
||||||
|
|
||||||
g.Expect(config.Data["config.json"]).ToNot(BeEmpty(), "listener configuration file should not be empty")
|
g.Expect(config.Data["config.json"]).ToNot(BeEmpty(), "listener configuration file should not be empty")
|
||||||
|
|
||||||
var listenerConfig listenerconfig.Config
|
var listenerConfig ghalistenerconfig.Config
|
||||||
err = json.Unmarshal(config.Data["config.json"], &listenerConfig)
|
err = json.Unmarshal(config.Data["config.json"], &listenerConfig)
|
||||||
g.Expect(err).NotTo(HaveOccurred(), "failed to parse listener configuration file")
|
g.Expect(err).NotTo(HaveOccurred(), "failed to parse listener configuration file")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -207,14 +207,6 @@ func (r *AutoscalingRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl
|
||||||
return r.updateRunnerScaleSetName(ctx, autoscalingRunnerSet, log)
|
return r.updateRunnerScaleSetName(ctx, autoscalingRunnerSet, log)
|
||||||
}
|
}
|
||||||
|
|
||||||
secret := new(corev1.Secret)
|
|
||||||
if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingRunnerSet.Namespace, Name: autoscalingRunnerSet.Spec.GitHubConfigSecret}, secret); err != nil {
|
|
||||||
log.Error(err, "Failed to find GitHub config secret.",
|
|
||||||
"namespace", autoscalingRunnerSet.Namespace,
|
|
||||||
"name", autoscalingRunnerSet.Spec.GitHubConfigSecret)
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
existingRunnerSets, err := r.listEphemeralRunnerSets(ctx, autoscalingRunnerSet)
|
existingRunnerSets, err := r.listEphemeralRunnerSets(ctx, autoscalingRunnerSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err, "Failed to list existing ephemeral runner sets")
|
log.Error(err, "Failed to list existing ephemeral runner sets")
|
||||||
|
|
@ -402,12 +394,12 @@ func (r *AutoscalingRunnerSetReconciler) removeFinalizersFromDependentResources(
|
||||||
|
|
||||||
func (r *AutoscalingRunnerSetReconciler) createRunnerScaleSet(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, logger logr.Logger) (ctrl.Result, error) {
|
func (r *AutoscalingRunnerSetReconciler) createRunnerScaleSet(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, logger logr.Logger) (ctrl.Result, error) {
|
||||||
logger.Info("Creating a new runner scale set")
|
logger.Info("Creating a new runner scale set")
|
||||||
actionsClient, err := r.actionsClientFor(ctx, autoscalingRunnerSet)
|
actionsClient, err := r.GetActionsService(ctx, autoscalingRunnerSet)
|
||||||
if len(autoscalingRunnerSet.Spec.RunnerScaleSetName) == 0 {
|
if len(autoscalingRunnerSet.Spec.RunnerScaleSetName) == 0 {
|
||||||
autoscalingRunnerSet.Spec.RunnerScaleSetName = autoscalingRunnerSet.Name
|
autoscalingRunnerSet.Spec.RunnerScaleSetName = autoscalingRunnerSet.Name
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err, "Failed to initialize Actions service client for creating a new runner scale set")
|
logger.Error(err, "Failed to initialize Actions service client for creating a new runner scale set", "error", err.Error())
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -498,7 +490,7 @@ func (r *AutoscalingRunnerSetReconciler) updateRunnerScaleSetRunnerGroup(ctx con
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actionsClient, err := r.actionsClientFor(ctx, autoscalingRunnerSet)
|
actionsClient, err := r.GetActionsService(ctx, autoscalingRunnerSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err, "Failed to initialize Actions service client for updating a existing runner scale set")
|
logger.Error(err, "Failed to initialize Actions service client for updating a existing runner scale set")
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
|
|
@ -546,7 +538,7 @@ func (r *AutoscalingRunnerSetReconciler) updateRunnerScaleSetName(ctx context.Co
|
||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
actionsClient, err := r.actionsClientFor(ctx, autoscalingRunnerSet)
|
actionsClient, err := r.GetActionsService(ctx, autoscalingRunnerSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err, "Failed to initialize Actions service client for updating a existing runner scale set")
|
logger.Error(err, "Failed to initialize Actions service client for updating a existing runner scale set")
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
|
|
@ -597,7 +589,7 @@ func (r *AutoscalingRunnerSetReconciler) deleteRunnerScaleSet(ctx context.Contex
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
actionsClient, err := r.actionsClientFor(ctx, autoscalingRunnerSet)
|
actionsClient, err := r.GetActionsService(ctx, autoscalingRunnerSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err, "Failed to initialize Actions service client for updating a existing runner scale set")
|
logger.Error(err, "Failed to initialize Actions service client for updating a existing runner scale set")
|
||||||
return err
|
return err
|
||||||
|
|
@ -676,74 +668,6 @@ func (r *AutoscalingRunnerSetReconciler) listEphemeralRunnerSets(ctx context.Con
|
||||||
return &EphemeralRunnerSets{list: list}, nil
|
return &EphemeralRunnerSets{list: list}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *AutoscalingRunnerSetReconciler) actionsClientFor(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet) (actions.ActionsService, error) {
|
|
||||||
var configSecret corev1.Secret
|
|
||||||
if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingRunnerSet.Namespace, Name: autoscalingRunnerSet.Spec.GitHubConfigSecret}, &configSecret); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to find GitHub config secret: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts, err := r.actionsClientOptionsFor(ctx, autoscalingRunnerSet)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get actions client options: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.ActionsClient.GetClientFromSecret(
|
|
||||||
ctx,
|
|
||||||
autoscalingRunnerSet.Spec.GitHubConfigUrl,
|
|
||||||
autoscalingRunnerSet.Namespace,
|
|
||||||
configSecret.Data,
|
|
||||||
opts...,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *AutoscalingRunnerSetReconciler) actionsClientOptionsFor(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet) ([]actions.ClientOption, error) {
|
|
||||||
var options []actions.ClientOption
|
|
||||||
|
|
||||||
if autoscalingRunnerSet.Spec.Proxy != nil {
|
|
||||||
proxyFunc, err := autoscalingRunnerSet.Spec.Proxy.ProxyFunc(func(s string) (*corev1.Secret, error) {
|
|
||||||
var secret corev1.Secret
|
|
||||||
err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingRunnerSet.Namespace, Name: s}, &secret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get proxy secret %s: %w", s, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &secret, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get proxy func: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
options = append(options, actions.WithProxy(proxyFunc))
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig := autoscalingRunnerSet.Spec.GitHubServerTLS
|
|
||||||
if tlsConfig != nil {
|
|
||||||
pool, err := tlsConfig.ToCertPool(func(name, key string) ([]byte, error) {
|
|
||||||
var configmap corev1.ConfigMap
|
|
||||||
err := r.Get(
|
|
||||||
ctx,
|
|
||||||
types.NamespacedName{
|
|
||||||
Namespace: autoscalingRunnerSet.Namespace,
|
|
||||||
Name: name,
|
|
||||||
},
|
|
||||||
&configmap,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get configmap %s: %w", name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte(configmap.Data[key]), nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get tls config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
options = append(options, actions.WithRootCAs(pool))
|
|
||||||
}
|
|
||||||
|
|
||||||
return options, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetupWithManager sets up the controller with the Manager.
|
// SetupWithManager sets up the controller with the Manager.
|
||||||
func (r *AutoscalingRunnerSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *AutoscalingRunnerSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
return ctrl.NewControllerManagedBy(mgr).
|
return ctrl.NewControllerManagedBy(mgr).
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,12 @@ var _ = Describe("Test AutoScalingRunnerSet controller", Ordered, func() {
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
ControllerNamespace: autoscalingNS.Name,
|
ControllerNamespace: autoscalingNS.Name,
|
||||||
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
||||||
ActionsClient: fake.NewMultiClient(),
|
ResourceBuilder: ResourceBuilder{
|
||||||
|
SecretResolver: &SecretResolver{
|
||||||
|
k8sClient: k8sClient,
|
||||||
|
multiClient: fake.NewMultiClient(),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
@ -677,33 +682,40 @@ 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(
|
||||||
|
fake.WithDefaultClient(
|
||||||
|
fake.NewFakeClient(
|
||||||
|
fake.WithUpdateRunnerScaleSet(
|
||||||
|
&actions.RunnerScaleSet{
|
||||||
|
Id: 1,
|
||||||
|
Name: "testset_update",
|
||||||
|
RunnerGroupId: 1,
|
||||||
|
RunnerGroupName: "testgroup",
|
||||||
|
Labels: []actions.Label{{Type: "test", Name: "test"}},
|
||||||
|
RunnerSetting: actions.RunnerSetting{},
|
||||||
|
CreatedOn: time.Now(),
|
||||||
|
RunnerJitConfigUrl: "test.test.test",
|
||||||
|
Statistics: nil,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
nil,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
controller := &AutoscalingRunnerSetReconciler{
|
controller := &AutoscalingRunnerSetReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
ControllerNamespace: autoscalingNS.Name,
|
ControllerNamespace: autoscalingNS.Name,
|
||||||
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
||||||
ActionsClient: fake.NewMultiClient(
|
ResourceBuilder: ResourceBuilder{
|
||||||
fake.WithDefaultClient(
|
SecretResolver: &SecretResolver{
|
||||||
fake.NewFakeClient(
|
k8sClient: k8sClient,
|
||||||
fake.WithUpdateRunnerScaleSet(
|
multiClient: multiClient,
|
||||||
&actions.RunnerScaleSet{
|
},
|
||||||
Id: 1,
|
},
|
||||||
Name: "testset_update",
|
|
||||||
RunnerGroupId: 1,
|
|
||||||
RunnerGroupName: "testgroup",
|
|
||||||
Labels: []actions.Label{{Type: "test", Name: "test"}},
|
|
||||||
RunnerSetting: actions.RunnerSetting{},
|
|
||||||
CreatedOn: time.Now(),
|
|
||||||
RunnerJitConfigUrl: "test.test.test",
|
|
||||||
Statistics: nil,
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
nil,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
@ -818,7 +830,12 @@ var _ = Describe("Test AutoscalingController creation failures", Ordered, func()
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
ControllerNamespace: autoscalingNS.Name,
|
ControllerNamespace: autoscalingNS.Name,
|
||||||
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
||||||
ActionsClient: fake.NewMultiClient(),
|
ResourceBuilder: ResourceBuilder{
|
||||||
|
SecretResolver: &SecretResolver{
|
||||||
|
k8sClient: k8sClient,
|
||||||
|
multiClient: fake.NewMultiClient(),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
@ -937,14 +954,19 @@ 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(),
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
ControllerNamespace: autoscalingNS.Name,
|
ControllerNamespace: autoscalingNS.Name,
|
||||||
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
||||||
ActionsClient: actions.NewMultiClient(logr.Discard()),
|
ResourceBuilder: ResourceBuilder{
|
||||||
|
SecretResolver: &SecretResolver{
|
||||||
|
k8sClient: k8sClient,
|
||||||
|
multiClient: multiClient,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
|
|
@ -1127,7 +1149,12 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
ControllerNamespace: autoscalingNS.Name,
|
ControllerNamespace: autoscalingNS.Name,
|
||||||
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
||||||
ActionsClient: fake.NewMultiClient(),
|
ResourceBuilder: ResourceBuilder{
|
||||||
|
SecretResolver: &SecretResolver{
|
||||||
|
k8sClient: k8sClient,
|
||||||
|
multiClient: fake.NewMultiClient(),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
err = controller.SetupWithManager(mgr)
|
err = controller.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
@ -1136,7 +1163,10 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should be able to make requests to a server using root CAs", func() {
|
It("should be able to make requests to a server using root CAs", func() {
|
||||||
controller.ActionsClient = actions.NewMultiClient(logr.Discard())
|
controller.SecretResolver = &SecretResolver{
|
||||||
|
k8sClient: k8sClient,
|
||||||
|
multiClient: actions.NewMultiClient(logr.Discard()),
|
||||||
|
}
|
||||||
|
|
||||||
certsFolder := filepath.Join(
|
certsFolder := filepath.Join(
|
||||||
"../../",
|
"../../",
|
||||||
|
|
@ -1171,7 +1201,7 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
|
||||||
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
||||||
GitHubConfigUrl: server.ConfigURLForOrg("my-org"),
|
GitHubConfigUrl: server.ConfigURLForOrg("my-org"),
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
|
GitHubServerTLS: &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
|
@ -1224,7 +1254,7 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
|
||||||
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
||||||
GitHubConfigUrl: "https://github.com/owner/repo",
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
|
GitHubServerTLS: &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
|
@ -1288,7 +1318,7 @@ var _ = Describe("Test client optional configuration", Ordered, func() {
|
||||||
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
||||||
GitHubConfigUrl: "https://github.com/owner/repo",
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
|
GitHubServerTLS: &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
|
@ -1361,7 +1391,12 @@ var _ = Describe("Test external permissions cleanup", Ordered, func() {
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
ControllerNamespace: autoscalingNS.Name,
|
ControllerNamespace: autoscalingNS.Name,
|
||||||
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
||||||
ActionsClient: fake.NewMultiClient(),
|
ResourceBuilder: ResourceBuilder{
|
||||||
|
SecretResolver: &SecretResolver{
|
||||||
|
k8sClient: k8sClient,
|
||||||
|
multiClient: fake.NewMultiClient(),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
@ -1519,7 +1554,12 @@ var _ = Describe("Test external permissions cleanup", Ordered, func() {
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
ControllerNamespace: autoscalingNS.Name,
|
ControllerNamespace: autoscalingNS.Name,
|
||||||
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
||||||
ActionsClient: fake.NewMultiClient(),
|
ResourceBuilder: ResourceBuilder{
|
||||||
|
SecretResolver: &SecretResolver{
|
||||||
|
k8sClient: k8sClient,
|
||||||
|
multiClient: fake.NewMultiClient(),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
@ -1727,7 +1767,12 @@ var _ = Describe("Test resource version and build version mismatch", func() {
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
ControllerNamespace: autoscalingNS.Name,
|
ControllerNamespace: autoscalingNS.Name,
|
||||||
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
||||||
ActionsClient: fake.NewMultiClient(),
|
ResourceBuilder: ResourceBuilder{
|
||||||
|
SecretResolver: &SecretResolver{
|
||||||
|
k8sClient: k8sClient,
|
||||||
|
multiClient: fake.NewMultiClient(),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,8 @@ const (
|
||||||
// EphemeralRunnerReconciler reconciles a EphemeralRunner object
|
// EphemeralRunnerReconciler reconciles a EphemeralRunner object
|
||||||
type EphemeralRunnerReconciler struct {
|
type EphemeralRunnerReconciler struct {
|
||||||
client.Client
|
client.Client
|
||||||
Log logr.Logger
|
Log logr.Logger
|
||||||
Scheme *runtime.Scheme
|
Scheme *runtime.Scheme
|
||||||
ActionsClient actions.MultiClient
|
|
||||||
ResourceBuilder
|
ResourceBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -529,7 +528,7 @@ func (r *EphemeralRunnerReconciler) deletePodAsFailed(ctx context.Context, ephem
|
||||||
func (r *EphemeralRunnerReconciler) updateStatusWithRunnerConfig(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, log logr.Logger) (*ctrl.Result, error) {
|
func (r *EphemeralRunnerReconciler) updateStatusWithRunnerConfig(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, log logr.Logger) (*ctrl.Result, error) {
|
||||||
// Runner is not registered with the service. We need to register it first
|
// 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.actionsClientFor(ctx, ephemeralRunner)
|
actionsClient, err := r.GetActionsService(ctx, ephemeralRunner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ctrl.Result{}, fmt.Errorf("failed to get actions client for generating JIT config: %w", err)
|
return &ctrl.Result{}, fmt.Errorf("failed to get actions client for generating JIT config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -753,77 +752,10 @@ func (r *EphemeralRunnerReconciler) updateRunStatusFromPod(ctx context.Context,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *EphemeralRunnerReconciler) actionsClientFor(ctx context.Context, runner *v1alpha1.EphemeralRunner) (actions.ActionsService, error) {
|
|
||||||
secret := new(corev1.Secret)
|
|
||||||
if err := r.Get(ctx, types.NamespacedName{Namespace: runner.Namespace, Name: runner.Spec.GitHubConfigSecret}, secret); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get secret: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts, err := r.actionsClientOptionsFor(ctx, runner)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get actions client options: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.ActionsClient.GetClientFromSecret(
|
|
||||||
ctx,
|
|
||||||
runner.Spec.GitHubConfigUrl,
|
|
||||||
runner.Namespace,
|
|
||||||
secret.Data,
|
|
||||||
opts...,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EphemeralRunnerReconciler) actionsClientOptionsFor(ctx context.Context, runner *v1alpha1.EphemeralRunner) ([]actions.ClientOption, error) {
|
|
||||||
var opts []actions.ClientOption
|
|
||||||
if runner.Spec.Proxy != nil {
|
|
||||||
proxyFunc, err := runner.Spec.Proxy.ProxyFunc(func(s string) (*corev1.Secret, error) {
|
|
||||||
var secret corev1.Secret
|
|
||||||
err := r.Get(ctx, types.NamespacedName{Namespace: runner.Namespace, Name: s}, &secret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get proxy secret %s: %w", s, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &secret, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get proxy func: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts = append(opts, actions.WithProxy(proxyFunc))
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig := runner.Spec.GitHubServerTLS
|
|
||||||
if tlsConfig != nil {
|
|
||||||
pool, err := tlsConfig.ToCertPool(func(name, key string) ([]byte, error) {
|
|
||||||
var configmap corev1.ConfigMap
|
|
||||||
err := r.Get(
|
|
||||||
ctx,
|
|
||||||
types.NamespacedName{
|
|
||||||
Namespace: runner.Namespace,
|
|
||||||
Name: name,
|
|
||||||
},
|
|
||||||
&configmap,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get configmap %s: %w", name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte(configmap.Data[key]), nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get tls config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts = append(opts, actions.WithRootCAs(pool))
|
|
||||||
}
|
|
||||||
|
|
||||||
return opts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// runnerRegisteredWithService checks if the runner is still registered with the service
|
// runnerRegisteredWithService checks if the runner is still registered with the service
|
||||||
// Returns found=false and err=nil if ephemeral runner does not exist in GitHub service and should be deleted
|
// Returns found=false and err=nil if ephemeral runner does not exist in GitHub service and should be deleted
|
||||||
func (r EphemeralRunnerReconciler) runnerRegisteredWithService(ctx context.Context, runner *v1alpha1.EphemeralRunner, log logr.Logger) (found bool, err error) {
|
func (r EphemeralRunnerReconciler) runnerRegisteredWithService(ctx context.Context, runner *v1alpha1.EphemeralRunner, log logr.Logger) (found bool, err error) {
|
||||||
actionsClient, err := r.actionsClientFor(ctx, runner)
|
actionsClient, err := r.GetActionsService(ctx, runner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("failed to get Actions client for ScaleSet: %w", err)
|
return false, fmt.Errorf("failed to get Actions client for ScaleSet: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -850,7 +782,7 @@ func (r EphemeralRunnerReconciler) runnerRegisteredWithService(ctx context.Conte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *EphemeralRunnerReconciler) deleteRunnerFromService(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, log logr.Logger) error {
|
func (r *EphemeralRunnerReconciler) deleteRunnerFromService(ctx context.Context, ephemeralRunner *v1alpha1.EphemeralRunner, log logr.Logger) error {
|
||||||
client, err := r.actionsClientFor(ctx, ephemeralRunner)
|
client, err := r.GetActionsService(ctx, ephemeralRunner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get actions client for runner: %w", err)
|
return fmt.Errorf("failed to get actions client for runner: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,10 +107,15 @@ var _ = Describe("EphemeralRunner", func() {
|
||||||
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
||||||
|
|
||||||
controller = &EphemeralRunnerReconciler{
|
controller = &EphemeralRunnerReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
ActionsClient: fake.NewMultiClient(),
|
ResourceBuilder: ResourceBuilder{
|
||||||
|
SecretResolver: &SecretResolver{
|
||||||
|
k8sClient: mgr.GetClient(),
|
||||||
|
multiClient: fake.NewMultiClient(),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
|
|
@ -789,22 +794,27 @@ var _ = Describe("EphemeralRunner", func() {
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
ActionsClient: fake.NewMultiClient(
|
ResourceBuilder: ResourceBuilder{
|
||||||
fake.WithDefaultClient(
|
SecretResolver: &SecretResolver{
|
||||||
fake.NewFakeClient(
|
k8sClient: mgr.GetClient(),
|
||||||
fake.WithGetRunner(
|
multiClient: fake.NewMultiClient(
|
||||||
|
fake.WithDefaultClient(
|
||||||
|
fake.NewFakeClient(
|
||||||
|
fake.WithGetRunner(
|
||||||
|
nil,
|
||||||
|
&actions.ActionsError{
|
||||||
|
StatusCode: http.StatusNotFound,
|
||||||
|
Err: &actions.ActionsExceptionError{
|
||||||
|
ExceptionName: "AgentNotFoundException",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
nil,
|
nil,
|
||||||
&actions.ActionsError{
|
|
||||||
StatusCode: http.StatusNotFound,
|
|
||||||
Err: &actions.ActionsExceptionError{
|
|
||||||
ExceptionName: "AgentNotFoundException",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
nil,
|
},
|
||||||
),
|
},
|
||||||
),
|
|
||||||
}
|
}
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
Expect(err).To(BeNil(), "failed to setup controller")
|
Expect(err).To(BeNil(), "failed to setup controller")
|
||||||
|
|
@ -861,10 +871,15 @@ var _ = Describe("EphemeralRunner", func() {
|
||||||
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoScalingNS.Name)
|
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoScalingNS.Name)
|
||||||
|
|
||||||
controller = &EphemeralRunnerReconciler{
|
controller = &EphemeralRunnerReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
ActionsClient: fake.NewMultiClient(),
|
ResourceBuilder: ResourceBuilder{
|
||||||
|
SecretResolver: &SecretResolver{
|
||||||
|
k8sClient: mgr.GetClient(),
|
||||||
|
multiClient: fake.NewMultiClient(),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
Expect(err).To(BeNil(), "failed to setup controller")
|
Expect(err).To(BeNil(), "failed to setup controller")
|
||||||
|
|
@ -874,7 +889,12 @@ var _ = Describe("EphemeralRunner", func() {
|
||||||
|
|
||||||
It("uses an actions client with proxy transport", func() {
|
It("uses an actions client with proxy transport", func() {
|
||||||
// Use an actual client
|
// Use an actual client
|
||||||
controller.ActionsClient = actions.NewMultiClient(logr.Discard())
|
controller.ResourceBuilder = ResourceBuilder{
|
||||||
|
SecretResolver: &SecretResolver{
|
||||||
|
k8sClient: mgr.GetClient(),
|
||||||
|
multiClient: actions.NewMultiClient(logr.Discard()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
proxySuccessfulllyCalled := false
|
proxySuccessfulllyCalled := false
|
||||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -1025,10 +1045,15 @@ var _ = Describe("EphemeralRunner", func() {
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to create configmap with root CAs")
|
Expect(err).NotTo(HaveOccurred(), "failed to create configmap with root CAs")
|
||||||
|
|
||||||
controller = &EphemeralRunnerReconciler{
|
controller = &EphemeralRunnerReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
ActionsClient: fake.NewMultiClient(),
|
ResourceBuilder: ResourceBuilder{
|
||||||
|
SecretResolver: &SecretResolver{
|
||||||
|
k8sClient: mgr.GetClient(),
|
||||||
|
multiClient: fake.NewMultiClient(),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.SetupWithManager(mgr)
|
err = controller.SetupWithManager(mgr)
|
||||||
|
|
@ -1059,11 +1084,16 @@ var _ = Describe("EphemeralRunner", func() {
|
||||||
server.StartTLS()
|
server.StartTLS()
|
||||||
|
|
||||||
// Use an actual client
|
// Use an actual client
|
||||||
controller.ActionsClient = actions.NewMultiClient(logr.Discard())
|
controller.ResourceBuilder = ResourceBuilder{
|
||||||
|
SecretResolver: &SecretResolver{
|
||||||
|
k8sClient: mgr.GetClient(),
|
||||||
|
multiClient: actions.NewMultiClient(logr.Discard()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
ephemeralRunner := newExampleRunner("test-runner", autoScalingNS.Name, configSecret.Name)
|
ephemeralRunner := newExampleRunner("test-runner", autoScalingNS.Name, configSecret.Name)
|
||||||
ephemeralRunner.Spec.GitHubConfigUrl = server.ConfigURLForOrg("my-org")
|
ephemeralRunner.Spec.GitHubConfigUrl = server.ConfigURLForOrg("my-org")
|
||||||
ephemeralRunner.Spec.GitHubServerTLS = &v1alpha1.GitHubServerTLSConfig{
|
ephemeralRunner.Spec.GitHubServerTLS = &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
|
|
||||||
|
|
@ -331,7 +331,7 @@ func (r *EphemeralRunnerSetReconciler) cleanUpEphemeralRunners(ctx context.Conte
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
actionsClient, err := r.actionsClientFor(ctx, ephemeralRunnerSet)
|
actionsClient, err := r.GetActionsService(ctx, ephemeralRunnerSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
@ -439,7 +439,7 @@ func (r *EphemeralRunnerSetReconciler) deleteIdleEphemeralRunners(ctx context.Co
|
||||||
log.Info("No pending or running ephemeral runners running at this time for scale down")
|
log.Info("No pending or running ephemeral runners running at this time for scale down")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
actionsClient, err := r.actionsClientFor(ctx, ephemeralRunnerSet)
|
actionsClient, err := r.GetActionsService(ctx, ephemeralRunnerSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create actions client for ephemeral runner replica set: %w", err)
|
return fmt.Errorf("failed to create actions client for ephemeral runner replica set: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -502,73 +502,6 @@ func (r *EphemeralRunnerSetReconciler) deleteEphemeralRunnerWithActionsClient(ct
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *EphemeralRunnerSetReconciler) actionsClientFor(ctx context.Context, rs *v1alpha1.EphemeralRunnerSet) (actions.ActionsService, error) {
|
|
||||||
secret := new(corev1.Secret)
|
|
||||||
if err := r.Get(ctx, types.NamespacedName{Namespace: rs.Namespace, Name: rs.Spec.EphemeralRunnerSpec.GitHubConfigSecret}, secret); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get secret: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts, err := r.actionsClientOptionsFor(ctx, rs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get actions client options: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.ActionsClient.GetClientFromSecret(
|
|
||||||
ctx,
|
|
||||||
rs.Spec.EphemeralRunnerSpec.GitHubConfigUrl,
|
|
||||||
rs.Namespace,
|
|
||||||
secret.Data,
|
|
||||||
opts...,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EphemeralRunnerSetReconciler) actionsClientOptionsFor(ctx context.Context, rs *v1alpha1.EphemeralRunnerSet) ([]actions.ClientOption, error) {
|
|
||||||
var opts []actions.ClientOption
|
|
||||||
if rs.Spec.EphemeralRunnerSpec.Proxy != nil {
|
|
||||||
proxyFunc, err := rs.Spec.EphemeralRunnerSpec.Proxy.ProxyFunc(func(s string) (*corev1.Secret, error) {
|
|
||||||
var secret corev1.Secret
|
|
||||||
err := r.Get(ctx, types.NamespacedName{Namespace: rs.Namespace, Name: s}, &secret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get secret %s: %w", s, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &secret, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get proxy func: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts = append(opts, actions.WithProxy(proxyFunc))
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig := rs.Spec.EphemeralRunnerSpec.GitHubServerTLS
|
|
||||||
if tlsConfig != nil {
|
|
||||||
pool, err := tlsConfig.ToCertPool(func(name, key string) ([]byte, error) {
|
|
||||||
var configmap corev1.ConfigMap
|
|
||||||
err := r.Get(
|
|
||||||
ctx,
|
|
||||||
types.NamespacedName{
|
|
||||||
Namespace: rs.Namespace,
|
|
||||||
Name: name,
|
|
||||||
},
|
|
||||||
&configmap,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get configmap %s: %w", name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte(configmap.Data[key]), nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get tls config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts = append(opts, actions.WithRootCAs(pool))
|
|
||||||
}
|
|
||||||
|
|
||||||
return opts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetupWithManager sets up the controller with the Manager.
|
// SetupWithManager sets up the controller with the Manager.
|
||||||
func (r *EphemeralRunnerSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *EphemeralRunnerSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
return ctrl.NewControllerManagedBy(mgr).
|
return ctrl.NewControllerManagedBy(mgr).
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,15 @@ var _ = Describe("Test EphemeralRunnerSet controller", func() {
|
||||||
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
||||||
|
|
||||||
controller := &EphemeralRunnerSetReconciler{
|
controller := &EphemeralRunnerSetReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
ActionsClient: fake.NewMultiClient(),
|
ResourceBuilder: ResourceBuilder{
|
||||||
|
SecretResolver: &SecretResolver{
|
||||||
|
k8sClient: mgr.GetClient(),
|
||||||
|
multiClient: fake.NewMultiClient(),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
@ -1104,10 +1109,15 @@ var _ = Describe("Test EphemeralRunnerSet controller with proxy settings", func(
|
||||||
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
||||||
|
|
||||||
controller := &EphemeralRunnerSetReconciler{
|
controller := &EphemeralRunnerSetReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
ActionsClient: actions.NewMultiClient(logr.Discard()),
|
ResourceBuilder: ResourceBuilder{
|
||||||
|
SecretResolver: &SecretResolver{
|
||||||
|
k8sClient: mgr.GetClient(),
|
||||||
|
multiClient: actions.NewMultiClient(logr.Discard()),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
err := controller.SetupWithManager(mgr)
|
err := controller.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
@ -1403,10 +1413,15 @@ var _ = Describe("Test EphemeralRunnerSet controller with custom root CA", func(
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to create configmap with root CAs")
|
Expect(err).NotTo(HaveOccurred(), "failed to create configmap with root CAs")
|
||||||
|
|
||||||
controller := &EphemeralRunnerSetReconciler{
|
controller := &EphemeralRunnerSetReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
ActionsClient: actions.NewMultiClient(logr.Discard()),
|
ResourceBuilder: ResourceBuilder{
|
||||||
|
SecretResolver: &SecretResolver{
|
||||||
|
k8sClient: mgr.GetClient(),
|
||||||
|
multiClient: actions.NewMultiClient(logr.Discard()),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
err = controller.SetupWithManager(mgr)
|
err = controller.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
@ -1445,7 +1460,7 @@ var _ = Describe("Test EphemeralRunnerSet controller with custom root CA", func(
|
||||||
EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{
|
EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{
|
||||||
GitHubConfigUrl: server.ConfigURLForOrg("my-org"),
|
GitHubConfigUrl: server.ConfigURLForOrg("my-org"),
|
||||||
GitHubConfigSecret: configSecret.Name,
|
GitHubConfigSecret: configSecret.Name,
|
||||||
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
|
GitHubServerTLS: &v1alpha1.TLSConfig{
|
||||||
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
LocalObjectReference: corev1.LocalObjectReference{
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,13 @@ import (
|
||||||
"strings"
|
"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/build"
|
"github.com/actions/actions-runner-controller/build"
|
||||||
listenerconfig "github.com/actions/actions-runner-controller/cmd/ghalistener/config"
|
ghalistenerconfig "github.com/actions/actions-runner-controller/cmd/ghalistener/config"
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
"github.com/actions/actions-runner-controller/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"
|
||||||
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"
|
||||||
|
|
@ -72,6 +74,7 @@ func SetListenerEntrypoint(entrypoint string) {
|
||||||
|
|
||||||
type ResourceBuilder struct {
|
type ResourceBuilder struct {
|
||||||
ExcludeLabelPropagationPrefixes []string
|
ExcludeLabelPropagationPrefixes []string
|
||||||
|
*SecretResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
// boolPtr returns a pointer to a bool value
|
// boolPtr returns a pointer to a bool value
|
||||||
|
|
@ -121,6 +124,7 @@ func (b *ResourceBuilder) newAutoScalingListener(autoscalingRunnerSet *v1alpha1.
|
||||||
Spec: v1alpha1.AutoscalingListenerSpec{
|
Spec: v1alpha1.AutoscalingListenerSpec{
|
||||||
GitHubConfigUrl: autoscalingRunnerSet.Spec.GitHubConfigUrl,
|
GitHubConfigUrl: autoscalingRunnerSet.Spec.GitHubConfigUrl,
|
||||||
GitHubConfigSecret: autoscalingRunnerSet.Spec.GitHubConfigSecret,
|
GitHubConfigSecret: autoscalingRunnerSet.Spec.GitHubConfigSecret,
|
||||||
|
VaultConfig: autoscalingRunnerSet.VaultConfig(),
|
||||||
RunnerScaleSetId: runnerScaleSetId,
|
RunnerScaleSetId: runnerScaleSetId,
|
||||||
AutoscalingRunnerSetNamespace: autoscalingRunnerSet.Namespace,
|
AutoscalingRunnerSetNamespace: autoscalingRunnerSet.Namespace,
|
||||||
AutoscalingRunnerSetName: autoscalingRunnerSet.Name,
|
AutoscalingRunnerSetName: autoscalingRunnerSet.Name,
|
||||||
|
|
@ -160,7 +164,7 @@ func (lm *listenerMetricsServerConfig) containerPort() (corev1.ContainerPort, er
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha1.AutoscalingListener, secret *corev1.Secret, metricsConfig *listenerMetricsServerConfig, cert string) (*corev1.Secret, error) {
|
func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha1.AutoscalingListener, appConfig *appconfig.AppConfig, metricsConfig *listenerMetricsServerConfig, cert string) (*corev1.Secret, error) {
|
||||||
var (
|
var (
|
||||||
metricsAddr = ""
|
metricsAddr = ""
|
||||||
metricsEndpoint = ""
|
metricsEndpoint = ""
|
||||||
|
|
@ -170,21 +174,8 @@ func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha
|
||||||
metricsEndpoint = metricsConfig.endpoint
|
metricsEndpoint = metricsConfig.endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
var appInstallationID int64
|
config := ghalistenerconfig.Config{
|
||||||
if id, ok := secret.Data["github_app_installation_id"]; ok {
|
|
||||||
var err error
|
|
||||||
appInstallationID, err = strconv.ParseInt(string(id), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to convert github_app_installation_id to int: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config := listenerconfig.Config{
|
|
||||||
ConfigureUrl: autoscalingListener.Spec.GitHubConfigUrl,
|
ConfigureUrl: autoscalingListener.Spec.GitHubConfigUrl,
|
||||||
AppID: string(secret.Data["github_app_id"]),
|
|
||||||
AppInstallationID: appInstallationID,
|
|
||||||
AppPrivateKey: string(secret.Data["github_app_private_key"]),
|
|
||||||
Token: string(secret.Data["github_token"]),
|
|
||||||
EphemeralRunnerSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
EphemeralRunnerSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
||||||
EphemeralRunnerSetName: autoscalingListener.Spec.EphemeralRunnerSetName,
|
EphemeralRunnerSetName: autoscalingListener.Spec.EphemeralRunnerSetName,
|
||||||
MaxRunners: autoscalingListener.Spec.MaxRunners,
|
MaxRunners: autoscalingListener.Spec.MaxRunners,
|
||||||
|
|
@ -199,6 +190,20 @@ func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha
|
||||||
Metrics: autoscalingListener.Spec.Metrics,
|
Metrics: autoscalingListener.Spec.Metrics,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vault := autoscalingListener.Spec.VaultConfig
|
||||||
|
if vault == nil {
|
||||||
|
config.AppConfig = appConfig
|
||||||
|
} else {
|
||||||
|
config.VaultType = vault.Type
|
||||||
|
config.VaultLookupKey = autoscalingListener.Spec.GitHubConfigSecret
|
||||||
|
config.AzureKeyVaultConfig = &azurekeyvault.Config{
|
||||||
|
TenantID: vault.AzureKeyVault.TenantID,
|
||||||
|
ClientID: vault.AzureKeyVault.ClientID,
|
||||||
|
URL: vault.AzureKeyVault.URL,
|
||||||
|
CertificatePath: vault.AzureKeyVault.CertificatePath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := config.Validate(); err != nil {
|
if err := config.Validate(); err != nil {
|
||||||
return nil, fmt.Errorf("invalid listener config: %w", err)
|
return nil, fmt.Errorf("invalid listener config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -219,7 +224,7 @@ func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *ResourceBuilder) newScaleSetListenerPod(autoscalingListener *v1alpha1.AutoscalingListener, podConfig *corev1.Secret, serviceAccount *corev1.ServiceAccount, secret *corev1.Secret, metricsConfig *listenerMetricsServerConfig, envs ...corev1.EnvVar) (*corev1.Pod, error) {
|
func (b *ResourceBuilder) newScaleSetListenerPod(autoscalingListener *v1alpha1.AutoscalingListener, podConfig *corev1.Secret, serviceAccount *corev1.ServiceAccount, metricsConfig *listenerMetricsServerConfig, envs ...corev1.EnvVar) (*corev1.Pod, error) {
|
||||||
listenerEnv := []corev1.EnvVar{
|
listenerEnv := []corev1.EnvVar{
|
||||||
{
|
{
|
||||||
Name: "LISTENER_CONFIG_PATH",
|
Name: "LISTENER_CONFIG_PATH",
|
||||||
|
|
@ -490,25 +495,6 @@ func (b *ResourceBuilder) newScaleSetListenerRoleBinding(autoscalingListener *v1
|
||||||
return newRoleBinding
|
return newRoleBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *ResourceBuilder) newScaleSetListenerSecretMirror(autoscalingListener *v1alpha1.AutoscalingListener, secret *corev1.Secret) *corev1.Secret {
|
|
||||||
dataHash := hash.ComputeTemplateHash(&secret.Data)
|
|
||||||
|
|
||||||
newListenerSecret := &corev1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: autoscalingListener.Name,
|
|
||||||
Namespace: autoscalingListener.Namespace,
|
|
||||||
Labels: b.mergeLabels(autoscalingListener.Labels, map[string]string{
|
|
||||||
LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
|
|
||||||
LabelKeyGitHubScaleSetName: autoscalingListener.Spec.AutoscalingRunnerSetName,
|
|
||||||
"secret-data-hash": dataHash,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
Data: secret.DeepCopy().Data,
|
|
||||||
}
|
|
||||||
|
|
||||||
return newListenerSecret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *ResourceBuilder) newEphemeralRunnerSet(autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet) (*v1alpha1.EphemeralRunnerSet, error) {
|
func (b *ResourceBuilder) newEphemeralRunnerSet(autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet) (*v1alpha1.EphemeralRunnerSet, error) {
|
||||||
runnerScaleSetId, err := strconv.Atoi(autoscalingRunnerSet.Annotations[runnerScaleSetIdAnnotationKey])
|
runnerScaleSetId, err := strconv.Atoi(autoscalingRunnerSet.Annotations[runnerScaleSetIdAnnotationKey])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -561,6 +547,7 @@ func (b *ResourceBuilder) newEphemeralRunnerSet(autoscalingRunnerSet *v1alpha1.A
|
||||||
Proxy: autoscalingRunnerSet.Spec.Proxy,
|
Proxy: autoscalingRunnerSet.Spec.Proxy,
|
||||||
GitHubServerTLS: autoscalingRunnerSet.Spec.GitHubServerTLS,
|
GitHubServerTLS: autoscalingRunnerSet.Spec.GitHubServerTLS,
|
||||||
PodTemplateSpec: autoscalingRunnerSet.Spec.Template,
|
PodTemplateSpec: autoscalingRunnerSet.Spec.Template,
|
||||||
|
VaultConfig: autoscalingRunnerSet.VaultConfig(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -582,6 +569,7 @@ func (b *ResourceBuilder) newEphemeralRunner(ephemeralRunnerSet *v1alpha1.Epheme
|
||||||
for key, val := range ephemeralRunnerSet.Annotations {
|
for key, val := range ephemeralRunnerSet.Annotations {
|
||||||
annotations[key] = val
|
annotations[key] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
annotations[AnnotationKeyPatchID] = strconv.Itoa(ephemeralRunnerSet.Spec.PatchID)
|
annotations[AnnotationKeyPatchID] = strconv.Itoa(ephemeralRunnerSet.Spec.PatchID)
|
||||||
return &v1alpha1.EphemeralRunner{
|
return &v1alpha1.EphemeralRunner{
|
||||||
TypeMeta: metav1.TypeMeta{},
|
TypeMeta: metav1.TypeMeta{},
|
||||||
|
|
|
||||||
|
|
@ -82,12 +82,7 @@ func TestLabelPropagation(t *testing.T) {
|
||||||
Name: "test",
|
Name: "test",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
listenerSecret := &corev1.Secret{
|
listenerPod, err := b.newScaleSetListenerPod(listener, &corev1.Secret{}, listenerServiceAccount, nil)
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "test",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
listenerPod, err := b.newScaleSetListenerPod(listener, &corev1.Secret{}, listenerServiceAccount, listenerSecret, nil)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, listenerPod.Labels, listener.Labels)
|
assert.Equal(t, listenerPod.Labels, listener.Labels)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,280 @@
|
||||||
|
package actionsgithubcom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||||
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
|
||||||
|
"github.com/actions/actions-runner-controller/github/actions"
|
||||||
|
"github.com/actions/actions-runner-controller/vault"
|
||||||
|
"github.com/actions/actions-runner-controller/vault/azurekeyvault"
|
||||||
|
"golang.org/x/net/http/httpproxy"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SecretResolver struct {
|
||||||
|
k8sClient client.Client
|
||||||
|
multiClient actions.MultiClient
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecretResolverOption func(*SecretResolver)
|
||||||
|
|
||||||
|
func NewSecretResolver(k8sClient client.Client, multiClient actions.MultiClient, opts ...SecretResolverOption) *SecretResolver {
|
||||||
|
if k8sClient == nil {
|
||||||
|
panic("k8sClient must not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
secretResolver := &SecretResolver{
|
||||||
|
k8sClient: k8sClient,
|
||||||
|
multiClient: multiClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(secretResolver)
|
||||||
|
}
|
||||||
|
|
||||||
|
return secretResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionsGitHubObject interface {
|
||||||
|
client.Object
|
||||||
|
GitHubConfigUrl() string
|
||||||
|
GitHubConfigSecret() string
|
||||||
|
GitHubProxy() *v1alpha1.ProxyConfig
|
||||||
|
GitHubServerTLS() *v1alpha1.TLSConfig
|
||||||
|
VaultConfig() *v1alpha1.VaultConfig
|
||||||
|
VaultProxy() *v1alpha1.ProxyConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr *SecretResolver) GetAppConfig(ctx context.Context, obj ActionsGitHubObject) (*appconfig.AppConfig, error) {
|
||||||
|
resolver, err := sr.resolverForObject(ctx, obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get resolver for object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
appConfig, err := resolver.appConfig(ctx, obj.GitHubConfigSecret())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve app config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return appConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr *SecretResolver) GetActionsService(ctx context.Context, obj ActionsGitHubObject) (actions.ActionsService, error) {
|
||||||
|
resolver, err := sr.resolverForObject(ctx, obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get resolver for object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
appConfig, err := resolver.appConfig(ctx, obj.GitHubConfigSecret())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve app config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var clientOptions []actions.ClientOption
|
||||||
|
if proxy := obj.GitHubProxy(); proxy != nil {
|
||||||
|
config := &httpproxy.Config{
|
||||||
|
NoProxy: strings.Join(proxy.NoProxy, ","),
|
||||||
|
}
|
||||||
|
|
||||||
|
if proxy.HTTP != nil {
|
||||||
|
u, err := url.Parse(proxy.HTTP.Url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse proxy http url %q: %w", proxy.HTTP.Url, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ref := proxy.HTTP.CredentialSecretRef; ref != "" {
|
||||||
|
u.User, err = resolver.proxyCredentials(ctx, ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve proxy credentials: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.HTTPProxy = u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if proxy.HTTPS != nil {
|
||||||
|
u, err := url.Parse(proxy.HTTPS.Url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse proxy https url %q: %w", proxy.HTTPS.Url, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ref := proxy.HTTPS.CredentialSecretRef; ref != "" {
|
||||||
|
u.User, err = resolver.proxyCredentials(ctx, ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve proxy credentials: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.HTTPSProxy = u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyFunc := func(req *http.Request) (*url.URL, error) {
|
||||||
|
return config.ProxyFunc()(req.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientOptions = append(clientOptions, actions.WithProxy(proxyFunc))
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig := obj.GitHubServerTLS()
|
||||||
|
if tlsConfig != nil {
|
||||||
|
pool, err := tlsConfig.ToCertPool(func(name, key string) ([]byte, error) {
|
||||||
|
var configmap corev1.ConfigMap
|
||||||
|
err := sr.k8sClient.Get(
|
||||||
|
ctx,
|
||||||
|
types.NamespacedName{
|
||||||
|
Namespace: obj.GetNamespace(),
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
&configmap,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get configmap %s: %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(configmap.Data[key]), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get tls config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientOptions = append(clientOptions, actions.WithRootCAs(pool))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sr.multiClient.GetClientFor(
|
||||||
|
ctx,
|
||||||
|
obj.GitHubConfigUrl(),
|
||||||
|
appConfig,
|
||||||
|
obj.GetNamespace(),
|
||||||
|
clientOptions...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr *SecretResolver) resolverForObject(ctx context.Context, obj ActionsGitHubObject) (resolver, error) {
|
||||||
|
vaultConfig := obj.VaultConfig()
|
||||||
|
if vaultConfig == nil || vaultConfig.Type == "" {
|
||||||
|
return &k8sResolver{
|
||||||
|
namespace: obj.GetNamespace(),
|
||||||
|
client: sr.k8sClient,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var proxy *httpproxy.Config
|
||||||
|
if vaultProxy := obj.VaultProxy(); vaultProxy != nil {
|
||||||
|
p, err := vaultProxy.ToHTTPProxyConfig(func(s string) (*corev1.Secret, error) {
|
||||||
|
var secret corev1.Secret
|
||||||
|
err := sr.k8sClient.Get(ctx, types.NamespacedName{Name: s, Namespace: obj.GetNamespace()}, &secret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get secret %s: %w", s, err)
|
||||||
|
}
|
||||||
|
return &secret, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create proxy config: %v", err)
|
||||||
|
}
|
||||||
|
proxy = p
|
||||||
|
}
|
||||||
|
|
||||||
|
switch vaultConfig.Type {
|
||||||
|
case vault.VaultTypeAzureKeyVault:
|
||||||
|
akv, err := azurekeyvault.New(azurekeyvault.Config{
|
||||||
|
TenantID: vaultConfig.AzureKeyVault.TenantID,
|
||||||
|
ClientID: vaultConfig.AzureKeyVault.ClientID,
|
||||||
|
URL: vaultConfig.AzureKeyVault.URL,
|
||||||
|
CertificatePath: vaultConfig.AzureKeyVault.CertificatePath,
|
||||||
|
Proxy: proxy,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create Azure Key Vault client: %v", err)
|
||||||
|
}
|
||||||
|
return &vaultResolver{
|
||||||
|
vault: akv,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown vault type %q", vaultConfig.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type resolver interface {
|
||||||
|
appConfig(ctx context.Context, key string) (*appconfig.AppConfig, error)
|
||||||
|
proxyCredentials(ctx context.Context, key string) (*url.Userinfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type k8sResolver struct {
|
||||||
|
namespace string
|
||||||
|
client client.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *k8sResolver) appConfig(ctx context.Context, key string) (*appconfig.AppConfig, error) {
|
||||||
|
nsName := types.NamespacedName{
|
||||||
|
Namespace: r.namespace,
|
||||||
|
Name: key,
|
||||||
|
}
|
||||||
|
secret := new(corev1.Secret)
|
||||||
|
if err := r.client.Get(
|
||||||
|
ctx,
|
||||||
|
nsName,
|
||||||
|
secret,
|
||||||
|
); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get kubernetes secret: %q", nsName.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return appconfig.FromSecret(secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *k8sResolver) proxyCredentials(ctx context.Context, key string) (*url.Userinfo, error) {
|
||||||
|
nsName := types.NamespacedName{Namespace: r.namespace, Name: key}
|
||||||
|
secret := new(corev1.Secret)
|
||||||
|
if err := r.client.Get(
|
||||||
|
ctx,
|
||||||
|
nsName,
|
||||||
|
secret,
|
||||||
|
); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get kubernetes secret: %q", nsName.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.UserPassword(
|
||||||
|
string(secret.Data["username"]),
|
||||||
|
string(secret.Data["password"]),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type vaultResolver struct {
|
||||||
|
vault vault.Vault
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *vaultResolver) appConfig(ctx context.Context, key string) (*appconfig.AppConfig, error) {
|
||||||
|
val, err := r.vault.GetSecret(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve secret: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return appconfig.FromJSONString(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *vaultResolver) proxyCredentials(ctx context.Context, key string) (*url.Userinfo, error) {
|
||||||
|
val, err := r.vault.GetSecret(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve secret: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type info struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var i info
|
||||||
|
if err := json.Unmarshal([]byte(val), &i); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal info: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.UserPassword(i.Username, i.Password), nil
|
||||||
|
}
|
||||||
|
|
@ -130,7 +130,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByQueuedAndInProgr
|
||||||
jobs, resp, err := ghc.Actions.ListWorkflowJobs(context.TODO(), user, repoName, runID, &opt)
|
jobs, resp, err := ghc.Actions.ListWorkflowJobs(context.TODO(), user, repoName, runID, &opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Log.Error(err, "Error listing workflow jobs")
|
r.Log.Error(err, "Error listing workflow jobs")
|
||||||
return //err
|
return // err
|
||||||
}
|
}
|
||||||
allJobs = append(allJobs, jobs.Jobs...)
|
allJobs = append(allJobs, jobs.Jobs...)
|
||||||
if resp.NextPage == 0 {
|
if resp.NextPage == 0 {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package fake
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"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/github/actions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -34,10 +35,6 @@ func NewMultiClient(opts ...MultiClientOption) actions.MultiClient {
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeMultiClient) GetClientFor(ctx context.Context, githubConfigURL string, creds actions.ActionsAuth, namespace string, options ...actions.ClientOption) (actions.ActionsService, error) {
|
func (f *fakeMultiClient) GetClientFor(ctx context.Context, githubConfigURL string, appConfig *appconfig.AppConfig, namespace string, options ...actions.ClientOption) (actions.ActionsService, error) {
|
||||||
return f.defaultClient, f.defaultErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakeMultiClient) GetClientFromSecret(ctx context.Context, githubConfigURL, namespace string, secretData actions.KubernetesSecretData, options ...actions.ClientOption) (actions.ActionsService, error) {
|
|
||||||
return f.defaultClient, f.defaultErr
|
return f.defaultClient, f.defaultErr
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,14 @@ package actions
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MultiClient interface {
|
type MultiClient interface {
|
||||||
GetClientFor(ctx context.Context, githubConfigURL string, creds ActionsAuth, namespace string, options ...ClientOption) (ActionsService, error)
|
GetClientFor(ctx context.Context, githubConfigURL string, appConfig *appconfig.AppConfig, namespace string, options ...ClientOption) (ActionsService, error)
|
||||||
GetClientFromSecret(ctx context.Context, githubConfigURL, namespace string, secretData KubernetesSecretData, options ...ClientOption) (ActionsService, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type multiClient struct {
|
type multiClient struct {
|
||||||
|
|
@ -50,15 +49,22 @@ func NewMultiClient(logger logr.Logger) MultiClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *multiClient) GetClientFor(ctx context.Context, githubConfigURL string, creds ActionsAuth, namespace string, options ...ClientOption) (ActionsService, error) {
|
func (m *multiClient) GetClientFor(ctx context.Context, githubConfigURL string, appConfig *appconfig.AppConfig, namespace string, options ...ClientOption) (ActionsService, error) {
|
||||||
m.logger.Info("retrieve actions client", "githubConfigURL", githubConfigURL, "namespace", namespace)
|
m.logger.Info("retrieve actions client", "githubConfigURL", githubConfigURL, "namespace", namespace)
|
||||||
|
|
||||||
if creds.Token == "" && creds.AppCreds == nil {
|
if err := appConfig.Validate(); err != nil {
|
||||||
return nil, fmt.Errorf("no credentials provided. either a PAT or GitHub App credentials should be provided")
|
return nil, fmt.Errorf("failed to validate app config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if creds.Token != "" && creds.AppCreds != nil {
|
var creds ActionsAuth
|
||||||
return nil, fmt.Errorf("both PAT and GitHub App credentials provided. should only provide one")
|
if len(appConfig.Token) > 0 {
|
||||||
|
creds.Token = appConfig.Token
|
||||||
|
} else {
|
||||||
|
creds.AppCreds = &GitHubAppAuth{
|
||||||
|
AppID: appConfig.AppID,
|
||||||
|
AppInstallationID: appConfig.AppInstallationID,
|
||||||
|
AppPrivateKey: appConfig.AppPrivateKey,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := NewClient(
|
client, err := NewClient(
|
||||||
|
|
@ -69,7 +75,7 @@ func (m *multiClient) GetClientFor(ctx context.Context, githubConfigURL string,
|
||||||
}, options...)...,
|
}, options...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to instantiate new client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
|
|
@ -94,42 +100,3 @@ func (m *multiClient) GetClientFor(ctx context.Context, githubConfigURL string,
|
||||||
|
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type KubernetesSecretData map[string][]byte
|
|
||||||
|
|
||||||
func (m *multiClient) GetClientFromSecret(ctx context.Context, githubConfigURL, namespace string, secretData KubernetesSecretData, options ...ClientOption) (ActionsService, error) {
|
|
||||||
if len(secretData) == 0 {
|
|
||||||
return nil, fmt.Errorf("must provide secret data with either PAT or GitHub App Auth")
|
|
||||||
}
|
|
||||||
|
|
||||||
token := string(secretData["github_token"])
|
|
||||||
hasToken := len(token) > 0
|
|
||||||
|
|
||||||
appID := string(secretData["github_app_id"])
|
|
||||||
appInstallationID := string(secretData["github_app_installation_id"])
|
|
||||||
appPrivateKey := string(secretData["github_app_private_key"])
|
|
||||||
hasGitHubAppAuth := len(appID) > 0 && len(appInstallationID) > 0 && len(appPrivateKey) > 0
|
|
||||||
|
|
||||||
if hasToken && hasGitHubAppAuth {
|
|
||||||
return nil, fmt.Errorf("must provide secret with only PAT or GitHub App Auth to avoid ambiguity in client behavior")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasToken && !hasGitHubAppAuth {
|
|
||||||
return nil, fmt.Errorf("neither PAT nor GitHub App Auth credentials provided in secret")
|
|
||||||
}
|
|
||||||
|
|
||||||
auth := ActionsAuth{}
|
|
||||||
|
|
||||||
if hasToken {
|
|
||||||
auth.Token = token
|
|
||||||
return m.GetClientFor(ctx, githubConfigURL, auth, namespace, options...)
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedAppInstallationID, err := strconv.ParseInt(appInstallationID, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
auth.AppCreds = &GitHubAppAuth{AppID: appID, AppInstallationID: parsedAppInstallationID, AppPrivateKey: appPrivateKey}
|
|
||||||
return m.GetClientFor(ctx, githubConfigURL, auth, namespace, options...)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
@ -23,10 +24,13 @@ func TestMultiClientCaching(t *testing.T) {
|
||||||
|
|
||||||
defaultNamespace := "default"
|
defaultNamespace := "default"
|
||||||
defaultConfigURL := "https://github.com/org/repo"
|
defaultConfigURL := "https://github.com/org/repo"
|
||||||
defaultCreds := &ActionsAuth{
|
defaultCreds := &appconfig.AppConfig{
|
||||||
Token: "token",
|
Token: "token",
|
||||||
}
|
}
|
||||||
client, err := NewClient(defaultConfigURL, defaultCreds)
|
defaultAuth := ActionsAuth{
|
||||||
|
Token: defaultCreds.Token,
|
||||||
|
}
|
||||||
|
client, err := NewClient(defaultConfigURL, &defaultAuth)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
multiClient.clients[ActionsClientKey{client.Identifier(), defaultNamespace}] = client
|
multiClient.clients[ActionsClientKey{client.Identifier(), defaultNamespace}] = client
|
||||||
|
|
@ -35,7 +39,7 @@ func TestMultiClientCaching(t *testing.T) {
|
||||||
cachedClient, err := multiClient.GetClientFor(
|
cachedClient, err := multiClient.GetClientFor(
|
||||||
ctx,
|
ctx,
|
||||||
defaultConfigURL,
|
defaultConfigURL,
|
||||||
*defaultCreds,
|
defaultCreds,
|
||||||
defaultNamespace,
|
defaultNamespace,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -47,7 +51,7 @@ func TestMultiClientCaching(t *testing.T) {
|
||||||
newClient, err := multiClient.GetClientFor(
|
newClient, err := multiClient.GetClientFor(
|
||||||
ctx,
|
ctx,
|
||||||
defaultConfigURL,
|
defaultConfigURL,
|
||||||
*defaultCreds,
|
defaultCreds,
|
||||||
otherNamespace,
|
otherNamespace,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -63,7 +67,7 @@ func TestMultiClientOptions(t *testing.T) {
|
||||||
defaultConfigURL := "https://github.com/org/repo"
|
defaultConfigURL := "https://github.com/org/repo"
|
||||||
|
|
||||||
t.Run("GetClientFor", func(t *testing.T) {
|
t.Run("GetClientFor", func(t *testing.T) {
|
||||||
defaultCreds := &ActionsAuth{
|
defaultCreds := &appconfig.AppConfig{
|
||||||
Token: "token",
|
Token: "token",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,7 +75,7 @@ func TestMultiClientOptions(t *testing.T) {
|
||||||
service, err := multiClient.GetClientFor(
|
service, err := multiClient.GetClientFor(
|
||||||
ctx,
|
ctx,
|
||||||
defaultConfigURL,
|
defaultConfigURL,
|
||||||
*defaultCreds,
|
defaultCreds,
|
||||||
defaultNamespace,
|
defaultNamespace,
|
||||||
)
|
)
|
||||||
service.SetUserAgent(testUserAgent)
|
service.SetUserAgent(testUserAgent)
|
||||||
|
|
@ -83,27 +87,6 @@ func TestMultiClientOptions(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, testUserAgent.String(), req.Header.Get("User-Agent"))
|
assert.Equal(t, testUserAgent.String(), req.Header.Get("User-Agent"))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("GetClientFromSecret", func(t *testing.T) {
|
|
||||||
secret := map[string][]byte{
|
|
||||||
"github_token": []byte("token"),
|
|
||||||
}
|
|
||||||
|
|
||||||
multiClient := NewMultiClient(logger)
|
|
||||||
service, err := multiClient.GetClientFromSecret(
|
|
||||||
ctx,
|
|
||||||
defaultConfigURL,
|
|
||||||
defaultNamespace,
|
|
||||||
secret,
|
|
||||||
)
|
|
||||||
service.SetUserAgent(testUserAgent)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
client := service.(*Client)
|
|
||||||
req, err := client.NewGitHubAPIRequest(ctx, "GET", "/test", nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, testUserAgent.String(), req.Header.Get("User-Agent"))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateJWT(t *testing.T) {
|
func TestCreateJWT(t *testing.T) {
|
||||||
|
|
|
||||||
9
go.mod
9
go.mod
|
|
@ -3,6 +3,9 @@ module github.com/actions/actions-runner-controller
|
||||||
go 1.24.3
|
go 1.24.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0
|
||||||
github.com/bradleyfalzon/ghinstallation/v2 v2.14.0
|
github.com/bradleyfalzon/ghinstallation/v2 v2.14.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
|
||||||
|
|
@ -39,6 +42,9 @@ require (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 // indirect
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect
|
||||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
|
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
|
||||||
|
|
@ -95,6 +101,7 @@ require (
|
||||||
github.com/go-sql-driver/mysql v1.9.0 // indirect
|
github.com/go-sql-driver/mysql v1.9.0 // indirect
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/gonvenience/bunt v1.4.0 // indirect
|
github.com/gonvenience/bunt v1.4.0 // indirect
|
||||||
github.com/gonvenience/idem v0.0.1 // indirect
|
github.com/gonvenience/idem v0.0.1 // indirect
|
||||||
|
|
@ -121,6 +128,7 @@ require (
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mailru/easyjson v0.9.0 // indirect
|
github.com/mailru/easyjson v0.9.0 // indirect
|
||||||
github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 // indirect
|
github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 // indirect
|
||||||
|
|
@ -134,6 +142,7 @@ require (
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/pquerna/otp v1.4.0 // indirect
|
github.com/pquerna/otp v1.4.0 // indirect
|
||||||
|
|
|
||||||
28
go.sum
28
go.sum
|
|
@ -1,5 +1,22 @@
|
||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/Azure/azure-sdk-for-go v51.0.0+incompatible h1:p7blnyJSjJqf5jflHbSGhIhEpXIgIFmYZNg5uwqweso=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 h1:1mvYtZfWQAnwNah/C+Z+Jb9rQH95LPE2vlmMuWAHJk8=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1/go.mod h1:75I/mXtme1JyWFtz8GocPHVFyH421IBoZErnO16dd0k=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1 h1:Bk5uOhSAenHyR5P61D/NzeQCv+4fEVV8mOkJ82NqpWw=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1/go.mod h1:QZ4pw3or1WPmRBxf0cHd1tknzrT54WPBOQoGutCPvSU=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0 h1:WLUIpeyv04H0RCcQHaA4TNoyrQ39Ox7V+re+iaqzTe0=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0/go.mod h1:hd8hTTIY3VmUVPRHNH7GVCHO3SHgXkJKZHReby/bnUQ=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 h1:eXnN9kaS8TiDwXjoie3hMRLuwdUBUMW9KRgOqB3mCaw=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0/go.mod h1:XIpam8wumeZ5rVMuhdDQLMfIPDf1WO3IzrCRO3e3e3o=
|
||||||
|
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
||||||
|
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
|
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
|
||||||
|
|
@ -97,6 +114,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
|
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
|
||||||
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||||
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
|
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
|
||||||
|
|
@ -134,6 +153,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
|
@ -215,6 +236,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
||||||
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
||||||
|
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
|
||||||
|
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
|
|
@ -269,6 +292,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
||||||
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
|
@ -284,6 +309,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ
|
||||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
|
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||||
|
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
|
|
@ -355,6 +382,7 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
|
|
||||||
10
main.go
10
main.go
|
|
@ -274,10 +274,18 @@ func main() {
|
||||||
log.WithName("actions-clients"),
|
log.WithName("actions-clients"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
secretResolver := actionsgithubcom.NewSecretResolver(
|
||||||
|
mgr.GetClient(),
|
||||||
|
actionsMultiClient,
|
||||||
|
)
|
||||||
|
|
||||||
rb := actionsgithubcom.ResourceBuilder{
|
rb := actionsgithubcom.ResourceBuilder{
|
||||||
ExcludeLabelPropagationPrefixes: excludeLabelPropagationPrefixes,
|
ExcludeLabelPropagationPrefixes: excludeLabelPropagationPrefixes,
|
||||||
|
SecretResolver: secretResolver,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info("Resource builder initializing")
|
||||||
|
|
||||||
if err = (&actionsgithubcom.AutoscalingRunnerSetReconciler{
|
if err = (&actionsgithubcom.AutoscalingRunnerSetReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Log: log.WithName("AutoscalingRunnerSet").WithValues("version", build.Version),
|
Log: log.WithName("AutoscalingRunnerSet").WithValues("version", build.Version),
|
||||||
|
|
@ -297,7 +305,6 @@ func main() {
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Log: log.WithName("EphemeralRunner").WithValues("version", build.Version),
|
Log: log.WithName("EphemeralRunner").WithValues("version", build.Version),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
ActionsClient: actionsMultiClient,
|
|
||||||
ResourceBuilder: rb,
|
ResourceBuilder: rb,
|
||||||
}).SetupWithManager(mgr, actionsgithubcom.WithMaxConcurrentReconciles(opts.RunnerMaxConcurrentReconciles)); err != nil {
|
}).SetupWithManager(mgr, actionsgithubcom.WithMaxConcurrentReconciles(opts.RunnerMaxConcurrentReconciles)); err != nil {
|
||||||
log.Error(err, "unable to create controller", "controller", "EphemeralRunner")
|
log.Error(err, "unable to create controller", "controller", "EphemeralRunner")
|
||||||
|
|
@ -308,7 +315,6 @@ func main() {
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Log: log.WithName("EphemeralRunnerSet").WithValues("version", build.Version),
|
Log: log.WithName("EphemeralRunnerSet").WithValues("version", build.Version),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
ActionsClient: actionsMultiClient,
|
|
||||||
PublishMetrics: metricsAddr != "0",
|
PublishMetrics: metricsAddr != "0",
|
||||||
ResourceBuilder: rb,
|
ResourceBuilder: rb,
|
||||||
}).SetupWithManager(mgr); err != nil {
|
}).SetupWithManager(mgr); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,6 @@ func TestARCJobs(t *testing.T) {
|
||||||
if !success {
|
if !success {
|
||||||
t.Fatal("Expected pods count did not match available pods count during job run.")
|
t.Fatal("Expected pods count did not match available pods count during job run.")
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
t.Run("Get available pods after job run", func(t *testing.T) {
|
t.Run("Get available pods after job run", func(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package azurekeyvault
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AzureKeyVault is a struct that holds the Azure Key Vault client.
|
||||||
|
type AzureKeyVault struct {
|
||||||
|
client *azsecrets.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg Config) (*AzureKeyVault, error) {
|
||||||
|
if err := cfg.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to validate config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := cfg.Client()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create azsecrets client from config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AzureKeyVault{client: client}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSecret retrieves a secret from Azure Key Vault.
|
||||||
|
func (v *AzureKeyVault) GetSecret(ctx context.Context, name string) (string, error) {
|
||||||
|
secret, err := v.client.GetSecret(ctx, name, "", nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get secret: %w", err)
|
||||||
|
}
|
||||||
|
if secret.Value == nil {
|
||||||
|
return "", fmt.Errorf("secret value is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return *secret.Value, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
package azurekeyvault
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
|
||||||
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
|
"golang.org/x/net/http/httpproxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AzureKeyVault is a struct that holds the Azure Key Vault client.
|
||||||
|
type Config struct {
|
||||||
|
TenantID string `json:"tenant_id"`
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
CertificatePath string `json:"certificate_path"`
|
||||||
|
Proxy *httpproxy.Config `json:"proxy,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Validate() error {
|
||||||
|
if c.TenantID == "" {
|
||||||
|
return errors.New("tenant_id is not set")
|
||||||
|
}
|
||||||
|
if c.ClientID == "" {
|
||||||
|
return errors.New("client_id is not set")
|
||||||
|
}
|
||||||
|
if _, err := url.ParseRequestURI(c.URL); err != nil {
|
||||||
|
return fmt.Errorf("failed to parse url: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.CertificatePath == "" {
|
||||||
|
return errors.New("cert path must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(c.CertificatePath); err != nil {
|
||||||
|
return fmt.Errorf("cert path %q does not exist: %v", c.CertificatePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Proxy != nil {
|
||||||
|
if c.Proxy.HTTPProxy == "" && c.Proxy.HTTPSProxy == "" && c.Proxy.NoProxy == "" {
|
||||||
|
return errors.New("proxy configuration is empty, at least one proxy must be set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client creates a new Azure Key Vault client using the provided configuration.
|
||||||
|
func (c *Config) Client() (*azsecrets.Client, error) {
|
||||||
|
return c.certClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) certClient() (*azsecrets.Client, error) {
|
||||||
|
data, err := os.ReadFile(c.CertificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read cert file from path %q: %v", c.CertificatePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certs, key, err := azidentity.ParseCertificates(data, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse certificates: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient, err := c.httpClient()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to instantiate http client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cred, err := azidentity.NewClientCertificateCredential(
|
||||||
|
c.TenantID,
|
||||||
|
c.ClientID,
|
||||||
|
certs,
|
||||||
|
key,
|
||||||
|
&azidentity.ClientCertificateCredentialOptions{
|
||||||
|
ClientOptions: policy.ClientOptions{
|
||||||
|
Transport: httpClient,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create client certificate credential: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := azsecrets.NewClient(c.URL, cred, &azsecrets.ClientOptions{
|
||||||
|
ClientOptions: policy.ClientOptions{
|
||||||
|
Transport: httpClient,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to instantiate client for azsecrets: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) httpClient() (*http.Client, error) {
|
||||||
|
retryClient := retryablehttp.NewClient()
|
||||||
|
retryClient.RetryMax = 4
|
||||||
|
retryClient.RetryWaitMax = 30 * time.Second
|
||||||
|
retryClient.HTTPClient.Timeout = 5 * time.Minute
|
||||||
|
|
||||||
|
transport, ok := retryClient.HTTPClient.Transport.(*http.Transport)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to get http transport")
|
||||||
|
}
|
||||||
|
if c.Proxy != nil {
|
||||||
|
transport.Proxy = func(req *http.Request) (*url.URL, error) {
|
||||||
|
return c.Proxy.ProxyFunc()(req.URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retryClient.StandardClient(), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
package azurekeyvault
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/net/http/httpproxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigValidate_invalid(t *testing.T) {
|
||||||
|
tenantID := "tenantID"
|
||||||
|
clientID := "clientID"
|
||||||
|
url := "https://example.com"
|
||||||
|
|
||||||
|
cp, err := os.CreateTemp("", "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = cp.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
certPath := cp.Name()
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
os.Remove(certPath)
|
||||||
|
})
|
||||||
|
|
||||||
|
tt := map[string]*Config{
|
||||||
|
"empty": {},
|
||||||
|
"no tenant id": {
|
||||||
|
TenantID: "",
|
||||||
|
ClientID: clientID,
|
||||||
|
URL: url,
|
||||||
|
CertificatePath: certPath,
|
||||||
|
},
|
||||||
|
"no client id": {
|
||||||
|
TenantID: tenantID,
|
||||||
|
ClientID: "",
|
||||||
|
URL: url,
|
||||||
|
CertificatePath: certPath,
|
||||||
|
},
|
||||||
|
"no url": {
|
||||||
|
TenantID: tenantID,
|
||||||
|
ClientID: clientID,
|
||||||
|
URL: "",
|
||||||
|
CertificatePath: certPath,
|
||||||
|
},
|
||||||
|
"no jwt and no cert path": {
|
||||||
|
TenantID: tenantID,
|
||||||
|
ClientID: clientID,
|
||||||
|
URL: url,
|
||||||
|
CertificatePath: "",
|
||||||
|
},
|
||||||
|
"invalid proxy": {
|
||||||
|
TenantID: tenantID,
|
||||||
|
ClientID: clientID,
|
||||||
|
URL: url,
|
||||||
|
CertificatePath: certPath,
|
||||||
|
Proxy: &httpproxy.Config{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, cfg := range tt {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
err := cfg.Validate()
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate_valid(t *testing.T) {
|
||||||
|
tenantID := "tenantID"
|
||||||
|
clientID := "clientID"
|
||||||
|
url := "https://example.com"
|
||||||
|
|
||||||
|
certPath, err := filepath.Abs("testdata/server.crt")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tt := map[string]*Config{
|
||||||
|
"with cert": {
|
||||||
|
TenantID: tenantID,
|
||||||
|
ClientID: clientID,
|
||||||
|
URL: url,
|
||||||
|
CertificatePath: certPath,
|
||||||
|
},
|
||||||
|
"without proxy": {
|
||||||
|
TenantID: tenantID,
|
||||||
|
ClientID: clientID,
|
||||||
|
URL: url,
|
||||||
|
CertificatePath: certPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, cfg := range tt {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
err := cfg.Validate()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDOjCCAiKgAwIBAgIUQr7R8yN5+2and6ucUOPF6oIbD48wDQYJKoZIhvcNAQEL
|
||||||
|
BQAwFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMB4XDTI1MDIyODEyMDEzMFoXDTI2
|
||||||
|
MDcxMzEyMDEzMFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B
|
||||||
|
AQEFAAOCAQ8AMIIBCgKCAQEA4oL2hAPQlDVaNJru5fIstkpoVSuam0vpswC7ciRc
|
||||||
|
XQRjF3q8kjtIA7+jdySsKJqOLGnybDX3awvRyKMEjq11IfnZLjZc+FzTlA+x4z0h
|
||||||
|
MHb0GiBFXKNzrExGI9F0KEPtFxcMIqZ119LY2ReexxWkZBQYlgTepaevp71za4c2
|
||||||
|
n4Zy1+0iS5+uklZ4ANKMTBGlN76Qgt530VnpNiIeUbiUzY58Vx4q7kFcUv/oSz8p
|
||||||
|
rbXr+/GGpAjrOc6/JsezRE8YK2po60dvV80TJ2Jt6pduvF7OSQnq/v4mJl1xuXKl
|
||||||
|
Byo9HLbeu3BuVRWQs2/EwEzx5kX3Ugysl9Bm44K2yKe9/QIDAQABo4GAMH4wHwYD
|
||||||
|
VR0jBBgwFoAUfd/q0BY4fkVBV3X+HWzXH0toW08wCQYDVR0TBAIwADALBgNVHQ8E
|
||||||
|
BAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0RBAgwBocEfwAAATAdBgNV
|
||||||
|
HQ4EFgQUe0rTTfWjho3hgeLTnajTCpddo2MwDQYJKoZIhvcNAQELBQADggEBAIR2
|
||||||
|
5zkA7rPnddxCunsz8Jjq3wyhR/KiAFz+RGeFeiXDkF2fWr7QIQ9KbFbv8tpfXR7P
|
||||||
|
B75bY0sXwutHMB2sZDi92cH5sthNBfp19fI35cxcU4oTPxp4UZJKEiA3Qx8y73CX
|
||||||
|
NJu1009nPdOJNlIboDGAFdZ5SH6RCh+YcQZ68kjHPWBIpXxLbs9FN3QmpbAvtLh1
|
||||||
|
PoPaSy7IjKmxm1u+Lf6tyIn2IiB3MiynaB3OKvbkLCseM/5SZKMk6WKSDWopOCJr
|
||||||
|
xciPOc+yeLz5I2Omn0uViOIIciqjlgxncWAyNtDgvJcecwqB2cPiIhk6GY0QZ1uM
|
||||||
|
e7KoqGzWXvWLqJ13a9U=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package vault
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/actions/actions-runner-controller/vault/azurekeyvault"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Vault is the interface every vault implementation needs to adhere to
|
||||||
|
type Vault interface {
|
||||||
|
GetSecret(ctx context.Context, name string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VaultType represents the type of vault that can be used in the application.
|
||||||
|
// It is used to identify which vault integration should be used to resolve secrets.
|
||||||
|
type VaultType string
|
||||||
|
|
||||||
|
// VaultType is the type of vault supported
|
||||||
|
const (
|
||||||
|
VaultTypeAzureKeyVault VaultType = "azure_key_vault"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t VaultType) String() string {
|
||||||
|
return string(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t VaultType) Validate() error {
|
||||||
|
switch t {
|
||||||
|
case VaultTypeAzureKeyVault:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown vault type: %q", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time checks
|
||||||
|
var _ Vault = (*azurekeyvault.AzureKeyVault)(nil)
|
||||||
Loading…
Reference in New Issue