blob: 68964d3e5c79d57c5a8a97dcc6d4a7051379498f [file] [log] [blame]
package soft
import (
"fmt"
"io/ioutil"
"log"
"net"
"os"
"time"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5"
// "github.com/go-git/go-git/v5/config"
"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"
"golang.org/x/crypto/ssh"
"sigs.k8s.io/yaml"
)
type Client struct {
ip string
port int
Signer ssh.Signer
log *log.Logger
}
func NewClient(ip string, port int, clientPrivateKey []byte, log *log.Logger) (*Client, error) {
signer, err := ssh.ParsePrivateKey(clientPrivateKey)
if err != nil {
return nil, err
}
log.SetPrefix("SOFT-SERVE: ")
return &Client{
ip,
port,
signer,
log,
}, nil
}
func (ss *Client) AddUser(name, pubKey string) error {
log.Printf("Adding user %s", name)
if err := ss.RunCommand(fmt.Sprintf("user create %s", name)); err != nil {
return err
}
return ss.RunCommand(fmt.Sprintf("user add-pubkey %s %s", name, pubKey))
}
func (ss *Client) MakeUserAdmin(name string) error {
log.Printf("Making user %s admin", name)
return ss.RunCommand(fmt.Sprintf("user set-admin %s true", name))
}
func (ss *Client) RunCommand(cmd string) error {
log.Printf("Running command %s", cmd)
client, err := ssh.Dial("tcp", ss.addressSSH(), ss.sshClientConfig())
if err != nil {
return err
}
session, err := client.NewSession()
if err != nil {
return err
}
defer session.Close()
return session.Run(cmd)
}
func (ss *Client) AddRepository(name, readme string) error {
log.Printf("Adding repository %s", name)
return ss.RunCommand(fmt.Sprintf("repo create %s -d \"%s\"", name, readme))
}
func (ss *Client) AddCollaborator(repo, user string) error {
log.Printf("Adding collaborator %s %s", repo, user)
return ss.RunCommand(fmt.Sprintf("repo collab add %s %s", repo, user))
}
func (ss *Client) CreateRepository(name string) error {
log.Printf("Creating repository %s", name)
configRepo, err := ss.getConfigRepo()
if err != nil {
return err
}
wt, err := configRepo.Worktree()
if err != nil {
return err
}
if err = wt.Checkout(&git.CheckoutOptions{
Branch: "refs/heads/master",
}); err != nil {
return err
}
f, err := wt.Filesystem.Open("config.yaml")
if err != nil {
return err
}
defer f.Close()
configBytes, err := ioutil.ReadAll(f)
if err != nil {
return err
}
config := make(map[string]interface{})
if err := yaml.Unmarshal(configBytes, &config); err != nil {
return err
}
repos := config["repos"].([]interface{})
repos = append(repos, map[string]interface{}{
"name": name,
"repo": name,
"private": true,
"note": fmt.Sprintf("PCloud env for %s", name),
})
config["repos"] = repos
configBytes, err = yaml.Marshal(config)
if err != nil {
return err
}
if err := ss.writeFile(wt, "config.yaml", string(configBytes)); err != nil {
return err
}
if err := ss.Commit(wt, fmt.Sprintf("add-repo: %s", name)); err != nil {
return err
}
return ss.Push(configRepo)
}
func (ss *Client) getConfigRepo() (*git.Repository, error) {
return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
URL: ss.addressGit(),
Auth: ss.authGit(),
RemoteName: "soft",
ReferenceName: "refs/heads/master",
Depth: 1,
InsecureSkipTLS: true,
Progress: os.Stdout,
})
}
func (ss *Client) GetRepo(name string) (*git.Repository, error) {
return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
URL: fmt.Sprintf("%s/%s", ss.addressGit(), name),
Auth: ss.authGit(),
RemoteName: "soft",
ReferenceName: "refs/heads/master",
Depth: 1,
InsecureSkipTLS: true,
Progress: os.Stdout,
})
}
func (ss *Client) repoPathByName(name string) string {
return fmt.Sprintf("%s/%s", ss.addressGit(), name)
}
func (ss *Client) Commit(wt *git.Worktree, message string) error {
_, err := wt.Commit(message, &git.CommitOptions{
Author: &object.Signature{
Name: "pcloud",
Email: "pcloud@installer",
When: time.Now(),
},
})
return err
}
func (ss *Client) Push(repo *git.Repository) error {
return repo.Push(&git.PushOptions{
RemoteName: "soft",
Auth: ss.authGit(),
})
}
func (ss *Client) writeFile(wt *git.Worktree, path, contents string) error {
f, err := wt.Filesystem.Create(path)
if err != nil {
return err
}
defer f.Close()
if _, err = f.Write([]byte(contents)); err != nil {
return err
}
_, err = wt.Add(path)
return err
}
func (ss *Client) CloneRepository(name string) (*git.Repository, error) {
return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
URL: ss.repoPathByName(name),
Auth: ss.authGit(),
RemoteName: "soft",
InsecureSkipTLS: true,
})
}
func (ss *Client) authGit() *gitssh.PublicKeys {
return &gitssh.PublicKeys{
Signer: ss.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
},
},
}
}
func (ss *Client) GetPublicKey() ([]byte, error) {
var ret []byte
config := &ssh.ClientConfig{
Auth: []ssh.AuthMethod{
ssh.PublicKeys(ss.Signer),
},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
ret = ssh.MarshalAuthorizedKey(key)
return nil
},
}
_, err := ssh.Dial("tcp", ss.addressSSH(), config)
if err != nil {
return nil, err
}
return ret, nil
}
func (ss *Client) sshClientConfig() *ssh.ClientConfig {
return &ssh.ClientConfig{
Auth: []ssh.AuthMethod{
ssh.PublicKeys(ss.Signer),
},
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))
fmt.Printf("%s %s %s", hostname, remote, ssh.MarshalAuthorizedKey(key))
return nil
},
}
}
func (ss *Client) addressGit() string {
return fmt.Sprintf("ssh://%s:%d", ss.ip, ss.port)
}
func (ss *Client) addressSSH() string {
return fmt.Sprintf("%s:%d", ss.ip, ss.port)
}