@@ -18,8 +18,10 @@ package controller
1818
1919import (
2020 "context"
21+ "fmt"
2122 "time"
2223
24+ corev1 "k8s.io/api/core/v1"
2325 apierrors "k8s.io/apimachinery/pkg/api/errors"
2426 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2527 "k8s.io/apimachinery/pkg/runtime"
@@ -28,18 +30,31 @@ import (
2830 logf "sigs.k8s.io/controller-runtime/pkg/log"
2931
3032 impv1alpha1 "github.com/syscode-labs/imp/api/v1alpha1"
33+ "github.com/syscode-labs/imp/internal/runner"
3134)
3235
3336// ImpVMRunnerPoolReconciler reconciles ImpVMRunnerPool objects.
3437type ImpVMRunnerPoolReconciler struct {
3538 client.Client
36- Scheme * runtime.Scheme
39+ Scheme * runtime.Scheme
40+ DriverFactory RunnerDriverFactory
3741}
3842
43+ type runnerQueueDepthReader interface {
44+ QueueDepth (ctx context.Context ) (int , error )
45+ }
46+
47+ type RunnerDriverFactory func (
48+ ctx context.Context ,
49+ c client.Client ,
50+ pool * impv1alpha1.ImpVMRunnerPool ,
51+ ) (runnerQueueDepthReader , error )
52+
3953// +kubebuilder:rbac:groups=imp.dev,resources=impvmrunnerpools,verbs=get;list;watch;create;update;patch;delete
4054// +kubebuilder:rbac:groups=imp.dev,resources=impvmrunnerpools/status,verbs=get;update;patch
4155// +kubebuilder:rbac:groups=imp.dev,resources=impvmtemplates,verbs=get;list;watch
4256// +kubebuilder:rbac:groups=imp.dev,resources=impvms,verbs=get;list;watch;create;delete
57+ // +kubebuilder:rbac:groups=core,resources=secrets,verbs=get
4358
4459func (r * ImpVMRunnerPoolReconciler ) Reconcile (ctx context.Context , req ctrl.Request ) (ctrl.Result , error ) {
4560 log := logf .FromContext (ctx )
@@ -113,7 +128,15 @@ func (r *ImpVMRunnerPoolReconciler) Reconcile(ctx context.Context, req ctrl.Requ
113128 maxConcurrent = pool .Spec .Scaling .MaxConcurrent
114129 }
115130
116- toCreate := minIdle - activeCount
131+ desiredCount := minIdle
132+ queueDepth , err := r .queueDepth (ctx , pool )
133+ if err != nil {
134+ log .Info ("could not fetch runner queue depth; falling back to minIdle" , "pool" , pool .Name , "err" , err )
135+ } else if int32 (queueDepth ) > desiredCount {
136+ desiredCount = int32 (queueDepth )
137+ }
138+
139+ toCreate := desiredCount - activeCount
117140 if toCreate < 0 {
118141 toCreate = 0
119142 }
@@ -153,6 +176,24 @@ func (r *ImpVMRunnerPoolReconciler) Reconcile(ctx context.Context, req ctrl.Requ
153176 return ctrl.Result {RequeueAfter : requeueAfter }, nil
154177}
155178
179+ func (r * ImpVMRunnerPoolReconciler ) queueDepth (ctx context.Context , pool * impv1alpha1.ImpVMRunnerPool ) (int , error ) {
180+ if pool .Spec .JobDetection == nil ||
181+ pool .Spec .JobDetection .Polling == nil ||
182+ ! pool .Spec .JobDetection .Polling .Enabled {
183+ return 0 , nil
184+ }
185+
186+ factory := r .DriverFactory
187+ if factory == nil {
188+ factory = defaultRunnerDriverFactory
189+ }
190+ d , err := factory (ctx , r .Client , pool )
191+ if err != nil {
192+ return 0 , err
193+ }
194+ return d .QueueDepth (ctx )
195+ }
196+
156197func (r * ImpVMRunnerPoolReconciler ) createRunnerVM (ctx context.Context , pool * impv1alpha1.ImpVMRunnerPool , tpl * impv1alpha1.ImpVMTemplate ) error {
157198 classRef := tpl .Spec .ClassRef
158199 vm := & impv1alpha1.ImpVM {
@@ -179,6 +220,14 @@ func (r *ImpVMRunnerPoolReconciler) createRunnerVM(ctx context.Context, pool *im
179220 if tpl .Spec .NetworkGroup != "" {
180221 vm .Spec .NetworkGroup = tpl .Spec .NetworkGroup
181222 }
223+ if pool .Spec .RunnerLayer != "" {
224+ vm .Spec .RunnerLayer = pool .Spec .RunnerLayer
225+ } else if tpl .Spec .RunnerLayer != "" {
226+ vm .Spec .RunnerLayer = tpl .Spec .RunnerLayer
227+ }
228+ if tpl .Spec .CiliumLayer != "" {
229+ vm .Spec .CiliumLayer = tpl .Spec .CiliumLayer
230+ }
182231 if err := ctrl .SetControllerReference (pool , vm , r .Scheme ); err != nil {
183232 return err
184233 }
@@ -191,3 +240,76 @@ func (r *ImpVMRunnerPoolReconciler) SetupWithManager(mgr ctrl.Manager) error {
191240 Owns (& impv1alpha1.ImpVM {}).
192241 Complete (r )
193242}
243+
244+ func defaultRunnerDriverFactory (
245+ ctx context.Context ,
246+ c client.Client ,
247+ pool * impv1alpha1.ImpVMRunnerPool ,
248+ ) (runnerQueueDepthReader , error ) {
249+ var creds corev1.Secret
250+ if err := c .Get (ctx , client.ObjectKey {
251+ Namespace : pool .Namespace ,
252+ Name : pool .Spec .Platform .CredentialsSecret ,
253+ }, & creds ); err != nil {
254+ return nil , err
255+ }
256+ token := pickSecretValue (creds .Data , "token" )
257+ if token == "" {
258+ return nil , fmt .Errorf ("credentials secret %s/%s has no token value" , pool .Namespace , creds .Name )
259+ }
260+
261+ scope , err := platformScope (pool )
262+ if err != nil {
263+ return nil , err
264+ }
265+
266+ switch pool .Spec .Platform .Type {
267+ case "github-actions" :
268+ return runner .NewGitHubDriver (token , scope , nil )
269+ case "forgejo" :
270+ return runner .NewForgejoDriver (token , pool .Spec .Platform .ServerURL , scope , nil )
271+ case "gitlab" :
272+ return runner .NewGitLabDriver (token , pool .Spec .Platform .ServerURL , scope , nil )
273+ default :
274+ return nil , fmt .Errorf ("unsupported platform type %q" , pool .Spec .Platform .Type )
275+ }
276+ }
277+
278+ func platformScope (pool * impv1alpha1.ImpVMRunnerPool ) (string , error ) {
279+ if pool .Spec .Platform .Scope == nil {
280+ return "" , fmt .Errorf ("platform.scope is required" )
281+ }
282+ scope := pool .Spec .Platform .Scope
283+ switch pool .Spec .Platform .Type {
284+ case "github-actions" , "forgejo" :
285+ if scope .Org != "" {
286+ return "org:" + scope .Org , nil
287+ }
288+ if scope .Repo != "" {
289+ return "repo:" + scope .Repo , nil
290+ }
291+ case "gitlab" :
292+ if scope .Org != "" {
293+ return "group:" + scope .Org , nil
294+ }
295+ if scope .Repo != "" {
296+ return "project:" + scope .Repo , nil
297+ }
298+ }
299+ return "" , fmt .Errorf ("invalid platform.scope for type %q" , pool .Spec .Platform .Type )
300+ }
301+
302+ func pickSecretValue (m map [string ][]byte , preferredKey string ) string {
303+ if len (m ) == 0 {
304+ return ""
305+ }
306+ if v , ok := m [preferredKey ]; ok && len (v ) > 0 {
307+ return string (v )
308+ }
309+ for _ , v := range m {
310+ if len (v ) > 0 {
311+ return string (v )
312+ }
313+ }
314+ return ""
315+ }
0 commit comments