blob: 97bf9b6cb75f7335ce0e3c3569f5c6088eb0e0d5 [file] [log] [blame]
package installer
import (
"errors"
"fmt"
"io"
"io/fs"
"net"
"path"
"path/filepath"
"time"
"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"
"golang.org/x/crypto/ssh"
"sigs.k8s.io/yaml"
)
type RepoIO interface {
Fetch() error
ReadKustomization(path string) (*Kustomization, error)
WriteKustomization(path string, kust Kustomization) error
WriteYaml(path string, data any) error
CommitAndPush(message string) error
Reader(path string) (io.ReadCloser, error)
Writer(path string) (io.WriteCloser, error)
CreateDir(path string) error
RemoveDir(path string) error
InstallApp(app App, path string, values map[string]any) error
}
type repoIO struct {
repo *git.Repository
signer ssh.Signer
}
func NewRepoIO(repo *git.Repository, signer ssh.Signer) RepoIO {
return &repoIO{
repo,
signer,
}
}
func (r *repoIO) Fetch() error {
err := r.repo.Fetch(&git.FetchOptions{
RemoteName: "origin",
Auth: auth(r.signer),
Force: true,
})
if err == nil || err == git.NoErrAlreadyUpToDate {
return nil
}
return err
}
func (r *repoIO) ReadKustomization(path string) (*Kustomization, error) {
inp, err := r.Reader(path)
if err != nil {
return nil, err
}
defer inp.Close()
return ReadKustomization(inp)
}
func (r *repoIO) Reader(path string) (io.ReadCloser, error) {
wt, err := r.repo.Worktree()
if err != nil {
return nil, err
}
return wt.Filesystem.Open(path)
}
func (r *repoIO) Writer(path string) (io.WriteCloser, error) {
wt, err := r.repo.Worktree()
if err != nil {
return nil, err
}
if err := wt.Filesystem.MkdirAll(filepath.Dir(path), fs.ModePerm); err != nil {
return nil, err
}
return wt.Filesystem.Create(path)
}
func (r *repoIO) WriteKustomization(path string, kust Kustomization) error {
out, err := r.Writer(path)
if err != nil {
return err
}
return kust.Write(out)
}
func (r *repoIO) WriteYaml(path string, data any) error {
out, err := r.Writer(path)
if err != nil {
return err
}
serialized, err := yaml.Marshal(data)
if err != nil {
return err
}
if _, err := out.Write(serialized); err != nil {
return err
}
return nil
}
func (r *repoIO) CommitAndPush(message string) error {
wt, err := r.repo.Worktree()
if err != nil {
return err
}
if err := wt.AddGlob("*"); err != nil {
return err
}
if _, err := wt.Commit(message, &git.CommitOptions{
Author: &object.Signature{
Name: "pcloud-installer",
When: time.Now(),
},
}); err != nil {
return err
}
return r.repo.Push(&git.PushOptions{
RemoteName: "origin",
Auth: auth(r.signer),
})
}
func (r *repoIO) CreateDir(path string) error {
wt, err := r.repo.Worktree()
if err != nil {
return err
}
return wt.Filesystem.MkdirAll(path, fs.ModePerm)
}
func (r *repoIO) RemoveDir(path string) error {
wt, err := r.repo.Worktree()
if err != nil {
return err
}
err = util.RemoveAll(wt.Filesystem, path)
if err == nil || errors.Is(err, fs.ErrNotExist) {
return nil
}
return err
}
func (r *repoIO) InstallApp(app App, root string, values map[string]any) error {
{
appsKustPath := path.Join(root, "kustomization.yaml")
appsKust, err := r.ReadKustomization(appsKustPath)
if err != nil {
return err
}
appsKust.AddResources(app.Name)
if err := r.WriteKustomization(appsKustPath, *appsKust); err != nil {
return err
}
}
appRootDir := path.Join(root, app.Name)
{
if err := r.RemoveDir(appRootDir); err != nil {
return err
}
if err := r.CreateDir(appRootDir); err != nil {
return err
}
if err := r.WriteYaml(path.Join(appRootDir, configFileName), values); err != nil {
return err
}
}
{
appKust := NewKustomization()
for _, t := range app.Templates {
appKust.AddResources(t.Name())
out, err := r.Writer(path.Join(appRootDir, t.Name()))
if err != nil {
return err
}
defer out.Close()
if err := t.Execute(out, values); err != nil {
return err
}
}
if err := r.WriteKustomization(path.Join(appRootDir, "kustomization.yaml"), appKust); err != nil {
return err
}
}
return r.CommitAndPush(fmt.Sprintf("install: %s", app.Name))
}
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
},
},
}
}