Beispiel #1
0
// Export dumps all data from a database service and downloads the encrypted
// data to the local machine. The export is accomplished by first creating a
// backup. Once finished, the CLI asks where the file can be downloaded from.
// The file is downloaded, decrypted, and saved locally.
func Export(databaseLabel string, filePath string, force bool, settings *models.Settings) {
	helpers.PHIPrompt()
	helpers.SignIn(settings)
	if !force {
		if _, err := os.Stat(filePath); err == nil {
			fmt.Printf("File already exists at path '%s'. Specify `--force` to overwrite\n", filePath)
			os.Exit(1)
		}
	} else {
		os.Remove(filePath)
	}
	service := helpers.RetrieveServiceByLabel(databaseLabel, settings)
	if service == nil {
		fmt.Printf("Could not find a service with the label \"%s\"\n", databaseLabel)
		os.Exit(1)
	}
	task := helpers.CreateBackup(service.ID, settings)
	fmt.Printf("Export started (task ID = %s)\n", task.ID)
	fmt.Print("Polling until export finishes.")
	ch := make(chan string, 1)
	go helpers.PollTaskStatus(task.ID, ch, settings)
	status := <-ch
	task.Status = status
	if task.Status != "finished" {
		fmt.Printf("\nExport finished with illegal status \"%s\", aborting.\n", task.Status)
		helpers.DumpLogs(service, task, "backup", settings)
		os.Exit(1)
	}
	fmt.Printf("\nEnded in status '%s'\n", task.Status)
	job := helpers.RetrieveJobFromTaskID(task.ID, settings)
	fmt.Printf("Downloading export %s\n", job.ID)
	tempURL := helpers.RetrieveTempURL(job.ID, service.ID, settings)
	dir, dirErr := ioutil.TempDir("", "")
	if dirErr != nil {
		fmt.Println(dirErr.Error())
		os.Exit(1)
	}
	defer os.Remove(dir)
	tmpFile, tmpFileErr := ioutil.TempFile(dir, "")
	if tmpFileErr != nil {
		fmt.Println(tmpFileErr.Error())
		os.Exit(1)
	}
	resp, respErr := http.Get(tempURL.URL)
	if respErr != nil {
		fmt.Println(respErr.Error())
		os.Exit(1)
	}
	defer resp.Body.Close()
	io.Copy(tmpFile, resp.Body)
	fmt.Println("Decrypting...")
	tmpFile.Close()
	helpers.DecryptFile(tmpFile.Name(), job.Backup.Key, job.Backup.IV, filePath)
	fmt.Printf("%s exported successfully to %s\n", databaseLabel, filePath)
	helpers.DumpLogs(service, task, "backup", settings)
}
Beispiel #2
0
// Console opens a secure console to a code or database service. For code
// services, a command is required. This command is executed as root in the
// context of the application root directory. For database services, no command
// is needed - instead, the appropriate command for the database type is run.
// For example, for a postgres database, psql is run.
func Console(serviceLabel string, command string, settings *models.Settings) {
	helpers.SignIn(settings)
	service := helpers.RetrieveServiceByLabel(serviceLabel, settings)
	if service == nil {
		fmt.Printf("Could not find a service with the label \"%s\"\n", serviceLabel)
		os.Exit(1)
	}
	fmt.Printf("Opening console to %s (%s)\n", serviceLabel, service.ID)
	task := helpers.RequestConsole(command, service.ID, settings)
	fmt.Print("Waiting for the console to be ready. This might take a minute.")

	ch := make(chan string, 1)
	go helpers.PollConsoleJob(task.ID, service.ID, ch, settings)
	jobID := <-ch
	defer helpers.DestroyConsole(jobID, service.ID, settings)
	creds := helpers.RetrieveConsoleTokens(jobID, service.ID, settings)

	creds.URL = strings.Replace(creds.URL, "http", "ws", 1)
	fmt.Println("Connecting...")

	// BEGIN websocket impl
	config, _ := websocket.NewConfig(creds.URL, "ws://localhost:9443/")
	config.TlsConfig = &tls.Config{
		MinVersion: tls.VersionTLS12,
	}
	config.Header["X-Console-Token"] = []string{creds.Token}
	ws, err := websocket.DialConfig(config)
	if err != nil {
		panic(err)
	}
	defer ws.Close()
	fmt.Println("Connection opened")

	stdin, stdout, _ := term.StdStreams()
	fdIn, isTermIn := term.GetFdInfo(stdin)
	if !isTermIn {
		panic(errors.New("StdIn is not a terminal"))
	}
	oldState, err := term.SetRawTerminal(fdIn)
	if err != nil {
		panic(err)
	}

	done := make(chan bool)
	msgCh := make(chan []byte, 2)
	go webSocketDaemon(ws, &stdout, done, msgCh)

	signal.Notify(make(chan os.Signal, 1), os.Interrupt)

	defer term.RestoreTerminal(fdIn, oldState)
	go termDaemon(&stdin, ws)
	<-done
}
Beispiel #3
0
// DownloadBackup an existing backup to the local machine. The backup is encrypted
// throughout the entire journey and then decrypted once it is stored locally.
func DownloadBackup(serviceLabel string, backupID string, filePath string, force bool, settings *models.Settings) {
	helpers.PHIPrompt()
	helpers.SignIn(settings)
	if !force {
		if _, err := os.Stat(filePath); err == nil {
			fmt.Printf("File already exists at path '%s'. Specify `--force` to overwrite\n", filePath)
			os.Exit(1)
		}
	} else {
		os.Remove(filePath)
	}
	service := helpers.RetrieveServiceByLabel(serviceLabel, settings)
	if service == nil {
		fmt.Printf("Could not find a service with the label \"%s\"\n", serviceLabel)
		os.Exit(1)
	}
	job := helpers.RetrieveJob(backupID, service.ID, settings)
	if job.Type != "backup" || job.Status != "finished" {
		fmt.Println("Only 'finished' 'backup' jobs may be downloaded")
	}
	fmt.Printf("Downloading backup %s\n", backupID)
	tempURL := helpers.RetrieveTempURL(backupID, service.ID, settings)
	dir, dirErr := ioutil.TempDir("", "")
	if dirErr != nil {
		fmt.Println(dirErr.Error())
		os.Exit(1)
	}
	defer os.Remove(dir)
	tmpFile, tmpFileErr := ioutil.TempFile(dir, "")
	if tmpFileErr != nil {
		fmt.Println(tmpFileErr.Error())
		os.Exit(1)
	}
	resp, respErr := http.Get(tempURL.URL)
	if respErr != nil {
		fmt.Println(respErr.Error())
		os.Exit(1)
	}
	defer resp.Body.Close()
	io.Copy(tmpFile, resp.Body)
	tmpFile.Close()
	fmt.Println("Decrypting...")
	helpers.DecryptFile(tmpFile.Name(), job.Backup.Key, job.Backup.IV, filePath)
	fmt.Printf("%s backup downloaded successfully to %s\n", serviceLabel, filePath)
}
Beispiel #4
0
// Import imports data into a database service. The import is accomplished
// by encrypting the file locally, requesting a location that it can be uploaded
// to, then uploads the file. Once uploaded an automated service processes the
// file and acts according to the given parameters.
//
// The type of file that should be imported depends on the database. For
// PostgreSQL and MySQL, this should be a single `.sql` file. For Mongo, this
// should be a single tar'ed, gzipped archive (`.tar.gz`) of the database dump
// that you want to import.
func Import(databaseLabel string, filePath string, mongoCollection string, mongoDatabase string, wipeFirst bool, settings *models.Settings) {
	helpers.SignIn(settings)
	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		fmt.Printf("A file does not exist at path '%s'\n", filePath)
		os.Exit(1)
	}
	service := helpers.RetrieveServiceByLabel(databaseLabel, settings)
	if service == nil {
		fmt.Printf("Could not find a service with the label \"%s\"\n", databaseLabel)
		os.Exit(1)
	}
	env := helpers.RetrieveEnvironment("spec", settings)
	pod := helpers.RetrievePodMetadata(env.PodID, settings)
	fmt.Printf("Importing '%s' into %s (ID = %s)\n", filePath, databaseLabel, service.ID)
	key := make([]byte, 32)
	iv := make([]byte, aes.BlockSize)
	rand.Read(key)
	rand.Read(iv)
	fmt.Println("Encrypting...")
	encrFilePath := helpers.EncryptFile(filePath, key, iv, pod.ImportRequiresLength)
	defer os.Remove(encrFilePath)
	options := map[string]string{}
	if mongoCollection != "" {
		options["mongoCollection"] = mongoCollection
	}
	if mongoDatabase != "" {
		options["mongoDatabase"] = mongoDatabase
	}
	fmt.Println("Uploading...")
	tempURL := helpers.RetrieveTempUploadURL(service.ID, settings)

	task := helpers.InitiateImport(tempURL.URL, encrFilePath, string(helpers.Base64Encode(helpers.Hex(key))), string(helpers.Base64Encode(helpers.Hex(iv))), options, wipeFirst, service.ID, settings)
	fmt.Printf("Processing import... (task ID = %s)\n", task.ID)

	ch := make(chan string, 1)
	go helpers.PollTaskStatus(task.ID, ch, settings)
	status := <-ch
	task.Status = status
	fmt.Printf("\nImport complete (end status = '%s')\n", task.Status)
	helpers.DumpLogs(service, task, "restore", settings)
	if task.Status != "finished" {
		os.Exit(1)
	}
}
Beispiel #5
0
// ListBackups lists the created backups for the service sorted from oldest to newest
func ListBackups(serviceLabel string, page int, pageSize int, settings *models.Settings) {
	helpers.SignIn(settings)
	service := helpers.RetrieveServiceByLabel(serviceLabel, settings)
	if service == nil {
		fmt.Printf("Could not find a service with the label \"%s\"\n", serviceLabel)
		os.Exit(1)
	}
	jobs := helpers.ListBackups(service.ID, page, pageSize, settings)
	sort.Sort(SortedJobs(*jobs))
	for _, job := range *jobs {
		fmt.Printf("%s %s (status = %s)\n", job.ID, job.CreatedAt, job.Status)
	}
	if len(*jobs) == pageSize && page == 1 {
		fmt.Println("(for older backups, try with --page 2 or adjust --page-size)")
	}
	if len(*jobs) == 0 && page == 1 {
		fmt.Println("No backups created yet for this service.")
	}
}
Beispiel #6
0
// CreateBackup a new backup
func CreateBackup(serviceLabel string, skipPoll bool, settings *models.Settings) {
	helpers.SignIn(settings)
	service := helpers.RetrieveServiceByLabel(serviceLabel, settings)
	if service == nil {
		fmt.Printf("Could not find a service with the label \"%s\"\n", serviceLabel)
		os.Exit(1)
	}
	task := helpers.CreateBackup(service.ID, settings)
	fmt.Printf("Backup started (task ID = %s)\n", task.ID)
	if !skipPoll {
		fmt.Print("Polling until backup finishes.")
		ch := make(chan string, 1)
		go helpers.PollTaskStatus(task.ID, ch, settings)
		status := <-ch
		task.Status = status
		fmt.Printf("\nEnded in status '%s'\n", task.Status)
		helpers.DumpLogs(service, task, "backup", settings)
		if task.Status != "finished" {
			os.Exit(1)
		}
	}
}
Beispiel #7
0
// Metrics prints out metrics for a given service or if the service is not
// specified, metrics for the entire environment are printed.
func Metrics(serviceLabel string, jsonFlag bool, csvFlag bool, sparkFlag bool, streamFlag bool, mins int, settings *models.Settings) {
	if streamFlag && (jsonFlag || csvFlag || mins != 1) {
		fmt.Println("--stream cannot be used with a custom format and multiple records")
		os.Exit(1)
	}
	var singleRetriever func(mins int, settings *models.Settings) *models.Metrics
	if serviceLabel != "" {
		service := helpers.RetrieveServiceByLabel(serviceLabel, settings)
		if service == nil {
			fmt.Printf("Could not find a service with the label \"%s\"\n", serviceLabel)
			os.Exit(1)
		}
		settings.ServiceID = service.ID
		singleRetriever = helpers.RetrieveServiceMetrics
	}
	var transformer Transformer
	redraw := make(chan bool)
	if jsonFlag {
		transformer = Transformer{
			SingleRetriever: singleRetriever,
			DataTransformer: &JSONTransformer{},
		}
	} else if csvFlag {
		buffer := &bytes.Buffer{}
		transformer = Transformer{
			SingleRetriever: singleRetriever,
			DataTransformer: &CSVTransformer{
				HeadersWritten: false,
				GroupMode:      serviceLabel == "",
				Buffer:         buffer,
				Writer:         csv.NewWriter(buffer),
			},
		}
	} else if sparkFlag {
		// the spark lines interface stays up until closed by the user, so
		// we might as well keep updating it as long as it is there
		streamFlag = true
		mins = 60
		err := ui.Init()
		if err != nil {
			fmt.Println(err.Error())
			os.Exit(1)
		}
		defer ui.Close()
		ui.UseTheme("helloworld")

		p := ui.NewPar("PRESS q TO QUIT")
		p.HasBorder = false
		p.TextFgColor = ui.Theme().SparklineTitle
		ui.Body.AddRows(
			ui.NewRow(ui.NewCol(12, 0, p)),
		)

		transformer = Transformer{
			SingleRetriever: singleRetriever,
			DataTransformer: &SparkTransformer{
				Redraw:     redraw,
				SparkLines: make(map[string]*ui.Sparklines),
			},
		}
	} else {
		transformer = Transformer{
			SingleRetriever: singleRetriever,
			DataTransformer: &TextTransformer{},
		}
	}
	transformer.GroupRetriever = helpers.RetrieveEnvironmentMetrics
	transformer.Stream = streamFlag
	transformer.GroupMode = serviceLabel == ""
	transformer.Mins = mins
	transformer.settings = settings

	helpers.SignIn(settings)

	if sparkFlag {
		go transformer.process()

		ui.Body.Align()
		ui.Render(ui.Body)

		quit := make(chan bool)
		go maintainSparkLines(redraw, quit)
		<-quit
	} else {
		transformer.process()
	}
}