786 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			786 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Go
		
	
	
	
package actionsgithubcom
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"maps"
 | 
						|
	"math"
 | 
						|
	"net"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
 | 
						|
	"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1/appconfig"
 | 
						|
	"github.com/actions/actions-runner-controller/build"
 | 
						|
	ghalistenerconfig "github.com/actions/actions-runner-controller/cmd/ghalistener/config"
 | 
						|
	"github.com/actions/actions-runner-controller/github/actions"
 | 
						|
	"github.com/actions/actions-runner-controller/hash"
 | 
						|
	"github.com/actions/actions-runner-controller/logging"
 | 
						|
	"github.com/actions/actions-runner-controller/vault/azurekeyvault"
 | 
						|
	corev1 "k8s.io/api/core/v1"
 | 
						|
	rbacv1 "k8s.io/api/rbac/v1"
 | 
						|
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						|
)
 | 
						|
 | 
						|
// secret constants
 | 
						|
const (
 | 
						|
	jitTokenKey = "jitToken"
 | 
						|
)
 | 
						|
 | 
						|
var commonLabelKeys = [...]string{
 | 
						|
	LabelKeyKubernetesPartOf,
 | 
						|
	LabelKeyKubernetesComponent,
 | 
						|
	LabelKeyKubernetesVersion,
 | 
						|
	LabelKeyGitHubScaleSetName,
 | 
						|
	LabelKeyGitHubScaleSetNamespace,
 | 
						|
	LabelKeyGitHubEnterprise,
 | 
						|
	LabelKeyGitHubOrganization,
 | 
						|
	LabelKeyGitHubRepository,
 | 
						|
}
 | 
						|
 | 
						|
const labelValueKubernetesPartOf = "gha-runner-scale-set"
 | 
						|
 | 
						|
var (
 | 
						|
	scaleSetListenerLogLevel   = DefaultScaleSetListenerLogLevel
 | 
						|
	scaleSetListenerLogFormat  = DefaultScaleSetListenerLogFormat
 | 
						|
	scaleSetListenerEntrypoint = "/ghalistener"
 | 
						|
)
 | 
						|
 | 
						|
