installer: env and app manager
diff --git a/core/installer/cmd/apps.go b/core/installer/cmd/apps.go
index eb49736..b96ac6f 100644
--- a/core/installer/cmd/apps.go
+++ b/core/installer/cmd/apps.go
@@ -1,19 +1,30 @@
package main
import (
+ "fmt"
"io/ioutil"
+ "net"
"os"
- "path/filepath"
+ "time"
"github.com/giolekva/pcloud/core/installer"
+ "github.com/go-git/go-billy/v5/memfs"
+ "github.com/go-git/go-git/v5"
+ "github.com/go-git/go-git/v5/plumbing/object"
+ gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
+ "github.com/go-git/go-git/v5/storage/memory"
"github.com/spf13/cobra"
+ "golang.org/x/crypto/ssh"
"sigs.k8s.io/yaml"
)
+const appDirName = "apps"
+
var installFlags struct {
- config string
- appName string
- outputDir string
+ sshKey string
+ config string
+ appName string
+ repoAddr string
}
func installCmd() *cobra.Command {
@@ -22,6 +33,12 @@
RunE: installCmdRun,
}
cmd.Flags().StringVar(
+ &installFlags.sshKey,
+ "ssh-key",
+ "",
+ "",
+ )
+ cmd.Flags().StringVar(
&installFlags.config,
"config",
"",
@@ -34,36 +51,92 @@
"",
)
cmd.Flags().StringVar(
- &installFlags.outputDir,
- "output-dir",
+ &installFlags.repoAddr,
+ "repo-addr",
"",
"",
)
return cmd
}
+type inMemoryAppRepository struct {
+ apps []installer.App
+}
+
+func NewInMemoryAppRepository(apps []installer.App) installer.AppRepository {
+ return &inMemoryAppRepository{
+ apps,
+ }
+}
+
+func (r inMemoryAppRepository) Find(name string) (*installer.App, error) {
+ for _, a := range r.apps {
+ if a.Name == name {
+ return &a, nil
+ }
+ }
+ return nil, fmt.Errorf("Application not found: %s", name)
+}
+
func installCmdRun(cmd *cobra.Command, args []string) error {
cfg, err := readConfig(installFlags.config)
if err != nil {
return err
}
- apps := installer.CreateAllApps()
- for _, a := range apps {
- if a.Name == installFlags.appName {
- for _, t := range a.Templates {
- out, err := os.Create(filepath.Join(installFlags.outputDir, t.Name()))
- if err != nil {
- return err
- }
- defer out.Close()
- if err := t.Execute(out, cfg); err != nil {
- return err
- }
- }
- break
- }
+ sshKey, err := os.ReadFile(installFlags.sshKey)
+ if err != nil {
+ return err
}
- return nil
+ signer, err := ssh.ParsePrivateKey(sshKey)
+ if err != nil {
+ return err
+ }
+ repo, err := cloneRepo(installFlags.repoAddr, signer)
+ if err != nil {
+ return err
+ }
+ wt, err := repo.Worktree()
+ if err != nil {
+ return err
+ }
+ appRoot, err := wt.Filesystem.Chroot(appDirName)
+ if err != nil {
+ return err
+ }
+ m, err := installer.NewAppManager(
+ appRoot,
+ cfg,
+ NewInMemoryAppRepository(installer.CreateAllApps()),
+ )
+ if err != nil {
+ return err
+ }
+ if err := m.Install(installFlags.appName); err != nil {
+ return err
+ }
+ if st, err := wt.Status(); err != nil {
+ return err
+ } else {
+ fmt.Printf("%+v\n", st)
+ }
+ wt.AddGlob("*")
+ if st, err := wt.Status(); err != nil {
+ return err
+ } else {
+ fmt.Printf("%+v\n", st)
+ }
+ if _, err := wt.Commit(fmt.Sprintf("install: %s", installFlags.appName), &git.CommitOptions{
+ Author: &object.Signature{
+ Name: "pcloud-appmanager",
+ When: time.Now(),
+ },
+ }); err != nil {
+ return err
+ }
+ return repo.Push(&git.PushOptions{
+ RemoteName: "origin",
+ Auth: auth(signer),
+ })
}
func readConfig(config string) (installer.Config, error) {
@@ -75,3 +148,25 @@
err = yaml.UnmarshalStrict(inp, &cfg)
return cfg, err
}
+
+func cloneRepo(address string, signer ssh.Signer) (*git.Repository, error) {
+ return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
+ URL: address,
+ Auth: auth(signer),
+ RemoteName: "origin",
+ InsecureSkipTLS: true,
+ })
+}
+
+func auth(signer ssh.Signer) *gitssh.PublicKeys {
+ return &gitssh.PublicKeys{
+ Signer: signer,
+ HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
+ HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
+ // TODO(giolekva): verify server public key
+ // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
+ return nil
+ },
+ },
+ }
+}
diff --git a/core/installer/cmd/bootstrap.go b/core/installer/cmd/bootstrap.go
index 151dd90..a11cfac 100644
--- a/core/installer/cmd/bootstrap.go
+++ b/core/installer/cmd/bootstrap.go
@@ -1,19 +1,18 @@
+// TODO
+// * ns pcloud not found
+
package main
import (
"context"
- "crypto/ed25519"
- "crypto/rand"
- "crypto/x509"
_ "embed"
- "encoding/pem"
"fmt"
- "golang.org/x/crypto/ssh"
"log"
"os"
"path/filepath"
"time"
+ "github.com/giolekva/pcloud/core/installer"
"github.com/giolekva/pcloud/core/installer/soft"
"github.com/spf13/cobra"
"helm.sh/helm/v3/pkg/action"
@@ -22,9 +21,12 @@
)
var bootstrapFlags struct {
- chartsDir string
- adminPubKey string
- adminPrivKey string
+ chartsDir string
+ adminPubKey string
+ adminPrivKey string
+ storageDir string
+ volumeDefaultReplicaCount int
+ softServeIP string
}
func bootstrapCmd() *cobra.Command {
@@ -50,52 +52,229 @@
"",
"",
)
+ cmd.Flags().StringVar(
+ &bootstrapFlags.storageDir,
+ "storage-dir",
+ "",
+ "",
+ )
+ cmd.Flags().IntVar(
+ &bootstrapFlags.volumeDefaultReplicaCount,
+ "volume-default-replica-count",
+ 3,
+ "",
+ )
+ cmd.Flags().StringVar(
+ &bootstrapFlags.softServeIP,
+ "soft-serve-ip",
+ "",
+ "",
+ )
return cmd
}
func bootstrapCmdRun(cmd *cobra.Command, args []string) error {
- adminPubKey, adminPrivKey, err := readAdminKeys()
+ adminPubKey, adminPrivKey, err := readAdminKeys()
+ if err != nil {
+ return err
+ }
+ fluxPub, fluxPriv, err := installer.GenerateSSHKeys()
+ if err != nil {
+ return err
+ }
+ softServePub, softServePriv, err := installer.GenerateSSHKeys()
+ if err != nil {
+ return err
+ }
+ if err := installMetallbNamespace(); err != nil {
+ return err
+ }
+ if err := installMetallb(); err != nil {
+ return err
+ }
+ time.Sleep(3 * time.Minute)
+ if err := installMetallbConfig(); err != nil {
+ return err
+ }
+ if err := installLonghorn(); err != nil {
+ return err
+ }
+ if err := installSoftServe(softServePub, softServePriv, string(adminPubKey)); err != nil {
+ return err
+ }
+ time.Sleep(30 * time.Second)
+ ss, err := soft.NewClient(bootstrapFlags.softServeIP, 22, adminPrivKey, log.Default())
+ if err != nil {
+ return err
+ }
+ if err := ss.AddUser("flux", fluxPub); err != nil {
+ return err
+ }
+ if err := ss.MakeUserAdmin("flux"); err != nil {
+ return err
+ }
+ fmt.Println("Creating /pcloud repo")
+ if err := ss.AddRepository("pcloud", "# PCloud Systems\n"); err != nil {
+ return err
+ }
+ fmt.Println("Installing Flux")
+ if err := installFlux("ssh://soft-serve.pcloud.svc.cluster.local:22/pcloud", "soft-serve.pcloud.svc.cluster.local", softServePub, fluxPriv); err != nil {
+ return err
+ }
+ // TODO(giolekva): everything below must be installed using Flux
+ if err := installIngressPublic(); err != nil {
+ return err
+ }
+ if err := installCertManager(); err != nil {
+ return err
+ }
+ if err := installCertManagerWebhookGandi(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func installMetallbNamespace() error {
+ fmt.Println("Installing metallb namespace")
+ // config, err := createActionConfig("default")
+ config, err := createActionConfig("pcloud")
if err != nil {
return err
}
- fluxPub, fluxPriv, err := generateSSHKeys()
+ chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "namespace"))
if err != nil {
return err
}
- softServePub, softServePriv, err := generateSSHKeys()
+ values := map[string]interface{}{
+ // "namespace": "pcloud-metallb",
+ "namespace": "metallb-system",
+ "labels": []string{
+ "pod-security.kubernetes.io/audit: privileged",
+ "pod-security.kubernetes.io/enforce: privileged",
+ "pod-security.kubernetes.io/warn: privileged",
+ },
+ }
+ installer := action.NewInstall(config)
+ installer.Namespace = "pcloud"
+ installer.ReleaseName = "metallb-ns"
+ installer.Wait = true
+ if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
+ return err
+ }
+ return nil
+}
+
+func installMetallb() error {
+ fmt.Println("Installing metallb")
+ // config, err := createActionConfig("default")
+ config, err := createActionConfig("metallb-system")
if err != nil {
return err
}
- fmt.Println("Installing SoftServe")
- if err := installSoftServe(softServePub, softServePriv, string(adminPubKey)); err != nil {
- return err
- }
- time.Sleep(30 * time.Second)
- ss, err := soft.NewClient("192.168.0.208", 22, adminPrivKey, log.Default())
+ chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "metallb"))
if err != nil {
return err
}
- if err := ss.UpdateConfig(
- soft.DefaultConfig([]string{string(adminPubKey), fluxPub}),
- "set admin keys"); err != nil {
+ values := map[string]interface{}{ // TODO(giolekva): add loadBalancerClass?
+ "controller": map[string]interface{}{
+ "image": map[string]interface{}{
+ "repository": "quay.io/metallb/controller",
+ "tag": "v0.13.9",
+ "pullPolicy": "IfNotPresent",
+ },
+ "logLevel": "info",
+ },
+ "speaker": map[string]interface{}{
+ "image": map[string]interface{}{
+ "repository": "quay.io/metallb/speaker",
+ "tag": "v0.13.9",
+ "pullPolicy": "IfNotPresent",
+ },
+ "logLevel": "info",
+ },
+ }
+ installer := action.NewInstall(config)
+ installer.Namespace = "metallb-system" // "pcloud-metallb"
+ installer.CreateNamespace = true
+ installer.ReleaseName = "metallb"
+ installer.IncludeCRDs = true
+ // installer.Wait = true
+ installer.Timeout = 20 * time.Minute
+ if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
return err
}
- if err := ss.ReloadConfig(); err != nil {
+ return nil
+}
+
+func installMetallbConfig() error {
+ fmt.Println("Installing metallb-config")
+ // config, err := createActionConfig("default")
+ config, err := createActionConfig("metallb-system")
+ if err != nil {
return err
}
- fmt.Println("Creating /pcloud repo")
- if err := ss.AddRepository("pcloud", "# PCloud Systems\n"); err != nil {
+ chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "metallb-config"))
+ if err != nil {
return err
}
- fmt.Println("Installing Flux")
- if err := installFlux("ssh://soft-serve.pcloud.svc.cluster.local:22/pcloud", "soft-serve.pcloud.svc.cluster.local", softServePub, fluxPriv); err != nil {
+ values := map[string]interface{}{
+ "from": "192.168.0.210",
+ "to": "192.168.0.240",
+ }
+ installer := action.NewInstall(config)
+ installer.Namespace = "metallb-system" // "pcloud-metallb"
+ installer.CreateNamespace = true
+ installer.ReleaseName = "metallb-cfg"
+ installer.Wait = true
+ installer.Timeout = 20 * time.Minute
+ if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
+ return err
+ }
+ return nil
+}
+
+func installLonghorn() error {
+ fmt.Println("Installing Longhorn")
+ config, err := createActionConfig("pcloud")
+ if err != nil {
+ return err
+ }
+ chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "longhorn"))
+ if err != nil {
+ return err
+ }
+ values := map[string]interface{}{
+ "defaultSettings": map[string]interface{}{
+ "defaultDataPath": bootstrapFlags.storageDir,
+ },
+ "persistence": map[string]interface{}{
+ "defaultClassReplicaCount": bootstrapFlags.volumeDefaultReplicaCount,
+ },
+ "service": map[string]interface{}{
+ "ui": map[string]interface{}{
+ "type": "LoadBalancer",
+ },
+ },
+ "ingress": map[string]interface{}{
+ "enabled": false,
+ },
+ }
+ installer := action.NewInstall(config)
+ installer.Namespace = "longhorn-system"
+ installer.CreateNamespace = true
+ installer.ReleaseName = "longhorn"
+ installer.Wait = true
+ installer.Timeout = 20 * time.Minute
+ if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
return err
}
return nil
}
func installSoftServe(pubKey, privKey, adminKey string) error {
- config, err := createActionConfig()
+ fmt.Println("Installing SoftServe")
+ config, err := createActionConfig("pcloud")
if err != nil {
return err
}
@@ -107,13 +286,14 @@
"privateKey": privKey,
"publicKey": pubKey,
"adminKey": adminKey,
+ "reservedIP": bootstrapFlags.softServeIP,
}
installer := action.NewInstall(config)
installer.Namespace = "pcloud"
installer.CreateNamespace = true
installer.ReleaseName = "soft-serve"
installer.Wait = true
- installer.Timeout = 5 * time.Minute
+ installer.Timeout = 20 * time.Minute
if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
return err
}
@@ -121,7 +301,7 @@
}
func installFlux(repoAddr, repoHost, repoHostPubKey, privateKey string) error {
- config, err := createActionConfig()
+ config, err := createActionConfig("pcloud")
if err != nil {
return err
}
@@ -141,18 +321,123 @@
installer.ReleaseName = "flux"
installer.Wait = true
installer.WaitForJobs = true
- installer.Timeout = 5 * time.Minute
+ installer.Timeout = 20 * time.Minute
if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
return err
}
return nil
}
-func createActionConfig() (*action.Configuration, error) {
+func installIngressPublic() error {
+ config, err := createActionConfig("pcloud")
+ if err != nil {
+ return err
+ }
+ chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "ingress-nginx"))
+ if err != nil {
+ return err
+ }
+ values := map[string]interface{}{
+ "fullnameOverride": "pcloud-ingress-public",
+ "controller": map[string]interface{}{
+ "service": map[string]interface{}{
+ "type": "LoadBalancer",
+ },
+ "ingressClassByName": true,
+ "ingressClassResource": map[string]interface{}{
+ "name": "pcloud-ingress-public",
+ "enabled": true,
+ "default": false,
+ "controllerValue": "k8s.io/pcloud-ingress-public",
+ },
+ "config": map[string]interface{}{
+ "proxy-body-size": "100M",
+ },
+ },
+ }
+ installer := action.NewInstall(config)
+ installer.Namespace = "pcloud-ingress-public"
+ installer.CreateNamespace = true
+ installer.ReleaseName = "ingress-public"
+ installer.Wait = true
+ installer.WaitForJobs = true
+ installer.Timeout = 20 * time.Minute
+ if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
+ return err
+ }
+ return nil
+}
+
+func installCertManager() error {
+ config, err := createActionConfig("pcloud-cert-manager")
+ if err != nil {
+ return err
+ }
+ chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "cert-manager"))
+ if err != nil {
+ return err
+ }
+ values := map[string]interface{}{
+ "fullnameOverride": "pcloud-cert-manager",
+ "installCRDs": true,
+ "image": map[string]interface{}{
+ "tag": "v1.11.1",
+ "pullPolicy": "IfNotPresent",
+ },
+ }
+ installer := action.NewInstall(config)
+ installer.Namespace = "pcloud-cert-manager"
+ installer.CreateNamespace = true
+ installer.ReleaseName = "cert-manager"
+ installer.Wait = true
+ installer.WaitForJobs = true
+ installer.Timeout = 20 * time.Minute
+ if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
+ return err
+ }
+ return nil
+}
+
+func installCertManagerWebhookGandi() error {
+ config, err := createActionConfig("pcloud-cert-manager")
+ if err != nil {
+ return err
+ }
+ chart, err := loader.Load(filepath.Join(bootstrapFlags.chartsDir, "cert-manager-webhook-gandi"))
+ if err != nil {
+ return err
+ }
+ values := map[string]interface{}{
+ "fullnameOverride": "pcloud-cert-manager-webhook-gandi",
+ "certManager": map[string]interface{}{
+ "namespace": "pcloud-cert-manager",
+ "serviceAccountName": "pcloud-cert-manager",
+ },
+ "image": map[string]interface{}{
+ "repository": "giolekva/cert-manager-webhook-gandi",
+ "tag": "v0.2.0",
+ "pullPolicy": "IfNotPresent",
+ },
+ "logLevel": 2,
+ }
+ installer := action.NewInstall(config)
+ installer.Namespace = "pcloud-cert-manager"
+ installer.CreateNamespace = false
+ installer.ReleaseName = "cert-manager-webhook-gandi"
+ installer.Wait = true
+ installer.WaitForJobs = true
+ installer.Timeout = 20 * time.Minute
+ if _, err := installer.RunWithContext(context.TODO(), chart, values); err != nil {
+ return err
+ }
+ return nil
+}
+
+func createActionConfig(namespace string) (*action.Configuration, error) {
config := new(action.Configuration)
if err := config.Init(
- kube.GetConfig(rootFlags.kubeConfig, "", ""),
- "pcloud",
+ kube.GetConfig(rootFlags.kubeConfig, "", namespace),
+ namespace,
"",
func(fmtString string, args ...interface{}) {
fmt.Printf(fmtString, args...)
@@ -164,28 +449,6 @@
return config, nil
}
-func generateSSHKeys() (string, string, error) {
- pub, priv, err := ed25519.GenerateKey(rand.Reader)
- if err != nil {
- return "", "", err
- }
- privEnc, err := x509.MarshalPKCS8PrivateKey(priv)
- if err != nil {
- return "", "", err
- }
- privPem := pem.EncodeToMemory(
- &pem.Block{
- Type: "PRIVATE KEY",
- Bytes: privEnc,
- },
- )
- pubKey, err := ssh.NewPublicKey(pub)
- if err != nil {
- return "", "", err
- }
- return string(ssh.MarshalAuthorizedKey(pubKey)), string(privPem), nil
-}
-
func readAdminKeys() ([]byte, []byte, error) {
pubKey, err := os.ReadFile(bootstrapFlags.adminPubKey)
if err != nil {
diff --git a/core/installer/cmd/env-tmpl/config-kustomization.yaml b/core/installer/cmd/env-tmpl/config-kustomization.yaml
new file mode 100644
index 0000000..d76bf0f
--- /dev/null
+++ b/core/installer/cmd/env-tmpl/config-kustomization.yaml
@@ -0,0 +1,13 @@
+apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
+kind: Kustomization
+metadata:
+ name: {{ .Name }}
+ namespace: {{ .Name }}
+spec:
+ interval: 1m
+ path: "./"
+ sourceRef:
+ kind: GitRepository
+ name: {{ .Name }}
+ namespace: {{ .Name }}
+ prune: true
diff --git a/core/installer/cmd/env-tmpl/config-secret.yaml b/core/installer/cmd/env-tmpl/config-secret.yaml
new file mode 100644
index 0000000..3ea515b
--- /dev/null
+++ b/core/installer/cmd/env-tmpl/config-secret.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+data:
+ identity: {{ .PrivateKey }}
+ identity.pub: {{ .PublicKey }}
+ known_hosts: {{ .KnownHosts }}
+kind: Secret
+metadata:
+ name: {{ .Name }}
+ namespace: {{ .Name }}
+type: Opaque
diff --git a/core/installer/cmd/env-tmpl/config-source.yaml b/core/installer/cmd/env-tmpl/config-source.yaml
new file mode 100644
index 0000000..113a0b4
--- /dev/null
+++ b/core/installer/cmd/env-tmpl/config-source.yaml
@@ -0,0 +1,14 @@
+apiVersion: source.toolkit.fluxcd.io/v1beta2
+kind: GitRepository
+metadata:
+ name: {{ .Name }}
+ namespace: {{ .Name }}
+spec:
+ gitImplementation: go-git
+ interval: 1m0s
+ ref:
+ branch: master
+ secretRef:
+ name: {{ .Name }}
+ timeout: 60s
+ url: ssh://{{ .GitHost }}/{{ .Name }}
diff --git a/core/installer/cmd/env-tmpl/kustomization.yaml b/core/installer/cmd/env-tmpl/kustomization.yaml
new file mode 100644
index 0000000..8ac663b
--- /dev/null
+++ b/core/installer/cmd/env-tmpl/kustomization.yaml
@@ -0,0 +1,6 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+resources:
+ - config-secret.yaml
+ - config-source.yaml
+ - config-kustomization.yaml
diff --git a/core/installer/cmd/env.go b/core/installer/cmd/env.go
index 6f24d19..1ae0888 100644
--- a/core/installer/cmd/env.go
+++ b/core/installer/cmd/env.go
@@ -1,14 +1,30 @@
+// TODO
+// * flux -n lekva create source git pcloud --url=ssh://192.168.0.211/pcloud-apps --branch=main --private-key-file=/Users/lekva/.ssh/id_rsa
+
package main
import (
+ "bytes"
+ "embed"
+ "encoding/base64"
"fmt"
+ "io"
"log"
"os"
+ "path"
+ "text/template"
+ "golang.org/x/exp/slices"
+
+ "github.com/giolekva/pcloud/core/installer"
"github.com/giolekva/pcloud/core/installer/soft"
"github.com/spf13/cobra"
+ "sigs.k8s.io/yaml"
)
+//go:embed env-tmpl
+var filesTmpls embed.FS
+
var createEnvFlags struct {
name string
adminPrivKey string
@@ -39,7 +55,19 @@
if err != nil {
return err
}
- ss, err := soft.NewClient("192.168.0.208", 22, adminPrivKey, log.Default())
+ ss, err := soft.NewClient("192.168.0.211", 22, adminPrivKey, log.Default())
+ if err != nil {
+ return err
+ }
+ ssPubKey, err := ss.GetPublicKey()
+ if err != nil {
+ return err
+ }
+ fmt.Println(string(ssPubKey))
+ pub, priv, err := installer.GenerateSSHKeys()
+ {
+ _ = priv
+ }
if err != nil {
return err
}
@@ -47,5 +75,77 @@
if err := ss.AddRepository(createEnvFlags.name, readme); err != nil {
return err
}
+ fluxUserName := fmt.Sprintf("flux-%s", createEnvFlags.name)
+ if err := ss.AddUser(fluxUserName, pub); err != nil {
+ return err
+ }
+ if err := ss.AddCollaborator(createEnvFlags.name, fluxUserName); err != nil {
+ return err
+ }
+ repo, err := ss.CloneRepository("pcloud")
+ if err != nil {
+ return err
+ }
+ wt, err := repo.Worktree()
+ if err != nil {
+ return err
+ }
+ envKust := "environments/kustomization.yaml"
+ envKustFile, err := wt.Filesystem.Open(envKust)
+ if err != nil {
+ return err
+ }
+ kust, err := installer.ReadKustomization(envKustFile)
+ if err != nil {
+ return err
+ }
+ if slices.Contains(kust.Resources, createEnvFlags.name) {
+ return fmt.Errorf("Environment already exists: %s", createEnvFlags.name)
+ }
+ tmpls, err := template.ParseFS(filesTmpls, "env-tmpl/*.yaml")
+ if err != nil {
+ return err
+ }
+ for _, tmpl := range tmpls.Templates() {
+ dstPath := path.Join("environments", createEnvFlags.name, tmpl.Name())
+ fmt.Println(dstPath)
+ dst, err := wt.Filesystem.Create(dstPath)
+ if err != nil {
+ return err
+ }
+ if err := tmpl.Execute(dst, map[string]string{
+ "Name": createEnvFlags.name,
+ "PrivateKey": base64.StdEncoding.EncodeToString([]byte(priv)),
+ "PublicKey": base64.StdEncoding.EncodeToString([]byte(pub)),
+ "GitHost": "192.168.0.211",
+ "KnownHosts": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("192.168.0.211 %s", ssPubKey))),
+ }); err != nil {
+ return err
+ }
+ if _, err := wt.Add(dstPath); err != nil {
+ return err
+ }
+ }
+ kust.Resources = append(kust.Resources, createEnvFlags.name)
+ ff, err := wt.Filesystem.Create(envKust)
+ if err != nil {
+ return err
+ }
+ contents, err := yaml.Marshal(kust)
+ if err != nil {
+ return err
+ }
+ if _, err := io.Copy(ff, bytes.NewReader(contents)); err != nil {
+ return err
+ }
+ if _, err := wt.Add(envKust); err != nil {
+ return err
+ }
+ if err := ss.Commit(wt, fmt.Sprintf("%s: new environment", createEnvFlags.name)); err != nil {
+ return err
+ }
+ if err := ss.Push(repo); err != nil {
+ return err
+ }
return nil
}