installer: appmanager cmd
diff --git a/core/installer/Makefile b/core/installer/Makefile
index 58a3176..ec40393 100644
--- a/core/installer/Makefile
+++ b/core/installer/Makefile
@@ -14,4 +14,4 @@
./pcloud create-env --admin-priv-key=/Users/lekva/.ssh/id_rsa --name=foo
rpuppy:
- ./pcloud install --ssh-key=/Users/lekva/.ssh/id_rsa --config=/Users/lekva/dev/src/pcloud/priv/lekva.yaml --app=rpuppy --repo-addr=ssh://localhost:2222/lekva
+ ./pcloud install --ssh-key=/Users/lekva/.ssh/id_rsa --app=rpuppy --repo-addr=ssh://localhost:2222/lekva
diff --git a/core/installer/app.go b/core/installer/app.go
index 2f702d8..6f766b9 100644
--- a/core/installer/app.go
+++ b/core/installer/app.go
@@ -2,17 +2,41 @@
import (
"embed"
+ "fmt"
"log"
"text/template"
)
+//go:embed values-tmpl
+var valuesTmpls embed.FS
+
type App struct {
Name string
Templates []*template.Template
}
-//go:embed values-tmpl
-var valuesTmpls embed.FS
+type AppRepository interface {
+ Find(name string) (*App, error)
+}
+
+type InMemoryAppRepository struct {
+ apps []App
+}
+
+func NewInMemoryAppRepository(apps []App) AppRepository {
+ return &InMemoryAppRepository{
+ apps,
+ }
+}
+
+func (r InMemoryAppRepository) Find(name string) (*App, error) {
+ for _, a := range r.apps {
+ if a.Name == name {
+ return &a, nil
+ }
+ }
+ return nil, fmt.Errorf("Application not found: %s", name)
+}
func CreateAllApps() []App {
tmpls, err := template.ParseFS(valuesTmpls, "values-tmpl/*.yaml")
diff --git a/core/installer/app_manager.go b/core/installer/app_manager.go
index 4b66fa5..0678114 100644
--- a/core/installer/app_manager.go
+++ b/core/installer/app_manager.go
@@ -1,60 +1,74 @@
package installer
import (
+ "fmt"
"io/fs"
+ "net"
+ "time"
+ "golang.org/x/crypto/ssh"
"golang.org/x/exp/slices"
- "github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/util"
+ "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"
)
+const appDirName = "apps"
+const configFileName = "config.yaml"
const kustomizationFileName = "kustomization.yaml"
-type AppRepository interface {
- Find(name string) (*App, error)
-}
-
type AppManager struct {
- fs billy.Filesystem
- config Config
- appRepo AppRepository
- rootKust *Kustomization
+ repo *git.Repository
+ signer ssh.Signer
}
-func NewAppManager(fs billy.Filesystem, config Config, appRepo AppRepository) (*AppManager, error) {
- rootKustF, err := fs.Open(kustomizationFileName)
+// func NewAppManager(repo *git.Repository, fs billy.Filesystem, config Config, appRepo AppRepository) (*AppManager, error) {
+func NewAppManager(repo *git.Repository, signer ssh.Signer) (*AppManager, error) {
+ return &AppManager{
+ repo,
+ signer,
+ }, nil
+}
+
+func (m *AppManager) Install(app App) error {
+ wt, err := m.repo.Worktree()
if err != nil {
- return nil, err
+ return err
+ }
+ configF, err := wt.Filesystem.Open(configFileName)
+ if err != nil {
+ return err
+ }
+ defer configF.Close()
+ config, err := ReadConfig(configF)
+ if err != nil {
+ return err
+ }
+ appsRoot, err := wt.Filesystem.Chroot(appDirName)
+ if err != nil {
+ return err
+ }
+ rootKustF, err := appsRoot.Open(kustomizationFileName)
+ if err != nil {
+ return err
}
defer rootKustF.Close()
rootKust, err := ReadKustomization(rootKustF)
if err != nil {
- return nil, err
- }
- return &AppManager{
- fs,
- config,
- appRepo,
- rootKust,
- }, nil
-}
-
-func (m *AppManager) Install(name string) error {
- app, err := m.appRepo.Find(name)
- if err != nil {
- return nil
- }
- if err := util.RemoveAll(m.fs, name); err != nil {
return err
}
- if err := m.fs.MkdirAll(name, fs.ModePerm); err != nil {
- return nil
- }
- appRoot, err := m.fs.Chroot(name)
+ appRoot, err := appsRoot.Chroot(app.Name)
if err != nil {
return err
}
+ if err := util.RemoveAll(appRoot, app.Name); err != nil {
+ return err
+ }
+ if err := appRoot.MkdirAll(app.Name, fs.ModePerm); err != nil {
+ return nil
+ }
appKust := NewKustomization()
for _, t := range app.Templates {
out, err := appRoot.Create(t.Name())
@@ -62,7 +76,7 @@
return err
}
defer out.Close()
- if err := t.Execute(out, m.config); err != nil {
+ if err := t.Execute(out, config); err != nil {
return err
}
appKust.Resources = append(appKust.Resources, t.Name())
@@ -75,14 +89,44 @@
if err := appKust.Write(appKustF); err != nil {
return err
}
- if slices.Contains(m.rootKust.Resources, name) {
- return nil
+ if !slices.Contains(rootKust.Resources, app.Name) {
+ rootKust.Resources = append(rootKust.Resources, app.Name)
+ rootKustFW, err := appsRoot.Create(kustomizationFileName)
+ if err != nil {
+ return err
+ }
+ defer rootKustFW.Close()
+ if err := rootKust.Write(rootKustFW); err != nil {
+ return err
+ }
}
- m.rootKust.Resources = append(m.rootKust.Resources, name)
- rootKustF, err := m.fs.Create(kustomizationFileName)
- if err != nil {
+ // Commit and push
+ if err := wt.AddGlob("*"); err != nil {
return err
}
- defer rootKustF.Close()
- return m.rootKust.Write(rootKustF)
+ if _, err := wt.Commit(fmt.Sprintf("install: %s", app.Name), &git.CommitOptions{
+ Author: &object.Signature{
+ Name: "pcloud-appmanager",
+ When: time.Now(),
+ },
+ }); err != nil {
+ return err
+ }
+ return m.repo.Push(&git.PushOptions{
+ RemoteName: "origin",
+ Auth: auth(m.signer),
+ })
+}
+
+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/app_manager.go b/core/installer/cmd/app_manager.go
new file mode 100644
index 0000000..1fc6d64
--- /dev/null
+++ b/core/installer/cmd/app_manager.go
@@ -0,0 +1,55 @@
+package main
+
+import (
+ "os"
+
+ "github.com/giolekva/pcloud/core/installer"
+ "github.com/spf13/cobra"
+ "golang.org/x/crypto/ssh"
+)
+
+var appManagerFlags struct {
+ sshKey string
+ repoAddr string
+}
+
+func appManagerCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "appmanager",
+ RunE: installCmdRun,
+ }
+ cmd.Flags().StringVar(
+ &installFlags.sshKey,
+ "ssh-key",
+ "",
+ "",
+ )
+ cmd.Flags().StringVar(
+ &installFlags.repoAddr,
+ "repo-addr",
+ "",
+ "",
+ )
+ return cmd
+}
+
+func appManagerCmdRun(cmd *cobra.Command, args []string) error {
+ sshKey, err := os.ReadFile(installFlags.sshKey)
+ if err != nil {
+ return err
+ }
+ signer, err := ssh.ParsePrivateKey(sshKey)
+ if err != nil {
+ return err
+ }
+ repo, err := cloneRepo(installFlags.repoAddr, signer)
+ if err != nil {
+ return err
+ }
+ _, err = installer.NewAppManager(repo, signer)
+ if err != nil {
+ return err
+ }
+ // TODO(gio): start server
+ return nil
+}
diff --git a/core/installer/cmd/apps.go b/core/installer/cmd/apps.go
index b96ac6f..0a58be2 100644
--- a/core/installer/cmd/apps.go
+++ b/core/installer/cmd/apps.go
@@ -1,28 +1,22 @@
package main
import (
- "fmt"
- "io/ioutil"
"net"
"os"
- "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 {
sshKey string
- config string
appName string
repoAddr string
}
@@ -39,12 +33,6 @@
"",
)
cmd.Flags().StringVar(
- &installFlags.config,
- "config",
- "",
- "",
- )
- cmd.Flags().StringVar(
&installFlags.appName,
"app",
"",
@@ -59,30 +47,7 @@
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
- }
sshKey, err := os.ReadFile(installFlags.sshKey)
if err != nil {
return err
@@ -95,58 +60,19 @@
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()),
+ repo,
+ signer,
)
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) {
- var cfg installer.Config
- inp, err := ioutil.ReadFile(config)
+ appRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
+ app, err := appRepo.Find(installFlags.appName)
if err != nil {
- return cfg, err
+ return err
}
- err = yaml.UnmarshalStrict(inp, &cfg)
- return cfg, err
+ return m.Install(*app)
}
func cloneRepo(address string, signer ssh.Signer) (*git.Repository, error) {
diff --git a/core/installer/config.go b/core/installer/config.go
index 4563f4d..fa171e8 100644
--- a/core/installer/config.go
+++ b/core/installer/config.go
@@ -1,5 +1,12 @@
package installer
+import (
+ "io"
+ "io/ioutil"
+
+ "sigs.k8s.io/yaml"
+)
+
type Config struct {
Values Values `json:"values"`
}
@@ -22,3 +29,13 @@
PiholeOAuth2ClientSecret string `json:"piholeOAuth2ClientSecret,omitempty"`
PiholeOAuth2CookieSecret string `json:"piholeOAuth2CookieSecret,omitempty"`
}
+
+func ReadConfig(r io.Reader) (Config, error) {
+ var cfg Config
+ contents, err := ioutil.ReadAll(r)
+ if err != nil {
+ return cfg, err
+ }
+ err = yaml.UnmarshalStrict(contents, &cfg)
+ return cfg, err
+}