@@ -3,83 +3,167 @@ package cloudproviderkindinstaller
33import (
44 "context"
55 "fmt"
6- "time"
6+ "os"
7+ "path/filepath"
8+ "sync"
79
8- "github.com/devantler-tech/ksail/v5/pkg/client/helm "
10+ cpkcmd "sigs.k8s.io/cloud-provider-kind/cmd "
911)
1012
11- // CloudProviderKINDInstaller installs or upgrades Cloud Provider KIND.
13+ var (
14+ // Global state for the cloud-provider-kind controller
15+ globalController * cloudProviderController
16+ globalMu sync.Mutex
17+ )
18+
19+ // cloudProviderController wraps the cloud-provider-kind controller with lifecycle management.
20+ type cloudProviderController struct {
21+ cancel context.CancelFunc
22+ refCount int
23+ done chan struct {}
24+ }
25+
26+ // CloudProviderKINDInstaller manages the cloud-provider-kind controller as a background goroutine.
1227type CloudProviderKINDInstaller struct {
13- kubeconfig string
14- context string
15- timeout time.Duration
16- client helm.Interface
28+ // No fields needed - controller is managed globally
1729}
1830
1931// NewCloudProviderKINDInstaller creates a new Cloud Provider KIND installer instance.
20- func NewCloudProviderKINDInstaller (
21- client helm.Interface ,
22- kubeconfig , context string ,
23- timeout time.Duration ,
24- ) * CloudProviderKINDInstaller {
25- return & CloudProviderKINDInstaller {
26- client : client ,
27- kubeconfig : kubeconfig ,
28- context : context ,
29- timeout : timeout ,
30- }
32+ func NewCloudProviderKINDInstaller () * CloudProviderKINDInstaller {
33+ return & CloudProviderKINDInstaller {}
3134}
3235
33- // Install installs or upgrades Cloud Provider KIND via its Helm chart.
36+ // Install starts the cloud-provider-kind controller if not already running.
37+ // The controller runs as a background goroutine and monitors all KIND clusters.
38+ // Multiple calls to Install() will increment a reference count, ensuring the
39+ // controller stays running as long as at least one cluster needs it.
3440func (c * CloudProviderKINDInstaller ) Install (ctx context.Context ) error {
35- err := c .helmInstallOrUpgradeCloudProviderKIND (ctx )
36- if err != nil {
37- return fmt .Errorf ("failed to install cloud-provider-kind: %w" , err )
41+ globalMu .Lock ()
42+ defer globalMu .Unlock ()
43+
44+ // Check if another ksail process is managing cloud-provider-kind
45+ if isRunningExternally () {
46+ // Another process is managing it, just increment local reference
47+ if globalController == nil {
48+ // Initialize local state but don't start the controller
49+ globalController = & cloudProviderController {
50+ refCount : 1 ,
51+ }
52+ } else {
53+ globalController .refCount ++
54+ }
55+
56+ return nil
57+ }
58+
59+ // If controller is already running in this process, increment reference count
60+ if globalController != nil && globalController .done != nil {
61+ globalController .refCount ++
62+
63+ return nil
64+ }
65+
66+ // Start the cloud-provider-kind controller using the cmd package
67+ cmd := cpkcmd .NewCommand ()
68+
69+ // Create a cancelable context for the controller
70+ ctrlCtx , cancel := context .WithCancel (context .Background ())
71+ done := make (chan struct {})
72+
73+ // Start controller in background goroutine
74+ go func () {
75+ defer close (done )
76+ // Run the command - this will block until context is canceled
77+ _ = cmd .ExecuteContext (ctrlCtx )
78+ }()
79+
80+ // Mark as running and create lock file
81+ if err := createLockFile (); err != nil {
82+ cancel ()
83+ <- done // Wait for goroutine to finish
84+
85+ return fmt .Errorf ("failed to create lock file: %w" , err )
86+ }
87+
88+ globalController = & cloudProviderController {
89+ cancel : cancel ,
90+ refCount : 1 ,
91+ done : done ,
3892 }
3993
4094 return nil
4195}
4296
43- // Uninstall uninstalls Cloud Provider KIND via Helm.
97+ // Uninstall decrements the reference count and stops the cloud-provider-kind controller
98+ // if no more clusters are using it.
4499func (c * CloudProviderKINDInstaller ) Uninstall (ctx context.Context ) error {
45- err := c .client .UninstallRelease (ctx , "cloud-provider-kind" , "kube-system" )
46- if err != nil {
47- return fmt .Errorf ("failed to uninstall cloud-provider-kind: %w" , err )
100+ globalMu .Lock ()
101+ defer globalMu .Unlock ()
102+
103+ if globalController == nil {
104+ return nil // Nothing to uninstall
105+ }
106+
107+ globalController .refCount --
108+
109+ // Only stop the controller if reference count reaches zero
110+ if globalController .refCount <= 0 {
111+ if globalController .cancel != nil {
112+ globalController .cancel ()
113+
114+ // Wait for the goroutine to finish
115+ if globalController .done != nil {
116+ <- globalController .done
117+ }
118+ }
119+
120+ // Remove lock file
121+ if err := removeLockFile (); err != nil {
122+ // Log error but don't fail uninstall
123+ fmt .Fprintf (os .Stderr , "Warning: failed to remove lock file: %v\n " , err )
124+ }
125+
126+ globalController = nil
48127 }
49128
50129 return nil
51130}
52131
53- // --- internals ---
132+ // --- Lock file management ---
54133
55- func (c * CloudProviderKINDInstaller ) helmInstallOrUpgradeCloudProviderKIND (
56- ctx context.Context ,
57- ) error {
58- repoEntry := & helm.RepositoryEntry {
59- Name : "cloud-provider-kind" ,
60- URL : "https://kubernetes-sigs.github.io/cloud-provider-kind" ,
61- }
134+ const lockFileName = "cloud-provider-kind.lock"
62135
63- addRepoErr := c .client .AddRepository (ctx , repoEntry , c .timeout )
64- if addRepoErr != nil {
65- return fmt .Errorf ("failed to add cloud-provider-kind repository: %w" , addRepoErr )
66- }
136+ func getLockFilePath () string {
137+ // Use a temporary directory for the lock file
138+ tmpDir := os .TempDir ()
67139
68- spec := & helm.ChartSpec {
69- ReleaseName : "cloud-provider-kind" ,
70- ChartName : "cloud-provider-kind/cloud-provider-kind" ,
71- Namespace : "kube-system" ,
72- RepoURL : "https://kubernetes-sigs.github.io/cloud-provider-kind" ,
73- Atomic : true ,
74- Wait : true ,
75- WaitForJobs : true ,
76- Timeout : c .timeout ,
77- }
140+ return filepath .Join (tmpDir , lockFileName )
141+ }
78142
79- _ , err := c .client .InstallOrUpgradeChart (ctx , spec )
80- if err != nil {
81- return fmt .Errorf ("failed to install cloud-provider-kind chart: %w" , err )
143+ func isRunningExternally () bool {
144+ lockPath := getLockFilePath ()
145+ if _ , err := os .Stat (lockPath ); err == nil {
146+ // Lock file exists - check if the process is still running
147+ // For now, we assume if the file exists, it's running
148+ // In production, we'd want to validate the PID
149+ return true
82150 }
83151
84- return nil
152+ return false
153+ }
154+
155+ func createLockFile () error {
156+ lockPath := getLockFilePath ()
157+
158+ // Create lock file with current process ID
159+ pid := os .Getpid ()
160+ content := fmt .Sprintf ("%d\n " , pid )
161+
162+ return os .WriteFile (lockPath , []byte (content ), 0644 )
163+ }
164+
165+ func removeLockFile () error {
166+ lockPath := getLockFilePath ()
167+
168+ return os .Remove (lockPath )
85169}
0 commit comments