package main import ( "encoding/json" "fmt" "io" "io/fs" "mime/multipart" "net/http" "net/textproto" "net/url" "os" "path/filepath" "strings" "github.com/sethvargo/go-githubactions" ) type AddResponse struct { Bytes int64 Hash string Name string Size string } type IpfsMultipartWriter struct { multipart.Writer } var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") func escapeQuotes(s string) string { return quoteEscaper.Replace(s) } func NewIpfsMultipartWriter(w io.Writer) *IpfsMultipartWriter { return &IpfsMultipartWriter{ Writer: *multipart.NewWriter(w), } } func (w *IpfsMultipartWriter) CreateIpfsDirectoryPart(name string) (io.Writer, error) { h := make(textproto.MIMEHeader) encodedName := url.QueryEscape(name) h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, encodedName)) h.Set("Content-Type", "application/x-directory") return w.CreatePart(h) } func (w *IpfsMultipartWriter) CreateIpfsFilePart(name string) (io.Writer, error) { h := make(textproto.MIMEHeader) encodedName := url.QueryEscape(name) h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, escapeQuotes(encodedName))) h.Set("Content-Type", "application/octet-stream") return w.CreatePart(h) } func (w *IpfsMultipartWriter) CreateIpfsAbsFilePart(name, absPath string) (io.Writer, error) { h := make(textproto.MIMEHeader) encodedName := url.QueryEscape(name) h.Set("AbsPath", absPath) h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, escapeQuotes(encodedName))) h.Set("Content-Type", "application/octet-stream") return w.CreatePart(h) } func main() { githubactions.Debugf("Checking inputs") path := githubactions.GetInput("path_to_add") if path == "" { githubactions.Fatalf("Missing: path_to_add") } ipfsHost := githubactions.GetInput("ipfs_host") if ipfsHost == "" { githubactions.Fatalf("Missing: ipfs_host") } ipfsPort := githubactions.GetInput("ipfs_port") if ipfsPort == "" { githubactions.Fatalf("Missing: ipfs_port") } targetPath, err := os.Open(path) if err != nil { githubactions.Fatalf("Unable to access path_to_add: %v", err.Error()) } defer targetPath.Close() targetPathInfo, err := targetPath.Stat() if err != nil { githubactions.Fatalf("Unable to access to access path_to_add info: %v", fmt.Errorf("%w", err)) } if !targetPathInfo.IsDir() { githubactions.Fatalf("%v is not a directory", path) } githubactions.Debugf("Inputs OK") body, writer := io.Pipe() targetUrl := fmt.Sprintf("http://%v:%v/api/v0/add", ipfsHost, ipfsPort) req, err := http.NewRequest(http.MethodPost, targetUrl, body) if err != nil { githubactions.Fatalf("Unable to create request: %v", err.Error()) } q := req.URL.Query() q.Add("wrap-with-directory", "true") q.Add("progress", "false") q.Add("stream-true", "false") req.URL.RawQuery = q.Encode() mwriter := NewIpfsMultipartWriter(writer) req.Header.Add("Content-Type", mwriter.FormDataContentType()) go func() { defer writer.Close() defer mwriter.Close() err = filepath.Walk(path, func(innerPath string, info fs.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } relPath, _ := filepath.Rel(path, innerPath) w, err := mwriter.CreateIpfsFilePart(relPath) if err != nil { return err } fileReader, err := os.Open(innerPath) if err != nil { return err } defer fileReader.Close() written, err := io.Copy(w, fileReader) if err != nil { return fmt.Errorf("error copying %s (%d bytes written): %v", innerPath, written, err) } return nil }) if err != nil { githubactions.Fatalf("Unable to create request body: %v", fmt.Errorf("%w", err)) } }() githubactions.Infof("Calling node API...") client := &http.Client{} res, err := client.Do(req) if err != nil { githubactions.Fatalf(err.Error()) } githubactions.Infof("Reading response...") resBody, err := io.ReadAll(res.Body) if err != nil { githubactions.Fatalf(err.Error()) } githubactions.Infof("Response: %v", string(resBody)) d := json.NewDecoder(res.Body) for { var ipfsAddResponse []AddResponse if err := d.Decode(&ipfsAddResponse); err == io.EOF { break } else if err != nil { githubactions.Warningf("Failed to unmarshal response: %v", fmt.Errorf("%w", err)) } githubactions.Infof("Received response: %v", ipfsAddResponse) } }