From e3acd4ae9cf2bb9043c2dfeb94a2b18191138f56 Mon Sep 17 00:00:00 2001
From: lijianguo <lijianguo1991@outlook.com>
Date: Tue, 25 Feb 2025 17:46:32 +0800
Subject: [PATCH] chore(builds): fetch support preview and save to local

---
 cmd/builds.go         | 99 ++++++++++++++++++++++++++-----------------
 cmd/builds_test.go    |  4 +-
 cmd/cmd.go            |  2 +-
 cmd/config.go         |  4 +-
 cmd/keys_test.go      |  6 +--
 parser/builds.go      | 11 +++--
 parser/builds_test.go |  2 +-
 7 files changed, 77 insertions(+), 51 deletions(-)

diff --git a/cmd/builds.go b/cmd/builds.go
index defa0858..1c8e3833 100644
--- a/cmd/builds.go
+++ b/cmd/builds.go
@@ -34,26 +34,6 @@ func (d *DryccCmd) BuildsInfo(appID string, version int) error {
 	table.Append([]string{"Updated:", build.Updated})
 	table.Render()
 
-	if len(build.Dockerfile) != 0 {
-		table = d.getDefaultFormatTable([]string{})
-		table.Append([]string{"Dockerfile:"})
-		table.Append([]string{d.indentString(build.Dockerfile, 2)})
-		table.Render()
-	}
-
-	if len(build.Procfile) != 0 {
-		table = d.getDefaultFormatTable([]string{})
-		table.Append([]string{"Procfile:"})
-		table.Append([]string{d.indentString(d.toYamlString(build.Procfile, 2), 2)})
-		table.Render()
-	}
-
-	if len(build.Dryccfile) != 0 {
-		table = d.getDefaultFormatTable([]string{})
-		table.Append([]string{"Dryccfile:"})
-		table.Append([]string{d.indentString(d.toYamlString(build.Dryccfile, 2), 2)})
-		table.Render()
-	}
 	return nil
 }
 
@@ -102,7 +82,7 @@ func (d *DryccCmd) BuildsCreate(appID, image, stack, procfile, dryccpath, confir
 	return nil
 }
 
