github.com/nvkalinin/business-calendar@v1.0.2-0.20220515154925-e7df8a3d0c34/cmd/backup.go (about)

     1  package cmd
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"mime"
     7  	"net/http"
     8  	"os"
     9  	"time"
    10  
    11  	"github.com/nvkalinin/business-calendar/log"
    12  )
    13  
    14  type Backup struct {
    15  	ServerUrl   string        `long:"server-url" short:"s" env:"SERVER_URL" default:"http://localhost" description:"URL сервера с REST API календаря."`
    16  	AdminPasswd string        `long:"passwd" short:"p" env:"WEB_ADMIN_PASSWD" description:"Пароль пользователя admin."`
    17  	OutFile     string        `long:"out" short:"o" env:"OUT" description:"Путь к файлу, куда сохранить бекап. По умолчанию: cal_YYYY-MM-DD.bolt.gz. Значение '-' выводит в стандартный поток вывода."`
    18  	Timeout     time.Duration `long:"timeout" short:"t" env:"TIMEOUT" default:"600s" description:"Макс. время выполнения запроса."`
    19  }
    20  
    21  func (b *Backup) Execute(args []string) error {
    22  	url := makeUrl(b.ServerUrl, "/api/admin/backup")
    23  	req, err := http.NewRequest(http.MethodGet, url, http.NoBody)
    24  	if err != nil {
    25  		log.Fatalf("[ERROR] cannot create request: %v", err)
    26  	}
    27  	req.SetBasicAuth("admin", b.AdminPasswd)
    28  	log.Printf("[DEBUG] backup request: URL=%s, %#v", url, req)
    29  
    30  	client := &http.Client{Timeout: b.Timeout}
    31  	resp, err := client.Do(req)
    32  	if err != nil {
    33  		log.Fatalf("[ERROR] cannot make request: %v", err)
    34  	}
    35  	defer func() {
    36  		if err := resp.Body.Close(); err != nil {
    37  			log.Printf("[WARN] cannot close resp body: %v", err)
    38  		}
    39  	}()
    40  	log.Printf("[DEBUG] backup response: %#v", resp)
    41  
    42  	if resp.StatusCode != 200 {
    43  		respBody, err := io.ReadAll(resp.Body)
    44  		if err != nil {
    45  			log.Fatalf("[ERROR] cannot read err response (status %d): %v", resp.StatusCode, err)
    46  		}
    47  		err = readJsonError(respBody)
    48  		log.Fatalf("[ERROR] backup error (status %d): %v", resp.StatusCode, err)
    49  	}
    50  
    51  	fname := b.filename(resp)
    52  	log.Printf("[DEBUG] saving backup to file '%s'", fname)
    53  	var f *os.File
    54  	if fname == "-" {
    55  		f = os.Stdout
    56  	} else {
    57  		f, err = os.Create(fname)
    58  		if err != nil {
    59  			log.Fatalf("[ERROR] cannot open %s: %v", fname, err)
    60  		}
    61  		defer func() {
    62  			if err := f.Close(); err != nil {
    63  				log.Printf("[WARN] cannot close %s: %v", fname, err)
    64  			}
    65  		}()
    66  	}
    67  
    68  	written, err := io.Copy(f, resp.Body)
    69  	if err != nil {
    70  		log.Fatalf("[ERROR] cannot save backup to %s: %v", fname, err)
    71  	}
    72  	log.Printf("[DEBUG] %d bytes written", written)
    73  
    74  	return nil
    75  }
    76  
    77  func (b *Backup) filename(resp *http.Response) string {
    78  	if len(b.OutFile) > 0 {
    79  		return b.OutFile
    80  	}
    81  
    82  	defName := fmt.Sprintf("cal_%s.bolt.gz", time.Now().Format("2006-01-02"))
    83  
    84  	vals, ok := resp.Header["Content-Disposition"]
    85  	if !ok || len(vals) == 0 {
    86  		return defName
    87  	}
    88  
    89  	_, params, err := mime.ParseMediaType(vals[0])
    90  	if err != nil {
    91  		return defName
    92  	}
    93  
    94  	name, ok := params["filename"]
    95  	if !ok || len(name) == 0 {
    96  		return defName
    97  	}
    98  
    99  	return name
   100  }