github.com/supabase/cli@v1.168.1/internal/utils/container_output.go (about) 1 package utils 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/json" 7 "fmt" 8 "io" 9 "os" 10 "regexp" 11 "strconv" 12 "strings" 13 14 "github.com/docker/docker/pkg/jsonmessage" 15 "github.com/docker/docker/pkg/stdcopy" 16 "github.com/go-errors/errors" 17 ) 18 19 func ProcessPullOutput(out io.ReadCloser, p Program) error { 20 dec := json.NewDecoder(out) 21 22 downloads := make(map[string]struct{ current, total int64 }) 23 24 for { 25 var progress jsonmessage.JSONMessage 26 27 if err := dec.Decode(&progress); err == io.EOF { 28 break 29 } else if err != nil { 30 return err 31 } 32 33 if strings.HasPrefix(progress.Status, "Pulling from") { 34 p.Send(StatusMsg(progress.Status + "...")) 35 } else if progress.Status == "Pulling fs layer" || progress.Status == "Waiting" { 36 downloads[progress.ID] = struct{ current, total int64 }{ 37 current: 0, 38 total: 0, 39 } 40 } else if progress.Status == "Downloading" { 41 downloads[progress.ID] = struct{ current, total int64 }{ 42 current: progress.Progress.Current, 43 total: progress.Progress.Total, 44 } 45 46 var overallProgress float64 47 for _, percentage := range downloads { 48 if percentage.total > 0 { 49 progress := float64(percentage.current) / float64(percentage.total) 50 overallProgress += progress / float64(len(downloads)) 51 } 52 } 53 54 p.Send(ProgressMsg(&overallProgress)) 55 } 56 } 57 58 p.Send(ProgressMsg(nil)) 59 60 return nil 61 } 62 63 type DiffStream struct { 64 o bytes.Buffer 65 r *io.PipeReader 66 w *io.PipeWriter 67 p Program 68 } 69 70 func NewDiffStream(p Program) *DiffStream { 71 r, w := io.Pipe() 72 go func() { 73 if err := ProcessDiffProgress(p, r); err != nil { 74 fmt.Fprintln(os.Stderr, err) 75 } 76 }() 77 return &DiffStream{r: r, w: w, p: p} 78 } 79 80 func (c DiffStream) Stdout() io.Writer { 81 return &c.o 82 } 83 84 func (c DiffStream) Stderr() io.Writer { 85 return c.w 86 } 87 88 func (c DiffStream) Collect() ([]byte, error) { 89 if err := c.w.Close(); err != nil { 90 fmt.Fprintln(os.Stderr, "Failed to close stream:", err) 91 } 92 return ProcessDiffOutput(c.o.Bytes()) 93 } 94 95 func ProcessDiffProgress(p Program, out io.Reader) error { 96 scanner := bufio.NewScanner(out) 97 re := regexp.MustCompile(`(.*)([[:digit:]]{2,3})%`) 98 for scanner.Scan() { 99 line := scanner.Text() 100 101 if line == "Starting schema diff..." { 102 percentage := 0.0 103 p.Send(ProgressMsg(&percentage)) 104 } 105 106 matches := re.FindStringSubmatch(line) 107 if len(matches) != 3 { 108 // TODO: emit actual error statements 109 continue 110 } 111 112 p.Send(StatusMsg(matches[1])) 113 percentage, err := strconv.ParseFloat(matches[2], 64) 114 if err != nil { 115 continue 116 } 117 percentage = percentage / 100 118 p.Send(ProgressMsg(&percentage)) 119 } 120 p.Send(ProgressMsg(nil)) 121 return scanner.Err() 122 } 123 124 type DiffDependencies struct { 125 Type string `json:"type"` 126 } 127 128 type DiffEntry struct { 129 Type string `json:"type"` 130 Status string `json:"status"` 131 DiffDdl string `json:"diff_ddl"` 132 GroupName string `json:"group_name"` 133 Dependencies []DiffDependencies `json:"dependencies"` 134 SourceSchemaName *string `json:"source_schema_name"` 135 } 136 137 const diffHeader = `-- This script was generated by the Schema Diff utility in pgAdmin 4 138 -- For the circular dependencies, the order in which Schema Diff writes the objects is not very sophisticated 139 -- and may require manual changes to the script to ensure changes are applied in the correct order. 140 -- Please report an issue for any failure with the reproduction steps.` 141 142 func ProcessDiffOutput(diffBytes []byte) ([]byte, error) { 143 // TODO: Remove when https://github.com/supabase/pgadmin4/issues/24 is fixed. 144 diffBytes = bytes.TrimPrefix(diffBytes, []byte("NOTE: Configuring authentication for DESKTOP mode.\n")) 145 146 if len(diffBytes) == 0 { 147 return diffBytes, nil 148 } 149 150 var diffJson []DiffEntry 151 if err := json.Unmarshal(diffBytes, &diffJson); err != nil { 152 return nil, err 153 } 154 155 var filteredDiffDdls []string 156 for _, diffEntry := range diffJson { 157 if diffEntry.Status == "Identical" || diffEntry.DiffDdl == "" { 158 continue 159 } 160 161 switch diffEntry.Type { 162 case "extension", "function", "mview", "table", "trigger_function", "type", "view": 163 // skip 164 default: 165 continue 166 } 167 168 { 169 doContinue := false 170 for _, dep := range diffEntry.Dependencies { 171 if dep.Type == "extension" { 172 doContinue = true 173 break 174 } 175 } 176 177 if doContinue { 178 continue 179 } 180 } 181 182 isSchemaIgnored := func(schema string) bool { 183 for _, s := range InternalSchemas { 184 if s == schema { 185 return true 186 } 187 } 188 return false 189 } 190 191 if isSchemaIgnored(diffEntry.GroupName) || 192 // Needed at least for trigger_function 193 (diffEntry.SourceSchemaName != nil && isSchemaIgnored(*diffEntry.SourceSchemaName)) { 194 continue 195 } 196 197 trimmed := strings.TrimSpace(diffEntry.DiffDdl) 198 if len(trimmed) > 0 { 199 filteredDiffDdls = append(filteredDiffDdls, trimmed) 200 } 201 } 202 203 if len(filteredDiffDdls) == 0 { 204 return nil, nil 205 } 206 return []byte(diffHeader + "\n\n" + strings.Join(filteredDiffDdls, "\n\n") + "\n"), nil 207 } 208 209 func ProcessPsqlOutput(out io.Reader, p Program) error { 210 r, w := io.Pipe() 211 doneCh := make(chan struct{}, 1) 212 213 go func() { 214 scanner := bufio.NewScanner(r) 215 216 for scanner.Scan() { 217 select { 218 case <-doneCh: 219 return 220 default: 221 } 222 223 line := scanner.Text() 224 p.Send(PsqlMsg(&line)) 225 } 226 }() 227 228 var errBuf bytes.Buffer 229 if _, err := stdcopy.StdCopy(w, &errBuf, out); err != nil { 230 return err 231 } 232 if errBuf.Len() > 0 { 233 return errors.New("Error running SQL: " + errBuf.String()) 234 } 235 236 doneCh <- struct{}{} 237 p.Send(PsqlMsg(nil)) 238 239 return nil 240 }