2021-10-16 15:39:54 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/csv"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
)
|
|
|
|
|
|
|
|
type SSHConfig struct {
|
|
|
|
signer ssh.Signer
|
|
|
|
config *ssh.ClientConfig
|
|
|
|
client *ssh.Client
|
|
|
|
logged bool
|
|
|
|
name string
|
|
|
|
zfs map[string]string
|
|
|
|
snapshot []Snapshot
|
|
|
|
now time.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SSHConfig) getLastSnapshot(path string) (Snapshot, error) {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getLastSnapshot : Start %s:%s (%d snapshots)", s.name, path, len(s.snapshot))
|
|
|
|
}
|
|
|
|
var last Snapshot
|
|
|
|
for _, v := range s.snapshot {
|
|
|
|
if v.Path() == path {
|
|
|
|
last = v
|
|
|
|
} else {
|
|
|
|
if len(string(last)) > 0 {
|
|
|
|
return last, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(string(last)) > 0 {
|
|
|
|
return last, nil
|
|
|
|
}
|
|
|
|
return last, fmt.Errorf("no snapshot")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SSHConfig) isLastSnapshot(snapshot Snapshot) bool {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.isLastSnapshot : Start %s:%s", s.name, string(snapshot))
|
|
|
|
}
|
|
|
|
_, err := s.getNextSnapshot(snapshot)
|
|
|
|
if err != nil {
|
|
|
|
return true
|
|
|
|
} else {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SSHConfig) getFirstSnapshot(path string) (Snapshot, error) {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getFirstSnapshot : Start %s:%s", s.name, path)
|
|
|
|
}
|
|
|
|
var first Snapshot
|
|
|
|
for _, v := range s.snapshot {
|
|
|
|
if v.Path() == path {
|
|
|
|
first = v
|
2021-10-18 16:27:54 +02:00
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getFirstSnapshot : Return %s", string(first))
|
|
|
|
}
|
2021-10-16 15:39:54 +02:00
|
|
|
return first, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return first, fmt.Errorf("no snapshot")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SSHConfig) getNextSnapshot(snapshot Snapshot) (Snapshot, error) {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getNextSnapshot : Start %s:%s", s.name, string(snapshot))
|
|
|
|
}
|
|
|
|
var next Snapshot
|
|
|
|
for id, v := range s.snapshot {
|
|
|
|
if v == snapshot {
|
|
|
|
if len(s.snapshot) > id+1 {
|
|
|
|
next = s.snapshot[id+1]
|
|
|
|
if next.Path() == snapshot.Path() {
|
|
|
|
return next, nil
|
|
|
|
} else {
|
|
|
|
return next, fmt.Errorf("no snapshot")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return next, fmt.Errorf("no snapshot")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return next, fmt.Errorf("no snapshot")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SSHConfig) getSnapshotList() error {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getSnapshotList : %s : Start", s.name)
|
|
|
|
}
|
|
|
|
if !s.logged {
|
|
|
|
return fmt.Errorf("Client %s not logged in.", s.name)
|
|
|
|
}
|
|
|
|
|
|
|
|
session, err := s.client.NewSession()
|
|
|
|
if err != nil {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getSnapshotList : %s : client.NewSession() : %s", s.name, err)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var b bytes.Buffer
|
|
|
|
session.Stdout = &b
|
|
|
|
|
2021-11-04 13:18:18 +01:00
|
|
|
err = session.Run("TZ=\"" + cfg.Timezone + "\" zfs list -H -t snapshot -o name")
|
2021-10-16 15:39:54 +02:00
|
|
|
if err != nil {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getSnapshotList : %s : session.Run() : %s", s.name, err)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
s.snapshot = make([]Snapshot, 0)
|
|
|
|
|
|
|
|
csvReader := csv.NewReader(&b)
|
|
|
|
csvReader.Comma = '\t'
|
|
|
|
csvReader.FieldsPerRecord = 1
|
|
|
|
|
|
|
|
csvData, err := csvReader.ReadAll()
|
|
|
|
if err != nil {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getSnapshotList : %s : csvReader.ReadAll() : %s", s.name, err)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, rec := range csvData {
|
|
|
|
s.snapshot = append(s.snapshot, Snapshot(rec[0]))
|
|
|
|
}
|
|
|
|
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getSnapshotList : %s : read %d zfs snapshots", s.name, len(s.snapshot))
|
|
|
|
}
|
|
|
|
|
|
|
|
session.Close()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SSHConfig) getZFSList() error {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getZFSList : %s : Start", s.name)
|
|
|
|
}
|
|
|
|
if !s.logged {
|
|
|
|
return fmt.Errorf("Client %s not logged in.", s.name)
|
|
|
|
}
|
|
|
|
|
|
|
|
session, err := s.client.NewSession()
|
|
|
|
if err != nil {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getZFSList : %s : client.NewSession() : %s", s.name, err)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var b bytes.Buffer
|
|
|
|
session.Stdout = &b
|
|
|
|
|
2021-11-04 13:18:18 +01:00
|
|
|
err = session.Run("TZ=\"" + cfg.Timezone + "\" zfs list -H -o name,mountpoint")
|
2021-10-16 15:39:54 +02:00
|
|
|
if err != nil {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getZFSList : %s : session.Run() : %s", s.name, err)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
s.zfs = make(map[string]string)
|
|
|
|
|
|
|
|
csvReader := csv.NewReader(&b)
|
|
|
|
csvReader.Comma = '\t'
|
|
|
|
csvReader.FieldsPerRecord = 2
|
|
|
|
|
|
|
|
csvData, err := csvReader.ReadAll()
|
|
|
|
if err != nil {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getZFSList : %s : csvReader.ReadAll() : %s", s.name, err)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, rec := range csvData {
|
|
|
|
s.zfs[rec[0]] = rec[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getZFSList : %s : read %d zfs file systems", s.name, len(s.zfs))
|
|
|
|
}
|
|
|
|
|
|
|
|
session.Close()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SSHConfig) isZFS(path string) bool {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.isZFS : Start %s:%s", s.name, path)
|
|
|
|
}
|
|
|
|
if len(s.zfs) == 0 {
|
|
|
|
err := s.getZFSList()
|
|
|
|
if err != nil {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.isZFS : s.getZFSList(%s) : %s", s.name, err)
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_, ok := s.zfs[path]
|
|
|
|
if ok {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SSHConfig) getTime() error {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getTime : %s : Start", s.name)
|
|
|
|
}
|
|
|
|
|
|
|
|
session, err := s.client.NewSession()
|
|
|
|
if err != nil {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getTime : %s : client.NewSession() : %s", s.name, err)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var b bytes.Buffer
|
|
|
|
session.Stdout = &b
|
|
|
|
|
2021-11-04 13:18:18 +01:00
|
|
|
err = session.Run("TZ=\"" + cfg.Timezone + "\" date +\"%F %T\"")
|
2021-10-16 15:39:54 +02:00
|
|
|
if err != nil {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getTime : %s : session.Run() : %s", s.name, err)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
s.now, err = time.Parse("2006-01-02 15:04:05\n", b.String())
|
|
|
|
if err != nil {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getTime : %s : time.Parse() : %s", s.name, err)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.getTime : %s : now is %s", s.name, s.now.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
session.Close()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SSHConfig) exec(cmd string) error {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.exec : %s : Start %s", s.name, cmd)
|
|
|
|
}
|
|
|
|
|
|
|
|
session, err := s.client.NewSession()
|
|
|
|
if err != nil {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.exec : %s : client().NewSession(%s) : %s", s.name, cmd, err)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = session.Run("TZ=\"" + cfg.Timezone + "\" " + cmd)
|
|
|
|
if err != nil {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.exec : session(%s).Run(%s) : %s", s.name, cmd, err)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
session.Close()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SSHConfig) createZFS(path string) error {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.createZFS : Start %s:%s", s.name, path)
|
|
|
|
}
|
|
|
|
if len(s.zfs) == 0 {
|
|
|
|
err := s.getZFSList()
|
|
|
|
if err != nil {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.createZFS : s.getZFSList(%s) : %s", s.name, err)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
p := strings.Split(path, `/`)
|
|
|
|
var base string
|
|
|
|
for _, d := range p {
|
|
|
|
if base == "" {
|
|
|
|
base = d
|
|
|
|
} else {
|
|
|
|
base = base + `/` + d
|
|
|
|
}
|
|
|
|
if _, ok := s.zfs[base]; !ok {
|
|
|
|
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.createZFS : Creating %s:%s", s.name, base)
|
|
|
|
}
|
|
|
|
|
2021-11-04 13:18:18 +01:00
|
|
|
err := s.exec("zfs create -o mountpoint=none " + base)
|
2021-10-16 15:39:54 +02:00
|
|
|
if err != nil {
|
|
|
|
if *debugFlag {
|
|
|
|
log.Printf("SSHConfig.createZFS : s.exec(%s) : %s", s.name, err)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
s.zfs[base] = "none"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|