Dodo APP: infrastructure to deploy app by pusing to Git repo

Change-Id: I4034c6893255581b014ddb207c844261cb34202b
diff --git a/core/installer/app_manager.go b/core/installer/app_manager.go
index 44e39e6..ae18ff8 100644
--- a/core/installer/app_manager.go
+++ b/core/installer/app_manager.go
@@ -178,7 +178,7 @@
 }
 
 // TODO(gio): rename to CommitApp
-func InstallApp(
+func installApp(
 	repo soft.RepoIO,
 	appDir string,
 	name string,
@@ -242,7 +242,11 @@
 }
 
 // TODO(gio): commit instanceId -> appDir mapping as well
-func (m *AppManager) Install(app EnvApp, instanceId string, appDir string, namespace string, values map[string]any) (ReleaseResources, error) {
+func (m *AppManager) Install(app EnvApp, instanceId string, appDir string, namespace string, values map[string]any, opts ...InstallOption) (ReleaseResources, error) {
+	o := &installOptions{}
+	for _, i := range opts {
+		i(o)
+	}
 	appDir = filepath.Clean(appDir)
 	if err := m.repoIO.Pull(); err != nil {
 		return ReleaseResources{}, err
@@ -250,9 +254,15 @@
 	if err := m.nsCreator.Create(namespace); err != nil {
 		return ReleaseResources{}, err
 	}
-	env, err := m.Config()
-	if err != nil {
-		return ReleaseResources{}, err
+	var env EnvConfig
+	if o.Env != nil {
+		env = *o.Env
+	} else {
+		var err error
+		env, err = m.Config()
+		if err != nil {
+			return ReleaseResources{}, err
+		}
 	}
 	release := Release{
 		AppInstanceId: instanceId,
@@ -264,7 +274,12 @@
 	if err != nil {
 		return ReleaseResources{}, err
 	}
-	if _, err := InstallApp(m.repoIO, appDir, rendered.Name, rendered.Config, rendered.Ports, rendered.Resources, rendered.Data); err != nil {
+	dopts := []soft.DoOption{}
+	if o.Branch != "" {
+		dopts = append(dopts, soft.WithForce())
+		dopts = append(dopts, soft.WithCommitToBranch(o.Branch))
+	}
+	if _, err := installApp(m.repoIO, appDir, rendered.Name, rendered.Config, rendered.Ports, rendered.Resources, rendered.Data, dopts...); err != nil {
 		return ReleaseResources{}, err
 	}
 	// TODO(gio): add ingress-nginx to release resources
@@ -278,6 +293,7 @@
 
 type helmRelease struct {
 	Metadata Resource `json:"metadata"`
+	Kind     string   `json:"kind"`
 	Status   struct {
 		Conditions []struct {
 			Type   string `json:"type"`
@@ -293,7 +309,9 @@
 		if err := yaml.Unmarshal(contents, &h); err != nil {
 			panic(err) // TODO(gio): handle
 		}
-		ret = append(ret, h.Metadata)
+		if h.Kind == "HelmRelease" {
+			ret = append(ret, h.Metadata)
+		}
 	}
 	return ret
 }
@@ -322,7 +340,7 @@
 	if err != nil {
 		return ReleaseResources{}, err
 	}
-	return InstallApp(m.repoIO, instanceDir, rendered.Name, rendered.Config, rendered.Ports, rendered.Resources, rendered.Data, opts...)
+	return installApp(m.repoIO, instanceDir, rendered.Name, rendered.Config, rendered.Ports, rendered.Resources, rendered.Data, opts...)
 }
 
 func (m *AppManager) Remove(instanceId string) error {
@@ -368,6 +386,25 @@
 	nsCreator NamespaceCreator
 }
 
+type installOptions struct {
+	Env    *EnvConfig
+	Branch string
+}
+
+type InstallOption func(*installOptions)
+
+func WithConfig(env *EnvConfig) InstallOption {
+	return func(o *installOptions) {
+		o.Env = env
+	}
+}
+
+func WithBranch(branch string) InstallOption {
+	return func(o *installOptions) {
+		o.Branch = branch
+	}
+}
+
 func NewInfraAppManager(repoIO soft.RepoIO, nsCreator NamespaceCreator) (*InfraAppManager, error) {
 	return &InfraAppManager{
 		repoIO,
@@ -432,7 +469,7 @@
 	if err != nil {
 		return ReleaseResources{}, err
 	}
-	return InstallApp(m.repoIO, appDir, rendered.Name, rendered.Config, rendered.Ports, rendered.Resources, rendered.Data)
+	return installApp(m.repoIO, appDir, rendered.Name, rendered.Config, rendered.Ports, rendered.Resources, rendered.Data)
 }
 
 func (m *InfraAppManager) Update(app InfraApp, instanceId string, values map[string]any, opts ...soft.DoOption) (ReleaseResources, error) {
@@ -459,5 +496,5 @@
 	if err != nil {
 		return ReleaseResources{}, err
 	}
-	return InstallApp(m.repoIO, instanceDir, rendered.Name, rendered.Config, rendered.Ports, rendered.Resources, rendered.Data, opts...)
+	return installApp(m.repoIO, instanceDir, rendered.Name, rendered.Config, rendered.Ports, rendered.Resources, rendered.Data, opts...)
 }