Skip to content

Commit ff51438

Browse files
fix(OPENFRAM-002): bootstrap.Service is an exported empty struct with no constructor-injected dependencies, diverging from ClusterService pattern used elsewhere
1 parent 7a252f3 commit ff51438

1 file changed

Lines changed: 22 additions & 4 deletions

File tree

internal/bootstrap/service.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,23 @@ import (
2222
const defaultClusterName = "openframe-dev"
2323

2424
// Service provides bootstrap functionality
25-
type Service struct{}
25+
type Service struct {
26+
clusterService cluster.ServiceInterface
27+
}
2628

2729
// NewService creates a new bootstrap service
2830
func NewService() *Service {
2931
return &Service{}
3032
}
3133

34+
// NewServiceWithDependencies creates a new bootstrap service with an
35+
// injected cluster service, following the constructor-injection pattern
36+
// used by ClusterService. When clusterService is nil, it is lazily
37+
// constructed with a real command executor at the point of use.
38+
func NewServiceWithDependencies(clusterService cluster.ServiceInterface) *Service {
39+
return &Service{clusterService: clusterService}
40+
}
41+
3242
// Execute handles the bootstrap command execution
3343
func (s *Service) Execute(cmd *cobra.Command, args []string) error {
3444
// Get verbose flag - first check local flag, then root command
@@ -166,7 +176,7 @@ func (s *Service) createClusterSuppressed(ctx context.Context, clusterName strin
166176

167177
// installChart installs charts on the created cluster
168178
func (s *Service) installChart(ctx context.Context, clusterName string, nonInteractive, verbose bool, kubeConfig *rest.Config) error {
169-
return chartServices.InstallChartsWithConfigContext(ctx, bootstrapInstallRequest(clusterName, nonInteractive, verbose, kubeConfig))
179+
return chartServices.InstallChartsWithConfigContext(ctx, s.bootstrapInstallRequest(clusterName, nonInteractive, verbose, kubeConfig))
170180
}
171181

172182
// bootstrapInstallRequest builds the chart-install request for the cluster the
@@ -176,7 +186,14 @@ func (s *Service) installChart(ctx context.Context, clusterName string, nonInter
176186
// "install OpenFrame chart on ”?" and every helm call ran WITHOUT
177187
// --kube-context, silently targeting the kubeconfig's current context instead
178188
// of the cluster the native client was pointed at.
179-
func bootstrapInstallRequest(clusterName string, nonInteractive, verbose bool, kubeConfig *rest.Config) utilTypes.InstallationRequest {
189+
func (s *Service) bootstrapInstallRequest(clusterName string, nonInteractive, verbose bool, kubeConfig *rest.Config) utilTypes.InstallationRequest {
190+
// Prefer the injected cluster service (constructor injection); fall back
191+
// to constructing one with a real command executor if the Service was
192+
// created via the zero-dependency NewService() constructor.
193+
clusterAccess := s.clusterService
194+
if clusterAccess == nil {
195+
clusterAccess = cluster.NewClusterService(executor.NewRealCommandExecutor(false, verbose))
196+
}
180197
return utilTypes.InstallationRequest{
181198
Args: []string{clusterName},
182199
Force: false,
@@ -196,6 +213,7 @@ func bootstrapInstallRequest(clusterName string, nonInteractive, verbose bool, k
196213
KubeContext: "k3d-" + clusterName,
197214
// Inject cluster access from the orchestrator (composition root) so the
198215
// app subsystem stays isolated from cluster-creation code (req 18/19).
199-
ClusterAccess: cluster.NewClusterService(executor.NewRealCommandExecutor(false, verbose)),
216+
ClusterAccess: clusterAccess,
200217
}
201218
}
219+

0 commit comments

Comments
 (0)