func runExport(args *docopt.Args, client *controller.Client) error { var dest io.Writer = os.Stdout if filename := args.String["--file"]; filename != "" { f, err := os.Create(filename) if err != nil { return fmt.Errorf("error creating export file: %s", err) } defer f.Close() dest = f } app, err := client.GetApp(mustApp()) if err != nil { return fmt.Errorf("error getting app: %s", err) } tw := backup.NewTarWriter(app.Name, dest) defer tw.Close() if err := tw.WriteJSON("app.json", app); err != nil { return fmt.Errorf("error exporting app: %s", err) } routes, err := client.RouteList(mustApp()) if err != nil { return fmt.Errorf("error getting routes: %s", err) } if err := tw.WriteJSON("routes.json", routes); err != nil { return fmt.Errorf("error exporting routes: %s", err) } release, err := client.GetAppRelease(mustApp()) if err != nil && err != controller.ErrNotFound { return fmt.Errorf("error retrieving app: %s", err) } else if err == nil { // Do not allow the exporting of passwords. delete(release.Env, "REDIS_PASSWORD") if err := tw.WriteJSON("release.json", release); err != nil { return fmt.Errorf("error exporting release: %s", err) } } artifact, err := client.GetArtifact(release.ArtifactID) if err != nil && err != controller.ErrNotFound { return fmt.Errorf("error retrieving artifact: %s", err) } else if err == nil { if err := tw.WriteJSON("artifact.json", artifact); err != nil { return fmt.Errorf("error exporting artifact: %s", err) } } formation, err := client.GetFormation(mustApp(), release.ID) if err != nil && err != controller.ErrNotFound { return fmt.Errorf("error retrieving formation: %s", err) } else if err == nil { if err := tw.WriteJSON("formation.json", formation); err != nil { return fmt.Errorf("error exporting formation: %s", err) } } var bar *pb.ProgressBar if !args.Bool["--quiet"] && term.IsTerminal(os.Stderr.Fd()) { bar = pb.New(0) bar.SetUnits(pb.U_BYTES) bar.ShowBar = false bar.ShowSpeed = true bar.Output = os.Stderr bar.Start() defer bar.Finish() } if slug, ok := release.Env["SLUG_URL"]; ok { reqR, reqW := io.Pipe() config := runConfig{ App: mustApp(), Release: release.ID, DisableLog: true, Entrypoint: []string{"curl"}, Args: []string{"--include", "--raw", slug}, Stdout: reqW, Stderr: ioutil.Discard, } if bar != nil { config.Stdout = io.MultiWriter(config.Stdout, bar) } go func() { if err := runJob(client, config); err != nil { shutdown.Fatalf("error retrieving slug: %s", err) } }() res, err := http.ReadResponse(bufio.NewReader(reqR), nil) if err != nil { return fmt.Errorf("error reading slug response: %s", err) } if res.StatusCode != 200 { return fmt.Errorf("unexpected status getting slug: %d", res.StatusCode) } length, err := strconv.Atoi(res.Header.Get("Content-Length")) if err != nil { return fmt.Errorf("slug has missing or malformed Content-Length") } if err := tw.WriteHeader("slug.tar.gz", length); err != nil { return fmt.Errorf("error writing slug header: %s", err) } if _, err := io.Copy(tw, res.Body); err != nil { return fmt.Errorf("error writing slug: %s", err) } res.Body.Close() } if config, err := getAppPgRunConfig(client); err == nil { configPgDump(config) if err := tw.WriteCommandOutput(client, "postgres.dump", config.App, &ct.NewJob{ ReleaseID: config.Release, Entrypoint: config.Entrypoint, Cmd: config.Args, Env: config.Env, DisableLog: config.DisableLog, }); err != nil { return fmt.Errorf("error creating postgres dump: %s", err) } } return nil }
func runExport(args *docopt.Args, client controller.Client) error { var dest io.Writer = os.Stdout if filename := args.String["--file"]; filename != "" { f, err := os.Create(filename) if err != nil { return fmt.Errorf("error creating export file: %s", err) } defer f.Close() dest = f } app, err := client.GetApp(mustApp()) if err != nil { return fmt.Errorf("error getting app: %s", err) } var bar backup.ProgressBar if !args.Bool["--quiet"] && term.IsTerminal(os.Stderr.Fd()) { b := pb.New(0) b.SetUnits(pb.U_BYTES) b.ShowBar = false b.ShowSpeed = true b.Output = os.Stderr b.Start() defer b.Finish() bar = b } tw := backup.NewTarWriter(app.Name, dest, bar) defer tw.Close() if err := tw.WriteJSON("app.json", app); err != nil { return fmt.Errorf("error exporting app: %s", err) } routes, err := client.RouteList(mustApp()) if err != nil { return fmt.Errorf("error getting routes: %s", err) } if err := tw.WriteJSON("routes.json", routes); err != nil { return fmt.Errorf("error exporting routes: %s", err) } release, err := client.GetAppRelease(mustApp()) if err == controller.ErrNotFound { // if the app has no release then there is nothing more to export return nil } else if err != nil { return fmt.Errorf("error retrieving app: %s", err) } else if err == nil { // Do not allow the exporting of passwords. delete(release.Env, "REDIS_PASSWORD") if err := tw.WriteJSON("release.json", release); err != nil { return fmt.Errorf("error exporting release: %s", err) } } var artifact *ct.Artifact if artifactID := release.ImageArtifactID(); artifactID != "" { artifact, err = client.GetArtifact(artifactID) if err != nil && err != controller.ErrNotFound { return fmt.Errorf("error retrieving artifact: %s", err) } else if err == nil { if err := tw.WriteJSON("artifact.json", artifact); err != nil { return fmt.Errorf("error exporting artifact: %s", err) } } } formation, err := client.GetFormation(mustApp(), release.ID) if err != nil && err != controller.ErrNotFound { return fmt.Errorf("error retrieving formation: %s", err) } else if err == nil { if err := tw.WriteJSON("formation.json", formation); err != nil { return fmt.Errorf("error exporting formation: %s", err) } } // if the release was deployed via docker-receive, pull the docker // image and add it to the export using "docker save" if release.IsDockerReceiveDeploy() && artifact != nil { cluster, err := getCluster() if err != nil { return err } host, err := cluster.DockerPushHost() if err != nil { return err } // the artifact will have an internal discoverd URL which will // not work if the Docker daemon is outside the cluster, so // generate a reference using the configured DockerPushURL repo := artifact.Meta["docker-receive.repository"] digest := artifact.Meta["docker-receive.digest"] ref := fmt.Sprintf("%s/%s@%s", host, repo, digest) // pull the Docker image cmd := exec.Command("docker", "pull", ref) log.Printf("flynn: pulling Docker image with %q", strings.Join(cmd.Args, " ")) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return err } // give the image an explicit, random tag so that "docker save" // will export an image that we can reference on import (just // using the digest is not enough as "docker inspect" only // works with tags) tag := fmt.Sprintf("%s:flynn-export-%s", repo, random.String(8)) if out, err := exec.Command("docker", "tag", "--force", ref, tag).CombinedOutput(); err != nil { return fmt.Errorf("error tagging docker image: %s: %q", err, out) } defer exec.Command("docker", "rmi", tag).Run() if err := dockerSave(tag, tw, bar); err != nil { return fmt.Errorf("error exporting docker image: %s", err) } // add the tag to the backup so we know how to reference the // image once it has been imported config := struct { Tag string `json:"tag"` }{tag} if err := tw.WriteJSON("docker-image.json", &config); err != nil { return fmt.Errorf("error exporting docker image: %s", err) } } // expect releases deployed via git to have a slug as their first file // artifact, and legacy releases to have SLUG_URL set var slugURL string if release.IsGitDeploy() && len(release.FileArtifactIDs()) > 0 { slugArtifact, err := client.GetArtifact(release.FileArtifactIDs()[0]) if err != nil && err != controller.ErrNotFound { return fmt.Errorf("error retrieving slug artifact: %s", err) } else if err == nil { slugURL = slugArtifact.URI } } else if u, ok := release.Env["SLUG_URL"]; ok { slugURL = u } if slugURL != "" { reqR, reqW := io.Pipe() config := runConfig{ App: mustApp(), Release: release.ID, DisableLog: true, Args: []string{"curl", "--include", "--location", "--raw", slugURL}, Stdout: reqW, Stderr: ioutil.Discard, } if bar != nil { config.Stdout = io.MultiWriter(config.Stdout, bar) } go func() { if err := runJob(client, config); err != nil { shutdown.Fatalf("error retrieving slug: %s", err) } }() req := bufio.NewReader(reqR) var res *http.Response maxRedirects := 5 for i := 0; i < maxRedirects; i++ { res, err = http.ReadResponse(req, nil) if err != nil { return fmt.Errorf("error reading slug response: %s", err) } if res.StatusCode != http.StatusFound { break } } if res.StatusCode != http.StatusOK { return fmt.Errorf("unexpected status getting slug: %d", res.StatusCode) } length, err := strconv.Atoi(res.Header.Get("Content-Length")) if err != nil { return fmt.Errorf("slug has missing or malformed Content-Length") } if err := tw.WriteHeader("slug.tar.gz", length); err != nil { return fmt.Errorf("error writing slug header: %s", err) } if _, err := io.Copy(tw, res.Body); err != nil { return fmt.Errorf("error writing slug: %s", err) } res.Body.Close() } if pgConfig, err := getAppPgRunConfig(client); err == nil { configPgDump(pgConfig) if err := tw.WriteCommandOutput(client, "postgres.dump", pgConfig.App, &ct.NewJob{ ReleaseID: pgConfig.Release, Args: pgConfig.Args, Env: pgConfig.Env, DisableLog: pgConfig.DisableLog, }); err != nil { return fmt.Errorf("error creating postgres dump: %s", err) } } if mysqlConfig, err := getAppMysqlRunConfig(client); err == nil { configMysqlDump(mysqlConfig) if err := tw.WriteCommandOutput(client, "mysql.dump", mysqlConfig.App, &ct.NewJob{ ReleaseID: mysqlConfig.Release, Args: mysqlConfig.Args, Env: mysqlConfig.Env, DisableLog: mysqlConfig.DisableLog, }); err != nil { return fmt.Errorf("error creating mysql dump: %s", err) } } return nil }
func runExport(args *docopt.Args, client controller.Client) error { var dest io.Writer = os.Stdout if filename := args.String["--file"]; filename != "" { f, err := os.Create(filename) if err != nil { return fmt.Errorf("error creating export file: %s", err) } defer f.Close() dest = f } app, err := client.GetApp(mustApp()) if err != nil { return fmt.Errorf("error getting app: %s", err) } var bar backup.ProgressBar if !args.Bool["--quiet"] && term.IsTerminal(os.Stderr.Fd()) { b := pb.New(0) b.SetUnits(pb.U_BYTES) b.ShowBar = false b.ShowSpeed = true b.Output = os.Stderr b.Start() defer b.Finish() bar = b } tw := backup.NewTarWriter(app.Name, dest, bar) defer tw.Close() if err := tw.WriteJSON("app.json", app); err != nil { return fmt.Errorf("error exporting app: %s", err) } routes, err := client.RouteList(mustApp()) if err != nil { return fmt.Errorf("error getting routes: %s", err) } if err := tw.WriteJSON("routes.json", routes); err != nil { return fmt.Errorf("error exporting routes: %s", err) } release, err := client.GetAppRelease(mustApp()) if err == controller.ErrNotFound { // if the app has no release then there is nothing more to export return nil } else if err != nil { return fmt.Errorf("error retrieving app: %s", err) } else if err == nil { // Do not allow the exporting of passwords. delete(release.Env, "REDIS_PASSWORD") if err := tw.WriteJSON("release.json", release); err != nil { return fmt.Errorf("error exporting release: %s", err) } } if artifactID := release.ImageArtifactID(); artifactID != "" { artifact, err := client.GetArtifact(artifactID) if err != nil && err != controller.ErrNotFound { return fmt.Errorf("error retrieving artifact: %s", err) } else if err == nil { if err := tw.WriteJSON("artifact.json", artifact); err != nil { return fmt.Errorf("error exporting artifact: %s", err) } } } formation, err := client.GetFormation(mustApp(), release.ID) if err != nil && err != controller.ErrNotFound { return fmt.Errorf("error retrieving formation: %s", err) } else if err == nil { if err := tw.WriteJSON("formation.json", formation); err != nil { return fmt.Errorf("error exporting formation: %s", err) } } // expect releases deployed via git to have a slug as their first file // artifact, and legacy releases to have SLUG_URL set var slugURL string if release.IsGitDeploy() && len(release.FileArtifactIDs()) > 0 { slugArtifact, err := client.GetArtifact(release.FileArtifactIDs()[0]) if err != nil && err != controller.ErrNotFound { return fmt.Errorf("error retrieving slug artifact: %s", err) } else if err == nil { slugURL = slugArtifact.URI } } else if u, ok := release.Env["SLUG_URL"]; ok { slugURL = u } if slugURL != "" { reqR, reqW := io.Pipe() config := runConfig{ App: mustApp(), Release: release.ID, DisableLog: true, Entrypoint: []string{"curl"}, Args: []string{"--include", "--raw", slugURL}, Stdout: reqW, Stderr: ioutil.Discard, } if bar != nil { config.Stdout = io.MultiWriter(config.Stdout, bar) } go func() { if err := runJob(client, config); err != nil { shutdown.Fatalf("error retrieving slug: %s", err) } }() res, err := http.ReadResponse(bufio.NewReader(reqR), nil) if err != nil { return fmt.Errorf("error reading slug response: %s", err) } if res.StatusCode != 200 { return fmt.Errorf("unexpected status getting slug: %d", res.StatusCode) } length, err := strconv.Atoi(res.Header.Get("Content-Length")) if err != nil { return fmt.Errorf("slug has missing or malformed Content-Length") } if err := tw.WriteHeader("slug.tar.gz", length); err != nil { return fmt.Errorf("error writing slug header: %s", err) } if _, err := io.Copy(tw, res.Body); err != nil { return fmt.Errorf("error writing slug: %s", err) } res.Body.Close() } if pgConfig, err := getAppPgRunConfig(client); err == nil { configPgDump(pgConfig) if err := tw.WriteCommandOutput(client, "postgres.dump", pgConfig.App, &ct.NewJob{ ReleaseID: pgConfig.Release, Entrypoint: pgConfig.Entrypoint, Cmd: pgConfig.Args, Env: pgConfig.Env, DisableLog: pgConfig.DisableLog, }); err != nil { return fmt.Errorf("error creating postgres dump: %s", err) } } if mysqlConfig, err := getAppMysqlRunConfig(client); err == nil { configMysqlDump(mysqlConfig) if err := tw.WriteCommandOutput(client, "mysql.dump", mysqlConfig.App, &ct.NewJob{ ReleaseID: mysqlConfig.Release, Entrypoint: mysqlConfig.Entrypoint, Cmd: mysqlConfig.Args, Env: mysqlConfig.Env, DisableLog: mysqlConfig.DisableLog, }); err != nil { return fmt.Errorf("error creating mysql dump: %s", err) } } return nil }