-func (d *DryccCmd) BuildsFetch(appID string, version int, procfile, dryccpath, confirm string) error {
+func (d *DryccCmd) BuildsFetch(appID string, version int, procfile, dryccpath, confirm string, save bool) error {
 	s, appID, err := load(d.ConfigFile, appID)
 	if err != nil {
 		return err
@@ -113,33 +93,66 @@ func (d *DryccCmd) BuildsFetch(appID string, version int, procfile, dryccpath, c
 		return err
 	}
 	// Confirm again
-	err = buildFetchConfirmAction(confirm, procfile, dryccpath)
+	dockerfile := "Dockerfile"
+	err = buildFetchConfirmAction(confirm, dockerfile, procfile, dryccpath, save)
 	if err != nil {
 		return err
 	}
-	os.Remove(procfile)
-	os.ReadDir(dryccpath)
+	if len(build.Dockerfile) != 0 {
+		err := writeDockerfileToPath(d, dockerfile, build.Dockerfile, save)
+		if err != nil {
+			return fmt.Errorf("failed to write Dockerfile: %w", err)
+		}
+	}
+
 	if len(build.Procfile) != 0 {
-		err := os.WriteFile(procfile, []byte(d.toYamlString(build.Procfile, 2)), 0755)
+		err := writeProcfileToPath(d, procfile, build.Procfile, save)
 		if err != nil {
 			return fmt.Errorf("failed to write Procfile: %w", err)
 		}
 	}
 
 	if len(build.Dryccfile) != 0 {
-		err := writeDryccfileToPath(dryccpath, build.Dryccfile)
+		err := writeDryccfileToPath(d, dryccpath, build.Dryccfile, save)
 		if err != nil {
 			return fmt.Errorf("failed to write Dryccfile: %w", err)
 		}
 	}
-	d.Println("done")
+	if save {
+		d.Println("done")
+	}
+	return nil
+}
 
+func writeDockerfileToPath(d *DryccCmd, dockerfile string, dockerinfo string, save bool) error {
+	if save {
+		os.Remove(dockerfile)
+		err := os.WriteFile(dockerfile, []byte(dockerinfo), 0664)
+		return err
+	}
+	d.Println("# Source:", dockerfile)
+	d.Println(dockerinfo)
 	return nil
 
 }
 
-func writeDryccfileToPath(dryccpath string, dryccfile map[string]interface{}) error {
+func writeProcfileToPath(d *DryccCmd, procfile string, Procinfo map[string]string, save bool) error {
+	if save {
+		os.Remove(procfile)
+		err := os.WriteFile(procfile, []byte(d.toYamlString(Procinfo, 2)), 0664)
+		return err
+	}
+	d.Println("# Source:", procfile)
+	d.Println(d.toYamlString(Procinfo, 2))
+	return nil
+}
+
+func writeDryccfileToPath(d *DryccCmd, dryccpath string, dryccfile map[string]interface{}, save bool) error {
 	// Create the directory if it doesn't exist
+	if save {
+		os.Remove(dryccpath)
+	}
+	os.ReadDir(dryccpath)
 	err := os.MkdirAll(dryccpath, os.ModePerm)
 	if err != nil {
 		return fmt.Errorf("failed to create directory: %w", err)
@@ -161,9 +174,14 @@ func writeDryccfileToPath(dryccpath string, dryccfile map[string]interface{}) er
 				content += fmt.Sprintf("%s=%v\n", k, v)
 			}
 			// Write accumulated content once
-			err := os.WriteFile(envFilePath, []byte(content), 0755)
-			if err != nil {
-				return fmt.Errorf("failed to write env file: %w", err)
+			if save {
+				err := os.WriteFile(envFilePath, []byte(content), 0664)
+				if err != nil {
+					return fmt.Errorf("failed to write env file: %w", err)
+				}
+			} else {
+				d.Println("# Source:", envFilePath)
+				d.Println(content)
 			}
 		}
 	}
@@ -179,9 +197,14 @@ func writeDryccfileToPath(dryccpath string, dryccfile map[string]interface{}) er
 				return fmt.Errorf("failed to marshal pipeline config: %w", err)
 			}
 			yamlContent := buf.Bytes()
-			err = os.WriteFile(filePath, yamlContent, 0755)
-			if err != nil {
-				return fmt.Errorf("failed to write pipeline file: %w", err)
+			if save {
+				err = os.WriteFile(filePath, yamlContent, 0664)
+				if err != nil {
+					return fmt.Errorf("failed to write pipeline file: %w", err)
+				}
+			} else {
+				d.Println("# Source:", filePath)
+				d.Println(string(yamlContent))
 			}
 		}
 	}
@@ -215,13 +238,13 @@ func buildConfirmAction(c *drycc.Client, appID string, procfileMap map[string]st
 	return nil
 }
 
-func buildFetchConfirmAction(confirm, procfile, dryccpath string) error {
+func buildFetchConfirmAction(confirm, dockerfile, procfile, dryccpath string, save bool) error {
 
-	if confirm == "" || confirm != "yes" {
+	if save && (confirm == "" || confirm != "yes") {
 		// hint
 		msg := fmt.Sprintf(" !    WARNING: Potentially Build Fetch Action\n"+
-			" !    This operation will first rm the current \x1b[1m%s\x1b[0m and \x1b[1m%s\x1b[0m locally\n"+
-			" !    To proceed, type \"yes\" !\n\n> ", procfile, dryccpath)
+			" !    This operation will overwrite the current \x1b[1m%s\x1b[0m, \x1b[1m%s\x1b[0m or \x1b[1m%s\x1b[0m locally\n"+
+			" !    To proceed, type \"yes\" !\n\n> ", dockerfile, procfile, dryccpath)
 
 		fmt.Print(msg)
 		fmt.Scanln(&confirm)
diff --git a/cmd/builds_test.go b/cmd/builds_test.go
index 7336dbec..47c218e3 100644
--- a/cmd/builds_test.go
+++ b/cmd/builds_test.go
@@ -266,7 +266,7 @@ func TestBuildsFetch(t *testing.T) {
 			w.Write([]byte("yes\n"))
 		}()
 
-		err := cmdr.BuildsFetch("testapp", 0, procfilePath, dryccpath, "")
+		err := cmdr.BuildsFetch("testapp", 0, procfilePath, dryccpath, "", true)
 		if err != nil {
 			t.Fatalf("expected no error, got %v", err)
 		}
@@ -319,7 +319,7 @@ func TestBuildsFetch(t *testing.T) {
 			w.Write([]byte("no\n"))
 		}()
 
-		err := cmdr.BuildsFetch("testapp", 0, procfilePath, dryccpath, "")
+		err := cmdr.BuildsFetch("testapp", 0, procfilePath, dryccpath, "", true)
 		if err == nil || err.Error() != "cancel the build create fetch" {
 			t.Fatalf("expected cancellation error, got %v", err)
 		}
diff --git a/cmd/cmd.go b/cmd/cmd.go
index 689421f6..571e2200 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -36,7 +36,7 @@ type Commander interface {
 	TokensRemove(string, string) error
 	BuildsInfo(string, int) error
 	BuildsCreate(string, string, string, string, string, string) error
-	BuildsFetch(string, int, string, string, string) error
+	BuildsFetch(string, int, string, string, string, bool) error
 	CertsList(string, int) error
 	CertAdd(string, string, string, string) error
 	CertRemove(string, string) error
diff --git a/cmd/config.go b/cmd/config.go
index 6e81dfd6..ec7962ac 100644
--- a/cmd/config.go
+++ b/cmd/config.go
@@ -224,9 +224,9 @@ func (d *DryccCmd) ConfigPull(appID, ptype, group, fileName string, interactive
 				configMap[value.Name] = value.Value
 			}
 		}
-		return os.WriteFile(fileName, []byte(formatEnv(configMap)), 0755)
+		return os.WriteFile(fileName, []byte(formatEnv(configMap)), 0664)
 	}
-	return os.WriteFile(fileName, []byte(formatConfig(configValues)), 0755)
+	return os.WriteFile(fileName, []byte(formatConfig(configValues)), 0664)
 }
 
 // ConfigPush pushes an app's config from a file.
diff --git a/cmd/keys_test.go b/cmd/keys_test.go
index de4735cb..7cb419db 100644
--- a/cmd/keys_test.go
+++ b/cmd/keys_test.go
@@ -88,7 +88,7 @@ func TestListKeys(t *testing.T) {
 
 	folder := filepath.Join(name, ".ssh")
 
-	err = os.Mkdir(folder, 0755)
+	err = os.Mkdir(folder, 0600)
 	assert.NoError(t, err)
 
 	toWrite := []byte("ssh-rsa abc test@example.com")
@@ -302,7 +302,7 @@ func TestKeyAdd(t *testing.T) {
 	assert.NoError(t, err)
 	settings.SetHome(name)
 	folder := filepath.Join(name, ".ssh")
-	err = os.Mkdir(folder, 0755)
+	err = os.Mkdir(folder, 0600)
 	assert.NoError(t, err)
 
 	cf, server, err := testutil.NewTestServerAndClient()
@@ -348,7 +348,7 @@ func TestKeyAddName(t *testing.T) {
 	assert.NoError(t, err)
 	settings.SetHome(name)
 	folder := filepath.Join(name, ".ssh")
-	err = os.Mkdir(folder, 0755)
+	err = os.Mkdir(folder, 0600)
 	assert.NoError(t, err)
 
 	cf, server, err := testutil.NewTestServerAndClient()
diff --git a/parser/builds.go b/parser/builds.go
index fe3d9d3c..56215b12 100644
--- a/parser/builds.go
+++ b/parser/builds.go
@@ -114,7 +114,7 @@ Options:
 
 func buildsFetch(argv []string, cmdr cmd.Commander) error {
 	usage := `
-Print information about a specific build.
+Print process info about a specific build.
 
 Usage: drycc builds:fetch [options]
 
@@ -124,11 +124,13 @@ Options:
   -v --version=<version>
     the version for which the build info needs to be fetched.
   -p --procfile=<procfile>
-    a YAML file used to supply a Procfile to the application.
+    the filename of the procfile saved locally, default is 'Procfile'.
   -d --dryccpath=<dryccpath>
-    drycc config path to the application, default is '.drycc'.
+    the folder name of the dryccfile saved locally, default is '.drycc'.
   --confirm=yes
     to proceed, type "yes".
+  --save
+    save process info to the local.
 `
 
 	args, err := docopt.ParseArgs(usage, argv, "")
@@ -147,7 +149,8 @@ Options:
 
 	procfile := safeGetValue(args, "--procfile", "Procfile")
 	dryccpath := safeGetValue(args, "--dryccpath", ".drycc")
+	save := args["--save"].(bool)
 	confirm := safeGetString(args, "--confirm")
 
-	return cmdr.BuildsFetch(app, version, procfile, dryccpath, confirm)
+	return cmdr.BuildsFetch(app, version, procfile, dryccpath, confirm, save)
 }
diff --git a/parser/builds_test.go b/parser/builds_test.go
index b28cf268..41987006 100644
--- a/parser/builds_test.go
+++ b/parser/builds_test.go
@@ -17,7 +17,7 @@ func (d FakeDryccCmd) BuildsCreate(string, string, string, string, string, strin
 	return errors.New("builds:create")
 }
 
-func (d FakeDryccCmd) BuildsFetch(string, int, string, string, string) error {
+func (d FakeDryccCmd) BuildsFetch(string, int, string, string, string, bool) error {
 	return errors.New("builds:fetch")
 }
 