func SetListenerLoggingParameters(level string, format string) bool {
 | 
						|
	switch level {
 | 
						|
	case logging.LogLevelDebug, logging.LogLevelInfo, logging.LogLevelWarn, logging.LogLevelError:
 | 
						|
	default:
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	switch format {
 | 
						|
	case logging.LogFormatJSON, logging.LogFormatText:
 | 
						|
	default:
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	scaleSetListenerLogLevel = level
 | 
						|
	scaleSetListenerLogFormat = format
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func SetListenerEntrypoint(entrypoint string) {
 | 
						|
	if entrypoint != "" {
 | 
						|
		scaleSetListenerEntrypoint = entrypoint
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type ResourceBuilder struct {
 | 
						|
	ExcludeLabelPropagationPrefixes []string
 | 
						|
	*SecretResolver
 | 
						|
}
 | 
						|
 | 
						|
// boolPtr returns a pointer to a bool value
 | 
						|
func boolPtr(v bool) *bool {
 | 
						|
	return &v
 | 
						|
}
 | 
						|
 | 
						|
func (b *ResourceBuilder) newAutoScalingListener(autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, ephemeralRunnerSet *v1alpha1.EphemeralRunnerSet, namespace, image string, imagePullSecrets []corev1.LocalObjectReference) (*v1alpha1.AutoscalingListener, error) {
 | 
						|
	runnerScaleSetId, err := strconv.Atoi(autoscalingRunnerSet.Annotations[runnerScaleSetIdAnnotationKey])
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	effectiveMinRunners := 0
 | 
						|
	effectiveMaxRunners := math.MaxInt32
 | 
						|
	if autoscalingRunnerSet.Spec.MaxRunners != nil {
 | 
						|
		effectiveMaxRunners = *autoscalingRunnerSet.Spec.MaxRunners
 | 
						|
	}
 | 
						|
	if autoscalingRunnerSet.Spec.MinRunners != nil {
 | 
						|
		effectiveMinRunners = *autoscalingRunnerSet.Spec.MinRunners
 | 
						|
	}
 | 
						|
 | 
						|
	labels := b.mergeLabels(autoscalingRunnerSet.Labels, map[string]string{
 | 
						|
		LabelKeyGitHubScaleSetNamespace: autoscalingRunnerSet.Namespace,
 | 
						|
		LabelKeyGitHubScaleSetName:      autoscalingRunnerSet.Name,
 | 
						|
		LabelKeyKubernetesPartOf:        labelValueKubernetesPartOf,
 | 
						|
		LabelKeyKubernetesComponent:     "runner-scale-set-listener",
 | 
						|
		LabelKeyKubernetesVersion:       autoscalingRunnerSet.Labels[LabelKeyKubernetesVersion],
 | 
						|
	})
 | 
						|
 | 
						|
	annotations := map[string]string{
 | 
						|
		annotationKeyRunnerSpecHash: autoscalingRunnerSet.ListenerSpecHash(),
 | 
						|
		annotationKeyValuesHash:     autoscalingRunnerSet.Annotations[annotationKeyValuesHash],
 | 
						|
	}
 | 
						|
 | 
						|
	if err := applyGitHubURLLabels(autoscalingRunnerSet.Spec.GitHubConfigUrl, labels); err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to apply GitHub URL labels: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	autoscalingListener := &v1alpha1.AutoscalingListener{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name:        scaleSetListenerName(autoscalingRunnerSet),
 | 
						|
			Namespace:   namespace,
 | 
						|
			Labels:      labels,
 | 
						|
			Annotations: annotations,
 | 
						|
		},
 | 
						|
		Spec: v1alpha1.AutoscalingListenerSpec{
 | 
						|
			GitHubConfigUrl:               autoscalingRunnerSet.Spec.GitHubConfigUrl,
 | 
						|
			GitHubConfigSecret:            autoscalingRunnerSet.Spec.GitHubConfigSecret,
 | 
						|
			VaultConfig:                   autoscalingRunnerSet.VaultConfig(),
 | 
						|
			RunnerScaleSetId:              runnerScaleSetId,
 | 
						|
			AutoscalingRunnerSetNamespace: autoscalingRunnerSet.Namespace,
 | 
						|
			AutoscalingRunnerSetName:      autoscalingRunnerSet.Name,
 | 
						|
			EphemeralRunnerSetName:        ephemeralRunnerSet.Name,
 | 
						|
			MinRunners:                    effectiveMinRunners,
 | 
						|
			MaxRunners:                    effectiveMaxRunners,
 | 
						|
			Image:                         image,
 | 
						|
			ImagePullSecrets:              imagePullSecrets,
 | 
						|
			Proxy:                         autoscalingRunnerSet.Spec.Proxy,
 | 
						|
			GitHubServerTLS:               autoscalingRunnerSet.Spec.GitHubServerTLS,
 | 
						|
			Metrics:                       autoscalingRunnerSet.Spec.ListenerMetrics,
 | 
						|
			Template:                      autoscalingRunnerSet.Spec.ListenerTemplate,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	return autoscalingListener, nil
 | 
						|
}
 | 
						|
 | 
						|
type listenerMetricsServerConfig struct {
 | 
						|
	addr     string
 | 
						|
	endpoint string
 | 
						|
}
 | 
						|
 | 
						|
func (lm *listenerMetricsServerConfig) containerPort() (corev1.ContainerPort, error) {
 | 
						|
	_, portStr, err := net.SplitHostPort(lm.addr)
 | 
						|
	if err != nil {
 | 
						|
		return corev1.ContainerPort{}, err
 | 
						|
	}
 | 
						|
	port, err := strconv.ParseInt(portStr, 10, 32)
 | 
						|
	if err != nil {
 | 
						|
		return corev1.ContainerPort{}, err
 | 
						|
	}
 | 
						|
	return corev1.ContainerPort{
 | 
						|
		ContainerPort: int32(port),
 | 
						|
		Protocol:      corev1.ProtocolTCP,
 | 
						|
		Name:          "metrics",
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (b *ResourceBuilder) newScaleSetListenerConfig(autoscalingListener *v1alpha1.AutoscalingListener, appConfig *appconfig.AppConfig, metricsConfig *listenerMetricsServerConfig, cert string) (*corev1.Secret, error) {
 | 
						|
	var (
 | 
						|
		metricsAddr     = ""
 | 
						|
		metricsEndpoint = ""
 | 
						|
	)
 | 
						|
	if metricsConfig != nil {
 | 
						|
		metricsAddr = metricsConfig.addr
 | 
						|
		metricsEndpoint = metricsConfig.endpoint
 | 
						|
	}
 | 
						|
 | 
						|
	config := ghalistenerconfig.Config{
 | 
						|
		ConfigureUrl:                autoscalingListener.Spec.GitHubConfigUrl,
 | 
						|
		EphemeralRunnerSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
 | 
						|
		EphemeralRunnerSetName:      autoscalingListener.Spec.EphemeralRunnerSetName,
 | 
						|
		MaxRunners:                  autoscalingListener.Spec.MaxRunners,
 | 
						|
		MinRunners:                  autoscalingListener.Spec.MinRunners,
 | 
						|
		RunnerScaleSetId:            autoscalingListener.Spec.RunnerScaleSetId,
 | 
						|
		RunnerScaleSetName:          autoscalingListener.Spec.AutoscalingRunnerSetName,
 | 
						|
		ServerRootCA:                cert,
 | 
						|
		LogLevel:                    scaleSetListenerLogLevel,
 | 
						|
		LogFormat:                   scaleSetListenerLogFormat,
 | 
						|
		MetricsAddr:                 metricsAddr,
 | 
						|
		MetricsEndpoint:             metricsEndpoint,
 | 
						|
		Metrics:                     autoscalingListener.Spec.Metrics,
 | 
						|
	}
 | 
						|
 | 
						|
	vault := autoscalingListener.Spec.VaultConfig
 | 
						|
	if vault == nil {
 | 
						|
		config.AppConfig = appConfig
 | 
						|
	} else {
 | 
						|
		config.VaultType = vault.Type
 | 
						|
		config.VaultLookupKey = autoscalingListener.Spec.GitHubConfigSecret
 | 
						|
		config.AzureKeyVaultConfig = &azurekeyvault.Config{
 | 
						|
			TenantID:        vault.AzureKeyVault.TenantID,
 | 
						|
			ClientID:        vault.AzureKeyVault.ClientID,
 | 
						|
			URL:             vault.AzureKeyVault.URL,
 | 
						|
			CertificatePath: vault.AzureKeyVault.CertificatePath,
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if err := config.Validate(); err != nil {
 | 
						|
		return nil, fmt.Errorf("invalid listener config: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	var buf bytes.Buffer
 | 
						|
	if err := json.NewEncoder(&buf).Encode(config); err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to encode config: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return &corev1.Secret{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name:      scaleSetListenerConfigName(autoscalingListener),
 | 
						|
			Namespace: autoscalingListener.Namespace,
 | 
						|
		},
 | 
						|
		Data: map[string][]byte{
 | 
						|
			"config.json": buf.Bytes(),
 | 
						|
		},
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (b *ResourceBuilder) newScaleSetListenerPod(autoscalingListener *v1alpha1.AutoscalingListener, podConfig *corev1.Secret, serviceAccount *corev1.ServiceAccount, metricsConfig *listenerMetricsServerConfig, envs ...corev1.EnvVar) (*corev1.Pod, error) {
 | 
						|
	listenerEnv := []corev1.EnvVar{
 | 
						|
		{
 | 
						|
			Name:  "LISTENER_CONFIG_PATH",
 | 
						|
			Value: "/etc/gha-listener/config.json",
 | 
						|
		},
 | 
						|
	}
 | 
						|
	listenerEnv = append(listenerEnv, envs...)
 | 
						|
 | 
						|
	var ports []corev1.ContainerPort
 | 
						|
	if metricsConfig != nil && len(metricsConfig.addr) != 0 {
 | 
						|
		port, err := metricsConfig.containerPort()
 | 
						|
		if err != nil {
 | 
						|
			return nil, fmt.Errorf("failed to convert metrics server address to container port: %v", err)
 | 
						|
		}
 | 
						|
		ports = append(ports, port)
 | 
						|
	}
 | 
						|
 | 
						|
	terminationGracePeriodSeconds := int64(60)
 | 
						|
	podSpec := corev1.PodSpec{
 | 
						|
		ServiceAccountName: serviceAccount.Name,
 | 
						|
		Containers: []corev1.Container{
 | 
						|
			{
 | 
						|
				Name:  autoscalingListenerContainerName,
 | 
						|
				Image: autoscalingListener.Spec.Image,
 | 
						|
				Env:   listenerEnv,
 | 
						|
				Command: []string{
 | 
						|
					scaleSetListenerEntrypoint,
 | 
						|
				},
 | 
						|
				Ports: ports,
 | 
						|
				VolumeMounts: []corev1.VolumeMount{
 | 
						|
					{
 | 
						|
						Name:      "listener-config",
 | 
						|
						MountPath: "/etc/gha-listener",
 | 
						|
						ReadOnly:  true,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		Volumes: []corev1.Volume{
 | 
						|
			{
 | 
						|
				Name: "listener-config",
 | 
						|
				VolumeSource: corev1.VolumeSource{
 | 
						|
					Secret: &corev1.SecretVolumeSource{
 | 
						|
						SecretName: podConfig.Name,
 | 
						|
					},
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		ImagePullSecrets:              autoscalingListener.Spec.ImagePullSecrets,
 | 
						|
		RestartPolicy:                 corev1.RestartPolicyNever,
 | 
						|
		TerminationGracePeriodSeconds: &terminationGracePeriodSeconds,
 | 
						|
	}
 | 
						|
 | 
						|
	labels := make(map[string]string, len(autoscalingListener.Labels))
 | 
						|
	maps.Copy(labels, autoscalingListener.Labels)
 | 
						|
 | 
						|
	newRunnerScaleSetListenerPod := &corev1.Pod{
 | 
						|
		TypeMeta: metav1.TypeMeta{
 | 
						|
			Kind:       "Pod",
 | 
						|
			APIVersion: "v1",
 | 
						|
		},
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name:      autoscalingListener.Name,
 | 
						|
			Namespace: autoscalingListener.Namespace,
 | 
						|
			Labels:    labels,
 | 
						|
			OwnerReferences: []metav1.OwnerReference{
 | 
						|
				{
 | 
						|
					APIVersion:         autoscalingListener.GetObjectKind().GroupVersionKind().GroupVersion().String(),
 | 
						|
					Kind:               autoscalingListener.GetObjectKind().GroupVersionKind().Kind,
 | 
						|
					UID:                autoscalingListener.GetUID(),
 | 
						|
					Name:               autoscalingListener.GetName(),
 | 
						|
					Controller:         boolPtr(true),
 | 
						|
					BlockOwnerDeletion: boolPtr(true),
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		Spec: podSpec,
 | 
						|
	}
 | 
						|
 | 
						|
	if autoscalingListener.Spec.Template != nil {
 | 
						|
		mergeListenerPodWithTemplate(newRunnerScaleSetListenerPod, autoscalingListener.Spec.Template)
 | 
						|
	}
 | 
						|
 | 
						|
	return newRunnerScaleSetListenerPod, nil
 | 
						|
}
 | 
						|
 | 
						|
func mergeListenerPodWithTemplate(pod *corev1.Pod, tmpl *corev1.PodTemplateSpec) {
 | 
						|
	if pod.Annotations == nil {
 | 
						|
		pod.Annotations = make(map[string]string)
 | 
						|
	}
 | 
						|
	for k, v := range tmpl.Annotations {
 | 
						|
		if _, ok := pod.Annotations[k]; !ok {
 | 
						|
			pod.Annotations[k] = v
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for k, v := range tmpl.Labels {
 | 
						|
		if _, ok := pod.Labels[k]; !ok {
 | 
						|
			pod.Labels[k] = v
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// apply spec
 | 
						|
 | 
						|
	// apply container
 | 
						|
	listenerContainer := &pod.Spec.Containers[0] // if this panics, we have bigger problems
 | 
						|
	for i := range tmpl.Spec.Containers {
 | 
						|
		c := &tmpl.Spec.Containers[i]
 | 
						|
 | 
						|
		switch c.Name {
 | 
						|
		case autoscalingListenerContainerName:
 | 
						|
			mergeListenerContainer(listenerContainer, c)
 | 
						|
		default:
 | 
						|
			pod.Spec.Containers = append(pod.Spec.Containers, *c)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// apply pod related spec
 | 
						|
	// NOTE: fields that should be ignored
 | 
						|
	// - service account based fields
 | 
						|
 | 
						|
	if tmpl.Spec.RestartPolicy != "" {
 | 
						|
		pod.Spec.RestartPolicy = tmpl.Spec.RestartPolicy
 | 
						|
	}
 | 
						|
 | 
						|
	if tmpl.Spec.ImagePullSecrets != nil {
 | 
						|
		pod.Spec.ImagePullSecrets = tmpl.Spec.ImagePullSecrets
 | 
						|
	}
 | 
						|
 | 
						|
	pod.Spec.Volumes = append(pod.Spec.Volumes, tmpl.Spec.Volumes...)
 | 
						|
	pod.Spec.InitContainers = tmpl.Spec.InitContainers
 | 
						|
	pod.Spec.EphemeralContainers = tmpl.Spec.EphemeralContainers
 | 
						|
	pod.Spec.TerminationGracePeriodSeconds = tmpl.Spec.TerminationGracePeriodSeconds
 | 
						|
	pod.Spec.ActiveDeadlineSeconds = tmpl.Spec.ActiveDeadlineSeconds
 | 
						|
	pod.Spec.DNSPolicy = tmpl.Spec.DNSPolicy
 | 
						|
	pod.Spec.NodeSelector = tmpl.Spec.NodeSelector
 | 
						|
	pod.Spec.NodeName = tmpl.Spec.NodeName
 | 
						|
	pod.Spec.HostNetwork = tmpl.Spec.HostNetwork
 | 
						|
	pod.Spec.HostPID = tmpl.Spec.HostPID
 | 
						|
	pod.Spec.HostIPC = tmpl.Spec.HostIPC
 | 
						|
	pod.Spec.ShareProcessNamespace = tmpl.Spec.ShareProcessNamespace
 | 
						|
	pod.Spec.SecurityContext = tmpl.Spec.SecurityContext
 | 
						|
	pod.Spec.Hostname = tmpl.Spec.Hostname
 | 
						|
	pod.Spec.Subdomain = tmpl.Spec.Subdomain
 | 
						|
	pod.Spec.Affinity = tmpl.Spec.Affinity
 | 
						|
	pod.Spec.SchedulerName = tmpl.Spec.SchedulerName
 | 
						|
	pod.Spec.Tolerations = tmpl.Spec.Tolerations
 | 
						|
	pod.Spec.HostAliases = tmpl.Spec.HostAliases
 | 
						|
	pod.Spec.PriorityClassName = tmpl.Spec.PriorityClassName
 | 
						|
	pod.Spec.Priority = tmpl.Spec.Priority
 | 
						|
	pod.Spec.DNSConfig = tmpl.Spec.DNSConfig
 | 
						|
	pod.Spec.ReadinessGates = tmpl.Spec.ReadinessGates
 | 
						|
	pod.Spec.RuntimeClassName = tmpl.Spec.RuntimeClassName
 | 
						|
	pod.Spec.EnableServiceLinks = tmpl.Spec.EnableServiceLinks
 | 
						|
	pod.Spec.PreemptionPolicy = tmpl.Spec.PreemptionPolicy
 | 
						|
	pod.Spec.Overhead = tmpl.Spec.Overhead
 | 
						|
	pod.Spec.TopologySpreadConstraints = tmpl.Spec.TopologySpreadConstraints
 | 
						|
	pod.Spec.SetHostnameAsFQDN = tmpl.Spec.SetHostnameAsFQDN
 | 
						|
	pod.Spec.OS = tmpl.Spec.OS
 | 
						|
	pod.Spec.HostUsers = tmpl.Spec.HostUsers
 | 
						|
	pod.Spec.SchedulingGates = tmpl.Spec.SchedulingGates
 | 
						|
	pod.Spec.ResourceClaims = tmpl.Spec.ResourceClaims
 | 
						|
}
 | 
						|
 | 
						|
func mergeListenerContainer(base, from *corev1.Container) {
 | 
						|
	// name should not be modified
 | 
						|
 | 
						|
	if from.Image != "" {
 | 
						|
		base.Image = from.Image
 | 
						|
	}
 | 
						|
 | 
						|
	if len(from.Command) > 0 {
 | 
						|
		base.Command = from.Command
 | 
						|
	}
 | 
						|
 | 
						|
	base.Env = append(base.Env, from.Env...)
 | 
						|
 | 
						|
	base.ImagePullPolicy = from.ImagePullPolicy
 | 
						|
	base.Args = append(base.Args, from.Args...)
 | 
						|
	base.WorkingDir = from.WorkingDir
 | 
						|
	base.Ports = append(base.Ports, from.Ports...)
 | 
						|
	base.EnvFrom = append(base.EnvFrom, from.EnvFrom...)
 | 
						|
	base.Resources = from.Resources
 | 
						|
	base.VolumeMounts = append(base.VolumeMounts, from.VolumeMounts...)
 | 
						|
	base.VolumeDevices = append(base.VolumeDevices, from.VolumeDevices...)
 | 
						|
	base.LivenessProbe = from.LivenessProbe
 | 
						|
	base.ReadinessProbe = from.ReadinessProbe
 | 
						|
	base.StartupProbe = from.StartupProbe
 | 
						|
	base.Lifecycle = from.Lifecycle
 | 
						|
	base.TerminationMessagePath = from.TerminationMessagePath
 | 
						|
	base.TerminationMessagePolicy = from.TerminationMessagePolicy
 | 
						|
	base.ImagePullPolicy = from.ImagePullPolicy
 | 
						|
	base.SecurityContext = from.SecurityContext
 | 
						|
	base.ResizePolicy = from.ResizePolicy
 | 
						|
	base.RestartPolicy = from.RestartPolicy
 | 
						|
	base.Stdin = from.Stdin
 | 
						|
	base.StdinOnce = from.StdinOnce
 | 
						|
	base.TTY = from.TTY
 | 
						|
}
 | 
						|
 | 
						|
func (b *ResourceBuilder) newScaleSetListenerServiceAccount(autoscalingListener *v1alpha1.AutoscalingListener) *corev1.ServiceAccount {
 | 
						|
	return &corev1.ServiceAccount{
 | 
						|
		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,
 | 
						|
			}),
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (b *ResourceBuilder) newScaleSetListenerRole(autoscalingListener *v1alpha1.AutoscalingListener) *rbacv1.Role {
 | 
						|
	rules := rulesForListenerRole([]string{autoscalingListener.Spec.EphemeralRunnerSetName})
 | 
						|
	rulesHash := hash.ComputeTemplateHash(&rules)
 | 
						|
	newRole := &rbacv1.Role{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name:      autoscalingListener.Name,
 | 
						|
			Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
 | 
						|
			Labels: b.mergeLabels(autoscalingListener.Labels, map[string]string{
 | 
						|
				LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
 | 
						|
				LabelKeyGitHubScaleSetName:      autoscalingListener.Spec.AutoscalingRunnerSetName,
 | 
						|
				labelKeyListenerNamespace:       autoscalingListener.Namespace,
 | 
						|
				labelKeyListenerName:            autoscalingListener.Name,
 | 
						|
				"role-policy-rules-hash":        rulesHash,
 | 
						|
			}),
 | 
						|
		},
 | 
						|
		Rules: rules,
 | 
						|
	}
 | 
						|
 | 
						|
	return newRole
 | 
						|
}
 | 
						|
 | 
						|
func (b *ResourceBuilder) newScaleSetListenerRoleBinding(autoscalingListener *v1alpha1.AutoscalingListener, listenerRole *rbacv1.Role, serviceAccount *corev1.ServiceAccount) *rbacv1.RoleBinding {
 | 
						|
	roleRef := rbacv1.RoleRef{
 | 
						|
		Kind: "Role",
 | 
						|
		Name: listenerRole.Name,
 | 
						|
	}
 | 
						|
	roleRefHash := hash.ComputeTemplateHash(&roleRef)
 | 
						|
 | 
						|
	subjects := []rbacv1.Subject{
 | 
						|
		{
 | 
						|
			Kind:      "ServiceAccount",
 | 
						|
			Namespace: serviceAccount.Namespace,
 | 
						|
			Name:      serviceAccount.Name,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	subjectHash := hash.ComputeTemplateHash(&subjects)
 | 
						|
 | 
						|
	newRoleBinding := &rbacv1.RoleBinding{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name:      autoscalingListener.Name,
 | 
						|
			Namespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
 | 
						|
			Labels: b.mergeLabels(autoscalingListener.Labels, map[string]string{
 | 
						|
				LabelKeyGitHubScaleSetNamespace: autoscalingListener.Spec.AutoscalingRunnerSetNamespace,
 | 
						|
				LabelKeyGitHubScaleSetName:      autoscalingListener.Spec.AutoscalingRunnerSetName,
 | 
						|
				labelKeyListenerNamespace:       autoscalingListener.Namespace,
 | 
						|
				labelKeyListenerName:            autoscalingListener.Name,
 | 
						|
				"role-binding-role-ref-hash":    roleRefHash,
 | 
						|
				"role-binding-subject-hash":     subjectHash,
 | 
						|
			}),
 | 
						|
		},
 | 
						|
		RoleRef:  roleRef,
 | 
						|
		Subjects: subjects,
 | 
						|
	}
 | 
						|
 | 
						|
	return newRoleBinding
 | 
						|
}
 | 
						|
 | 
						|
func (b *ResourceBuilder) newEphemeralRunnerSet(autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet) (*v1alpha1.EphemeralRunnerSet, error) {
 | 
						|
	runnerScaleSetId, err := strconv.Atoi(autoscalingRunnerSet.Annotations[runnerScaleSetIdAnnotationKey])
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	runnerSpecHash := autoscalingRunnerSet.RunnerSetSpecHash()
 | 
						|
 | 
						|
	labels := b.mergeLabels(autoscalingRunnerSet.Labels, map[string]string{
 | 
						|
		LabelKeyKubernetesPartOf:        labelValueKubernetesPartOf,
 | 
						|
		LabelKeyKubernetesComponent:     "runner-set",
 | 
						|
		LabelKeyKubernetesVersion:       autoscalingRunnerSet.Labels[LabelKeyKubernetesVersion],
 | 
						|
		LabelKeyGitHubScaleSetName:      autoscalingRunnerSet.Name,
 | 
						|
		LabelKeyGitHubScaleSetNamespace: autoscalingRunnerSet.Namespace,
 | 
						|
	})
 | 
						|
 | 
						|
	if err := applyGitHubURLLabels(autoscalingRunnerSet.Spec.GitHubConfigUrl, labels); err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to apply GitHub URL labels: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	newAnnotations := map[string]string{
 | 
						|
		AnnotationKeyGitHubRunnerGroupName:    autoscalingRunnerSet.Annotations[AnnotationKeyGitHubRunnerGroupName],
 | 
						|
		AnnotationKeyGitHubRunnerScaleSetName: autoscalingRunnerSet.Annotations[AnnotationKeyGitHubRunnerScaleSetName],
 | 
						|
		annotationKeyRunnerSpecHash:           runnerSpecHash,
 | 
						|
	}
 | 
						|
 | 
						|
	newEphemeralRunnerSet := &v1alpha1.EphemeralRunnerSet{
 | 
						|
		TypeMeta: metav1.TypeMeta{},
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			GenerateName: autoscalingRunnerSet.Name + "-",
 | 
						|
			Namespace:    autoscalingRunnerSet.Namespace,
 | 
						|
			Labels:       labels,
 | 
						|
			Annotations:  newAnnotations,
 | 
						|
			OwnerReferences: []metav1.OwnerReference{
 | 
						|
				{
 | 
						|
					APIVersion:         autoscalingRunnerSet.GetObjectKind().GroupVersionKind().GroupVersion().String(),
 | 
						|
					Kind:               autoscalingRunnerSet.GetObjectKind().GroupVersionKind().Kind,
 | 
						|
					UID:                autoscalingRunnerSet.GetUID(),
 | 
						|
					Name:               autoscalingRunnerSet.GetName(),
 | 
						|
					Controller:         boolPtr(true),
 | 
						|
					BlockOwnerDeletion: boolPtr(true),
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		Spec: v1alpha1.EphemeralRunnerSetSpec{
 | 
						|
			Replicas: 0,
 | 
						|
			EphemeralRunnerSpec: v1alpha1.EphemeralRunnerSpec{
 | 
						|
				RunnerScaleSetId:   runnerScaleSetId,
 | 
						|
				GitHubConfigUrl:    autoscalingRunnerSet.Spec.GitHubConfigUrl,
 | 
						|
				GitHubConfigSecret: autoscalingRunnerSet.Spec.GitHubConfigSecret,
 | 
						|
				Proxy:              autoscalingRunnerSet.Spec.Proxy,
 | 
						|
				GitHubServerTLS:    autoscalingRunnerSet.Spec.GitHubServerTLS,
 | 
						|
				PodTemplateSpec:    autoscalingRunnerSet.Spec.Template,
 | 
						|
				VaultConfig:        autoscalingRunnerSet.VaultConfig(),
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	return newEphemeralRunnerSet, nil
 | 
						|
}
 | 
						|
 | 
						|
func (b *ResourceBuilder) newEphemeralRunner(ephemeralRunnerSet *v1alpha1.EphemeralRunnerSet) *v1alpha1.EphemeralRunner {
 | 
						|
	labels := make(map[string]string)
 | 
						|
	for k, v := range ephemeralRunnerSet.Labels {
 | 
						|
		if k == LabelKeyKubernetesComponent {
 | 
						|
			labels[k] = "runner"
 | 
						|
		} else {
 | 
						|
			labels[k] = v
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	annotations := make(map[string]string)
 | 
						|
	for key, val := range ephemeralRunnerSet.Annotations {
 | 
						|
		annotations[key] = val
 | 
						|
	}
 | 
						|
 | 
						|
	annotations[AnnotationKeyPatchID] = strconv.Itoa(ephemeralRunnerSet.Spec.PatchID)
 | 
						|
	return &v1alpha1.EphemeralRunner{
 | 
						|
		TypeMeta: metav1.TypeMeta{},
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			GenerateName: ephemeralRunnerSet.Name + "-runner-",
 | 
						|
			Namespace:    ephemeralRunnerSet.Namespace,
 | 
						|
			Labels:       labels,
 | 
						|
			Annotations:  annotations,
 | 
						|
			OwnerReferences: []metav1.OwnerReference{
 | 
						|
				{
 | 
						|
					APIVersion:         ephemeralRunnerSet.GetObjectKind().GroupVersionKind().GroupVersion().String(),
 | 
						|
					Kind:               ephemeralRunnerSet.GetObjectKind().GroupVersionKind().Kind,
 | 
						|
					UID:                ephemeralRunnerSet.GetUID(),
 | 
						|
					Name:               ephemeralRunnerSet.GetName(),
 | 
						|
					Controller:         boolPtr(true),
 | 
						|
					BlockOwnerDeletion: boolPtr(true),
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		Spec: ephemeralRunnerSet.Spec.EphemeralRunnerSpec,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (b *ResourceBuilder) newEphemeralRunnerPod(ctx context.Context, runner *v1alpha1.EphemeralRunner, secret *corev1.Secret, envs ...corev1.EnvVar) *corev1.Pod {
 | 
						|
	var newPod corev1.Pod
 | 
						|
 | 
						|
	labels := map[string]string{}
 | 
						|
	annotations := map[string]string{}
 | 
						|
 | 
						|
	for k, v := range runner.Labels {
 | 
						|
		labels[k] = v
 | 
						|
	}
 | 
						|
	for k, v := range runner.Spec.Labels {
 | 
						|
		labels[k] = v
 | 
						|
	}
 | 
						|
	labels["actions-ephemeral-runner"] = string(corev1.ConditionTrue)
 | 
						|
 | 
						|
	for k, v := range runner.Annotations {
 | 
						|
		annotations[k] = v
 | 
						|
	}
 | 
						|
	for k, v := range runner.Spec.Annotations {
 | 
						|
		annotations[k] = v
 | 
						|
	}
 | 
						|
 | 
						|
	labels[LabelKeyPodTemplateHash] = hash.FNVHashStringObjects(
 | 
						|
		FilterLabels(labels, LabelKeyRunnerTemplateHash),
 | 
						|
		annotations,
 | 
						|
		runner.Spec,
 | 
						|
		secret.Data,
 | 
						|
	)
 | 
						|
 | 
						|
	objectMeta := metav1.ObjectMeta{
 | 
						|
		Name:        runner.Name,
 | 
						|
		Namespace:   runner.Namespace,
 | 
						|
		Labels:      labels,
 | 
						|
		Annotations: annotations,
 | 
						|
		OwnerReferences: []metav1.OwnerReference{
 | 
						|
			{
 | 
						|
				APIVersion:         runner.GetObjectKind().GroupVersionKind().GroupVersion().String(),
 | 
						|
				Kind:               runner.GetObjectKind().GroupVersionKind().Kind,
 | 
						|
				UID:                runner.GetUID(),
 | 
						|
				Name:               runner.GetName(),
 | 
						|
				Controller:         boolPtr(true),
 | 
						|
				BlockOwnerDeletion: boolPtr(true),
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	newPod.ObjectMeta = objectMeta
 | 
						|
	newPod.Spec = runner.Spec.Spec
 | 
						|
	newPod.Spec.Containers = make([]corev1.Container, 0, len(runner.Spec.Spec.Containers))
 | 
						|
 | 
						|
	for _, c := range runner.Spec.Spec.Containers {
 | 
						|
		if c.Name == v1alpha1.EphemeralRunnerContainerName {
 | 
						|
			c.Env = append(
 | 
						|
				c.Env,
 | 
						|
				corev1.EnvVar{
 | 
						|
					Name: EnvVarRunnerJITConfig,
 | 
						|
					ValueFrom: &corev1.EnvVarSource{
 | 
						|
						SecretKeyRef: &corev1.SecretKeySelector{
 | 
						|
							LocalObjectReference: corev1.LocalObjectReference{
 | 
						|
								Name: secret.Name,
 | 
						|
							},
 | 
						|
							Key: jitTokenKey,
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
				corev1.EnvVar{
 | 
						|
					Name:  EnvVarRunnerExtraUserAgent,
 | 
						|
					Value: fmt.Sprintf("actions-runner-controller/%s", build.Version),
 | 
						|
				},
 | 
						|
			)
 | 
						|
			c.Env = append(c.Env, envs...)
 | 
						|
		}
 | 
						|
 | 
						|
		newPod.Spec.Containers = append(newPod.Spec.Containers, c)
 | 
						|
	}
 | 
						|
 | 
						|
	return &newPod
 | 
						|
}
 | 
						|
 | 
						|
func (b *ResourceBuilder) newEphemeralRunnerJitSecret(ephemeralRunner *v1alpha1.EphemeralRunner, jitConfig *actions.RunnerScaleSetJitRunnerConfig) *corev1.Secret {
 | 
						|
	return &corev1.Secret{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name:      ephemeralRunner.Name,
 | 
						|
			Namespace: ephemeralRunner.Namespace,
 | 
						|
		},
 | 
						|
		Data: map[string][]byte{
 | 
						|
			jitTokenKey:  []byte(jitConfig.EncodedJITConfig),
 | 
						|
			"runnerName": []byte(jitConfig.Runner.Name),
 | 
						|
			"runnerId":   []byte(strconv.Itoa(jitConfig.Runner.Id)),
 | 
						|
			"scaleSetId": []byte(strconv.Itoa(jitConfig.Runner.RunnerScaleSetId)),
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func scaleSetListenerConfigName(autoscalingListener *v1alpha1.AutoscalingListener) string {
 | 
						|
	return fmt.Sprintf("%s-config", autoscalingListener.Name)
 | 
						|
}
 | 
						|
 | 
						|
func scaleSetListenerName(autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet) string {
 | 
						|
	namespaceHash := hash.FNVHashString(autoscalingRunnerSet.Namespace)
 | 
						|
	if len(namespaceHash) > 8 {
 | 
						|
		namespaceHash = namespaceHash[:8]
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("%v-%v-listener", autoscalingRunnerSet.Name, namespaceHash)
 | 
						|
}
 | 
						|
 | 
						|
func proxyListenerSecretName(autoscalingListener *v1alpha1.AutoscalingListener) string {
 | 
						|
	namespaceHash := hash.FNVHashString(autoscalingListener.Spec.AutoscalingRunnerSetNamespace)
 | 
						|
	if len(namespaceHash) > 8 {
 | 
						|
		namespaceHash = namespaceHash[:8]
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("%v-%v-listener-proxy", autoscalingListener.Spec.AutoscalingRunnerSetName, namespaceHash)
 | 
						|
}
 | 
						|
 | 
						|
func proxyEphemeralRunnerSetSecretName(ephemeralRunnerSet *v1alpha1.EphemeralRunnerSet) string {
 | 
						|
	namespaceHash := hash.FNVHashString(ephemeralRunnerSet.Namespace)
 | 
						|
	if len(namespaceHash) > 8 {
 | 
						|
		namespaceHash = namespaceHash[:8]
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("%v-%v-runner-proxy", ephemeralRunnerSet.Name, namespaceHash)
 | 
						|
}
 | 
						|
 | 
						|
func rulesForListenerRole(resourceNames []string) []rbacv1.PolicyRule {
 | 
						|
	return []rbacv1.PolicyRule{
 | 
						|
		{
 | 
						|
			APIGroups:     []string{"actions.github.com"},
 | 
						|
			Resources:     []string{"ephemeralrunnersets"},
 | 
						|
			ResourceNames: resourceNames,
 | 
						|
			Verbs:         []string{"patch"},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			APIGroups: []string{"actions.github.com"},
 | 
						|
			Resources: []string{"ephemeralrunners", "ephemeralrunners/status"},
 | 
						|
			Verbs:     []string{"patch"},
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func applyGitHubURLLabels(url string, labels map[string]string) error {
 | 
						|
	githubConfig, err := actions.ParseGitHubConfigFromURL(url)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed to parse github config from url: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if len(githubConfig.Enterprise) > 0 {
 | 
						|
		labels[LabelKeyGitHubEnterprise] = trimLabelValue(githubConfig.Enterprise)
 | 
						|
	}
 | 
						|
	if len(githubConfig.Organization) > 0 {
 | 
						|
		labels[LabelKeyGitHubOrganization] = trimLabelValue(githubConfig.Organization)
 | 
						|
	}
 | 
						|
	if len(githubConfig.Repository) > 0 {
 | 
						|
		labels[LabelKeyGitHubRepository] = trimLabelValue(githubConfig.Repository)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
const trimLabelVauleSuffix = "-trim"
 | 
						|
 | 
						|
func trimLabelValue(val string) string {
 | 
						|
	if len(val) > 63 {
 | 
						|
		return val[:63-len(trimLabelVauleSuffix)] + trimLabelVauleSuffix
 | 
						|
	}
 | 
						|
	return strings.Trim(val, "-_.")
 | 
						|
}
 | 
						|
 | 
						|
func (b *ResourceBuilder) mergeLabels(base, overwrite map[string]string) map[string]string {
 | 
						|
	mergedLabels := make(map[string]string, len(base))
 | 
						|
 | 
						|
base:
 | 
						|
	for k, v := range base {
 | 
						|
		for _, prefix := range b.ExcludeLabelPropagationPrefixes {
 | 
						|
			if strings.HasPrefix(k, prefix) {
 | 
						|
				continue base
 | 
						|
			}
 | 
						|
		}
 | 
						|
		mergedLabels[k] = v
 | 
						|
	}
 | 
						|
 | 
						|
overwrite:
 | 
						|
	for k, v := range overwrite {
 | 
						|
		for _, prefix := range b.ExcludeLabelPropagationPrefixes {
 | 
						|
			if strings.HasPrefix(k, prefix) {
 | 
						|
				continue overwrite
 | 
						|
			}
 | 
						|
		}
 | 
						|
		mergedLabels[k] = v
 | 
						|
	}
 | 
						|
 | 
						|
	return mergedLabels
 | 
						|
}
 |