profile
viewpoint

loldesign/azure 31

A golang API to communicate with the Azure Storage.

marcosinger/attachment_magick 20

Upload images and videos(vimeo, youtube) and cache using Swfupload and Dragonfly

lucasrenan/publish 8

Adds the functionality to publish (or set as draft) a document using Mongoid.

lucasrenan/updated-by 7

Updated by provides functionality to store user which created/updated the given record.

marcosinger/auto_html 3

Rails plugin for transforming URLs to appropriate resource (image, link, YouTube, Vimeo video,...)

lmtani/cromwell-cli 2

Command line interface for Cromwell server

marcosinger/css_parser 2

Ruby CSS Parser

fork marcosinger/gofpdf

A PDF document generator with high level support for text, drawing and images

http://godoc.org/github.com/jung-kurt/gofpdf

fork in a month

issue commentkubernetes/minikube

start: Comma-delimited --addons silently ignored

@tstromberg I'm a newcomer here and I'd like to help with this one. I have some comments/questions before start:

to use a Slice instead of an Array, but I suspect there are devils in the implementation details

Accordingly to the docs, StringSliceVar might fix this issue without any drawbacks.

and that it errors out if an unknown addon is supplied

As far as I can see, both addon.Set and addon.RunCalbbacks functions handle with invalid addon name, but the command entry-point is addon.Start, which is filtering from invalid names. Do you think worth removing that check to keep the same behavior across commands?

WDYT?

Thanks!

tstromberg

comment created time in a month

Pull request review commentlmtani/cromwell-cli

First http request to Cromwell Server

+package commands++import (+	"bytes"+	"encoding/json"+	"errors"+	"fmt"+	"io/ioutil"+	"log"+	"mime/multipart"+	"net/http"+	"os"++	"github.com/mitchellh/mapstructure"+	"go.uber.org/zap"+)++func New(h, t string) Client {+	return Client{host: h, token: t}+}++func FromInterface(i interface{}) Client {+	c := Client{}+	mapstructure.Decode(i, &c)+	return c+}++type Client struct {+	host  string+	token string+}++func (c *Client) makeRequest(req *http.Request) (*http.Response, error) {+	if c.token != "" {+		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token))+	}+	zap.S().Debugw(fmt.Sprintf("%s request to: %s", req.Method, req.URL))+	client := &http.Client{}+	return client.Do(req)+}++func (c *Client) get(u string) (*http.Response, error) {+	uri := fmt.Sprintf("%s%s", c.host, u)+	req, _ := http.NewRequest("GET", uri, nil)+	return c.makeRequest(req)+}++func (c *Client) post(u string, files map[string]string) (*http.Response, error) {+	uri := fmt.Sprintf("%s%s", c.host, u)+	body := new(bytes.Buffer)+	writer := multipart.NewWriter(body)+	for k, v := range files {+		file, _ := os.Open(v)+		content, _ := ioutil.ReadAll(file)+		fi, _ := file.Stat()

you shouldn't ignore errors 😄

lmtani

comment created time in a month

Pull request review commentlmtani/cromwell-cli

First http request to Cromwell Server

 package commands  import ( 	"fmt"-	"io/ioutil"-	"net/http"+	"time" +	"github.com/urfave/cli/v2" 	"go.uber.org/zap" ) -func GetWorkflow() error {-	resp, err := http.Get("https://httpbin.org/get")-	if err != nil {-		return err+type QueryResponse struct {+	Results           []QueryResponseWorkflow+	TotalResultsCount int+}++type QueryResponseWorkflow struct {+	ID                    string+	Name                  string+	Status                string+	Submission            string+	Start                 time.Time+	End                   time.Time+	MetadataArchiveStatus string+}++func queryResponseToTable(workflows QueryResponse) ([]string, [][]string) {+	header := []string{"Operation", "Name", "Start", "End", "Status"}+	rows := [][]string{}+	timePattern := "2006-01-02 15h04m"+	for _, elem := range workflows.Results {+		rows = append(rows, []string{elem.ID, elem.Name, elem.Start.Format(timePattern), elem.End.Format(timePattern), elem.Status}) 	}-	defer resp.Body.Close()-	body, err := ioutil.ReadAll(resp.Body)+	return header, rows+}++func QueryWorkflow(c *cli.Context) error {

This is more like a suggestion than a review on this flow. Feel free to proceed as you wish :)

Let me use this as an example to decouple the query request from the query view. The queryResponseToTable method is tied to the QueryResponse, which might be hard to change it if we need to add/remove/change some fields inside that struct.

Another implementation could be to create a new struct, responsible only for the view part (QueryTableResponse) and change the CreateTable function to accept this new object.

In practice, something like this:

Table abstraction

// pkg/output/table.go

package output

import (
	"io"

	"github.com/olekukonko/tablewriter"
)

type Table struct {
	w *tablewriter.Table
}

type Tablee interface {
	Header() []string
	Rows() [][]string
}

func NewTable(w io.Writer) Table {
	return Table{
		tablewriter.NewWriter(w),
	}
}

func (t Table) Render(tab Tablee) {
	t.w.SetHeader(tab.Header())
	t.w.SetBorders(tablewriter.Border{Left: true, Top: true, Right: true, Bottom: true})
	t.w.AppendBulk(tab.Rows())
	t.w.Render()
}

Usage example on QueryWorkflow

type QueryTableResponse struct {
	Results           []QueryResponseWorkflow
	TotalResultsCount int
}

func (qtr QueryTableResponse) Header() []string {
	return []string{"Operation", "Name", "Start", "End", "Status"}
}

func (qtr QueryTableResponse) Rows() [][]string {
	rows := make([][]string, len(qtr.Results))

	timePattern := "2006-01-02 15h04m"
	for _, r := range qtr.Results {
		rows = append(rows, []string{
			r.ID,
			r.Name,
			r.Start.Format(timePattern),
			r.End.Format(timePattern),
			r.Status,
		})
	}

	return rows
}

func QueryWorkflow(c *cli.Context) error {
	cromwellClient := FromInterface(c.Context.Value("cromwell"))
	resp, err := cromwellClient.Query(c.String("name"))
	if err != nil {
		return err
	}
	zap.S().Debugw(fmt.Sprintf("Found %d workflows", resp.TotalResultsCount))

	var qtr = QueryTableResponse{
		TotalResultsCount: resp.TotalResultsCount,
		Results:           resp.Results,
	}

	output.NewTable(os.Stdout).Render(qtr)

	return err
}

To be honest, I don't like to abuse the use of interfaces, but in this particular case it's pretty straightforward and reduces the query flow's complexity 😄

lmtani

comment created time in a month

Pull request review commentlmtani/cromwell-cli

First http request to Cromwell Server

+package commands++import (+	"bytes"+	"encoding/json"+	"errors"+	"fmt"+	"io/ioutil"+	"log"+	"mime/multipart"+	"net/http"+	"os"++	"github.com/mitchellh/mapstructure"+	"go.uber.org/zap"+)++func New(h, t string) Client {+	return Client{host: h, token: t}+}++func FromInterface(i interface{}) Client {+	c := Client{}+	mapstructure.Decode(i, &c)+	return c+}++type Client struct {+	host  string+	token string+}++func (c *Client) makeRequest(req *http.Request) (*http.Response, error) {+	if c.token != "" {+		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token))+	}+	zap.S().Debugw(fmt.Sprintf("%s request to: %s", req.Method, req.URL))+	client := &http.Client{}+	return client.Do(req)+}++func (c *Client) get(u string) (*http.Response, error) {+	uri := fmt.Sprintf("%s%s", c.host, u)+	req, _ := http.NewRequest("GET", uri, nil)+	return c.makeRequest(req)+}++func (c *Client) post(u string, files map[string]string) (*http.Response, error) {+	uri := fmt.Sprintf("%s%s", c.host, u)+	body := new(bytes.Buffer)+	writer := multipart.NewWriter(body)+	for k, v := range files {+		file, _ := os.Open(v)+		content, _ := ioutil.ReadAll(file)+		fi, _ := file.Stat()+		part, _ := writer.CreateFormFile(k, fi.Name())+		_, err := part.Write(content)+		if err != nil {+			return nil, err+		}+	}++	err := writer.Close()+	if err != nil {+		return nil, err+	}+	req, _ := http.NewRequest("POST", uri, body)+	req.Header.Set("Content-Type", writer.FormDataContentType())+	return c.makeRequest(req)+}++func (c *Client) Kill(o string) (SubmitResponse, error) {+	route := fmt.Sprintf("/api/workflows/v1/%s/abort", o)+	r, err := c.post(route, map[string]string{})+	if err != nil {+		return SubmitResponse{}, err+	}+	defer r.Body.Close()+	body, _ := ioutil.ReadAll(r.Body)+	if r.StatusCode >= 400 {+		msg := fmt.Sprintf("Submission failed. The server returned %d\n%s", r.StatusCode, body)+		return SubmitResponse{}, errors.New(msg)+	}+	resp := SubmitResponse{}+	err = json.Unmarshal(body, &resp)+	if err != nil {+		return SubmitResponse{}, err+	}+	return resp, nil+}++func (c *Client) Status(o string) string {+	route := fmt.Sprintf("/api/workflow/v1/%s/status", o)+	return route+}++func (c *Client) Outputs(o string) string {+	route := fmt.Sprintf("/api/workflow/v1/%s/status", o)+	return route+}++func (c *Client) Query(n string) (QueryResponse, error) {+	route := "/api/workflows/v1/query"+	var urlParams string+	if n != "" {+		urlParams = fmt.Sprintf("?name=%s", n)+	}+	r, err := c.get(route + urlParams)+	if err != nil {+		return QueryResponse{}, err+	}+	defer r.Body.Close()+	resp := QueryResponse{}+	body, _ := ioutil.ReadAll(r.Body)+	err = json.Unmarshal(body, &resp)+	if err != nil {+		return QueryResponse{}, errors.New(string(body))+	}+	return resp, nil+}++func (c *Client) Metadata(o string) (MetadataResponse, error) {+	route := fmt.Sprintf("/api/workflows/v1/%s/metadata", o)+	r, err := c.get(route)+	if err != nil {+		return MetadataResponse{}, nil+	}+	defer r.Body.Close()+	body, _ := ioutil.ReadAll(r.Body)+	resp := MetadataResponse{}+	err = json.Unmarshal(body, &resp)+	if err != nil {+		return MetadataResponse{}, err+	}+	return resp, nil+}++func submitPrepare(r SubmitRequest) map[string]string {+	fileParams := map[string]string{+		"workflowSource": r.workflowSource,+		"workflowInputs": r.workflowInputs,+	}+	if r.workflowDependencies != "" {+		fileParams["workflowDependencies"] = r.workflowDependencies+	}+	return fileParams+}++func (c *Client) Submit(requestFields SubmitRequest) (SubmitResponse, error) {+	route := "/api/workflows/v1"+	fileParams := submitPrepare(requestFields)++	r, err := c.post(route, fileParams)+	if err != nil {+		log.Fatal(err)+	}+	defer r.Body.Close()+	resp := SubmitResponse{}+	body, _ := ioutil.ReadAll(r.Body)+	err = json.Unmarshal(body, &resp)+	if err != nil {+		return SubmitResponse{}, err+	}+	if r.StatusCode >= 400 {+		msg := fmt.Sprintf("Submission failed. The server returned %d\n%s", r.StatusCode, body)+		return SubmitResponse{}, errors.New(msg)

same here

lmtani

comment created time in a month

Pull request review commentlmtani/cromwell-cli

First http request to Cromwell Server

 func startLogger() (*zap.Logger, error) { }  func main() {+	keyCromwell := "cromwell" 	logger, _ := startLogger()+ 	app := &cli.App{ 		Name:  "cromwell-cli", 		Usage: "Command line interface for Cromwell Server",+		Flags: []cli.Flag{+			&cli.StringFlag{+				Name:     "token",+				Aliases:  []string{"t"},+				Required: false,+				Usage:    "Bearer token to be included in HTTP requsts",+			},+			&cli.StringFlag{+				Name:  "host",+				Value: "http://127.0.0.1:8000",+				Usage: "Url for your Cromwell Server",+			},+		},+		Before: func(c *cli.Context) error {+			cromwellClient := commands.New(c.String("host"), c.String("token"))+			c.Context = context.WithValue(c.Context, keyCromwell, cromwellClient)+			return nil+		}, 		Commands: []*cli.Command{+			{+				Name:    "query",+				Aliases: []string{"q"},+				Usage:   "Query a workflow by its name",+				Flags: []cli.Flag{+					&cli.StringFlag{Name: "name", Aliases: []string{"n"}, Required: false},+				},+				Action: commands.QueryWorkflow,+			},+ 			{ 				Name:    "submit", 				Aliases: []string{"s"},-				Usage:   "Submit a new job for Cromwell Server",+				Usage:   "Submit a workflow and its inputs to Cromwell", 				Flags: []cli.Flag{ 					&cli.StringFlag{Name: "wdl", Aliases: []string{"w"}, Required: true}, 					&cli.StringFlag{Name: "inputs", Aliases: []string{"i"}, Required: true},-					&cli.StringFlag{Name: "dependencies", Aliases: []string{"d"}, Required: true},-				},-				Action: func(c *cli.Context) error {-					err := commands.GetWorkflow()-					if err != nil {-						logger.Fatal(err.Error())-					}-					return nil+					&cli.StringFlag{Name: "dependencies", Aliases: []string{"d"}, Required: false}, 				},+				Action: commands.SubmitWorkflow, 			},+ 			{ 				Name:    "kill", 				Aliases: []string{"k"}, 				Usage:   "Kill a running job", 				Flags: []cli.Flag{ 					&cli.StringFlag{Name: "operation", Aliases: []string{"o"}, Required: true}, 				},-				Action: func(c *cli.Context) error {-					commands.GenerateTable()-					return nil+				Action: commands.KillWorkflow,+			},+			{+				Name:    "metadata",+				Aliases: []string{"m"},+				Usage:   "Inspect workflow details",+				Flags: []cli.Flag{+					&cli.StringFlag{Name: "operation", Aliases: []string{"o"}, Required: true}, 				},+				Action: commands.MetadataWorkflow, 			}, 		}, 	}  	err := app.Run(os.Args) 	if err != nil {-		log.Fatal(err)+		logger.Error(err.Error())

zap accepts some fields in order to create a good log message. I'd like to suggest using something like this:

if err := app.Run(os.Args); err != nil {
	logger.Error("cromwell.command.error",
		zap.NamedError("err", err),
	)
}

Using my suggestion on the Kill command and returning the error response as JSON, is going to produce this kind of output here:

./cromwell k -o 8d98881b-b588-4f3f-ae88-c404e6b9490a

2020-09-16T15:34:00.907-0300    ERROR   cromwell-cli/main.go:95 cromwell.command.error  {"err": "Submission failed. The server returned commands.ErrorResponse{HTTPStatus:\"404 Not Found\", Status:\"error\", Message:\"Couldn't abort 8d98881b-b588-4f3f-ae88-c404e6b9490a because no workflow with that ID is in progress\"}"}
lmtani

comment created time in a month

Pull request review commentlmtani/cromwell-cli

First http request to Cromwell Server

 func startLogger() (*zap.Logger, error) { }  func main() {+	keyCromwell := "cromwell" 	logger, _ := startLogger()+

try to not ignore errors:

if err != nil {
	log.Fatalf("could not initialize custom logger; got %v", err)
}
lmtani

comment created time in a month

Pull request review commentlmtani/cromwell-cli

First http request to Cromwell Server

+package commands++import (+	"bytes"+	"encoding/json"+	"errors"+	"fmt"+	"io/ioutil"+	"log"+	"mime/multipart"+	"net/http"+	"os"++	"github.com/mitchellh/mapstructure"+	"go.uber.org/zap"+)++func New(h, t string) Client {+	return Client{host: h, token: t}+}++func FromInterface(i interface{}) Client {+	c := Client{}+	mapstructure.Decode(i, &c)+	return c+}++type Client struct {+	host  string+	token string+}++func (c *Client) makeRequest(req *http.Request) (*http.Response, error) {+	if c.token != "" {+		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token))+	}+	zap.S().Debugw(fmt.Sprintf("%s request to: %s", req.Method, req.URL))+	client := &http.Client{}+	return client.Do(req)+}++func (c *Client) get(u string) (*http.Response, error) {+	uri := fmt.Sprintf("%s%s", c.host, u)+	req, _ := http.NewRequest("GET", uri, nil)+	return c.makeRequest(req)+}++func (c *Client) post(u string, files map[string]string) (*http.Response, error) {

how about it?

func (c *Client) post(u string, files map[string]string) (*http.Response, error) {
	var (
		uri    = fmt.Sprintf("%s%s", c.host, u)
		body   = new(bytes.Buffer)
		writer = multipart.NewWriter(body)
	)

	for field, path := range files {
		// gets file name from file path
		filename := filepath.Base(path)
		// creates a new form file writer
		fw, err := writer.CreateFormFile(field, filename)
		if err != nil {
			return nil, err
		}

		// prepare the file to be read
		file, err := os.Open(path)
		if err != nil {
			return nil, err
		}

		// copies the file content to the form file writer
		if _, err := io.Copy(fw, file); err != nil {
			return nil, err
		}
	}

	if err := writer.Close(); err != nil {
		return nil, err
	}

	req, err := http.NewRequest("POST", uri, body)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", writer.FormDataContentType())
	return c.makeRequest(req)
}
lmtani

comment created time in a month

Pull request review commentlmtani/cromwell-cli

First http request to Cromwell Server

+package commands++import (+	"bytes"+	"encoding/json"+	"errors"+	"fmt"+	"io/ioutil"+	"log"+	"mime/multipart"+	"net/http"+	"os"++	"github.com/mitchellh/mapstructure"+	"go.uber.org/zap"+)++func New(h, t string) Client {+	return Client{host: h, token: t}+}++func FromInterface(i interface{}) Client {+	c := Client{}+	mapstructure.Decode(i, &c)+	return c+}++type Client struct {+	host  string+	token string+}++func (c *Client) makeRequest(req *http.Request) (*http.Response, error) {+	if c.token != "" {+		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token))+	}+	zap.S().Debugw(fmt.Sprintf("%s request to: %s", req.Method, req.URL))+	client := &http.Client{}+	return client.Do(req)+}++func (c *Client) get(u string) (*http.Response, error) {+	uri := fmt.Sprintf("%s%s", c.host, u)+	req, _ := http.NewRequest("GET", uri, nil)+	return c.makeRequest(req)+}++func (c *Client) post(u string, files map[string]string) (*http.Response, error) {+	uri := fmt.Sprintf("%s%s", c.host, u)+	body := new(bytes.Buffer)+	writer := multipart.NewWriter(body)+	for k, v := range files {+		file, _ := os.Open(v)+		content, _ := ioutil.ReadAll(file)+		fi, _ := file.Stat()+		part, _ := writer.CreateFormFile(k, fi.Name())+		_, err := part.Write(content)+		if err != nil {+			return nil, err+		}+	}++	err := writer.Close()+	if err != nil {+		return nil, err+	}+	req, _ := http.NewRequest("POST", uri, body)+	req.Header.Set("Content-Type", writer.FormDataContentType())+	return c.makeRequest(req)+}++func (c *Client) Kill(o string) (SubmitResponse, error) {+	route := fmt.Sprintf("/api/workflows/v1/%s/abort", o)+	r, err := c.post(route, map[string]string{})+	if err != nil {+		return SubmitResponse{}, err+	}+	defer r.Body.Close()+	body, _ := ioutil.ReadAll(r.Body)+	if r.StatusCode >= 400 {

returning the response body as a string doesn't help when we need to troubleshooting, so how about creating an error struct and adding it as part of the error?

The Kill function, with this and all other suggestions, might be:

type ErrorResponse struct {
	HTTPStatus string
	Status     string
	Message    string
}

func (c *Client) Kill(o string) (SubmitResponse, error) {
	var sr SubmitResponse

	route := fmt.Sprintf("/api/workflows/v1/%s/abort", o)
	r, err := c.post(route, map[string]string{})
	if err != nil {
		return SubmitResponse{}, err
	}
	defer r.Body.Close()

	if r.StatusCode >= 400 {
		var er = ErrorResponse{
			HTTPStatus: r.Status,
		}

		if err := json.NewDecoder(r.Body).Decode(&er); err != nil {
			return sr, err
		}

		return sr, fmt.Errorf("submission failed. The server returned %#v", er)
	}

	if err := json.NewDecoder(r.Body).Decode(&sr); err != nil {
		return sr, err
	}
	return sr, nil
}
lmtani

comment created time in a month

Pull request review commentlmtani/cromwell-cli

First http request to Cromwell Server

+package commands++import (+	"bytes"+	"encoding/json"+	"errors"+	"fmt"+	"io/ioutil"+	"log"+	"mime/multipart"+	"net/http"+	"os"++	"github.com/mitchellh/mapstructure"+	"go.uber.org/zap"+)++func New(h, t string) Client {+	return Client{host: h, token: t}+}++func FromInterface(i interface{}) Client {+	c := Client{}+	mapstructure.Decode(i, &c)+	return c+}++type Client struct {+	host  string+	token string+}++func (c *Client) makeRequest(req *http.Request) (*http.Response, error) {+	if c.token != "" {+		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token))+	}+	zap.S().Debugw(fmt.Sprintf("%s request to: %s", req.Method, req.URL))+	client := &http.Client{}+	return client.Do(req)+}++func (c *Client) get(u string) (*http.Response, error) {+	uri := fmt.Sprintf("%s%s", c.host, u)+	req, _ := http.NewRequest("GET", uri, nil)+	return c.makeRequest(req)+}++func (c *Client) post(u string, files map[string]string) (*http.Response, error) {+	uri := fmt.Sprintf("%s%s", c.host, u)+	body := new(bytes.Buffer)+	writer := multipart.NewWriter(body)+	for k, v := range files {+		file, _ := os.Open(v)+		content, _ := ioutil.ReadAll(file)+		fi, _ := file.Stat()+		part, _ := writer.CreateFormFile(k, fi.Name())+		_, err := part.Write(content)+		if err != nil {+			return nil, err+		}+	}++	err := writer.Close()+	if err != nil {+		return nil, err+	}+	req, _ := http.NewRequest("POST", uri, body)+	req.Header.Set("Content-Type", writer.FormDataContentType())+	return c.makeRequest(req)+}++func (c *Client) Kill(o string) (SubmitResponse, error) {+	route := fmt.Sprintf("/api/workflows/v1/%s/abort", o)+	r, err := c.post(route, map[string]string{})+	if err != nil {+		return SubmitResponse{}, err+	}+	defer r.Body.Close()+	body, _ := ioutil.ReadAll(r.Body)+	if r.StatusCode >= 400 {+		msg := fmt.Sprintf("Submission failed. The server returned %d\n%s", r.StatusCode, body)+		return SubmitResponse{}, errors.New(msg)+	}+	resp := SubmitResponse{}+	err = json.Unmarshal(body, &resp)+	if err != nil {+		return SubmitResponse{}, err+	}+	return resp, nil+}++func (c *Client) Status(o string) string {+	route := fmt.Sprintf("/api/workflow/v1/%s/status", o)+	return route+}++func (c *Client) Outputs(o string) string {+	route := fmt.Sprintf("/api/workflow/v1/%s/status", o)+	return route+}++func (c *Client) Query(n string) (QueryResponse, error) {+	route := "/api/workflows/v1/query"+	var urlParams string+	if n != "" {+		urlParams = fmt.Sprintf("?name=%s", n)+	}+	r, err := c.get(route + urlParams)+	if err != nil {+		return QueryResponse{}, err+	}+	defer r.Body.Close()+	resp := QueryResponse{}+	body, _ := ioutil.ReadAll(r.Body)

the same comment regarding using json.NewDecoder applies here

lmtani

comment created time in a month

Pull request review commentlmtani/cromwell-cli

First http request to Cromwell Server

+package commands++import (+	"bytes"+	"encoding/json"+	"errors"+	"fmt"+	"io/ioutil"+	"log"+	"mime/multipart"+	"net/http"+	"os"++	"github.com/mitchellh/mapstructure"+	"go.uber.org/zap"+)++func New(h, t string) Client {+	return Client{host: h, token: t}+}++func FromInterface(i interface{}) Client {+	c := Client{}+	mapstructure.Decode(i, &c)+	return c+}++type Client struct {+	host  string+	token string+}++func (c *Client) makeRequest(req *http.Request) (*http.Response, error) {+	if c.token != "" {+		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token))+	}+	zap.S().Debugw(fmt.Sprintf("%s request to: %s", req.Method, req.URL))+	client := &http.Client{}+	return client.Do(req)+}++func (c *Client) get(u string) (*http.Response, error) {+	uri := fmt.Sprintf("%s%s", c.host, u)+	req, _ := http.NewRequest("GET", uri, nil)+	return c.makeRequest(req)+}++func (c *Client) post(u string, files map[string]string) (*http.Response, error) {+	uri := fmt.Sprintf("%s%s", c.host, u)+	body := new(bytes.Buffer)+	writer := multipart.NewWriter(body)+	for k, v := range files {+		file, _ := os.Open(v)+		content, _ := ioutil.ReadAll(file)+		fi, _ := file.Stat()+		part, _ := writer.CreateFormFile(k, fi.Name())+		_, err := part.Write(content)+		if err != nil {+			return nil, err+		}+	}++	err := writer.Close()+	if err != nil {+		return nil, err+	}+	req, _ := http.NewRequest("POST", uri, body)+	req.Header.Set("Content-Type", writer.FormDataContentType())+	return c.makeRequest(req)+}++func (c *Client) Kill(o string) (SubmitResponse, error) {+	route := fmt.Sprintf("/api/workflows/v1/%s/abort", o)+	r, err := c.post(route, map[string]string{})+	if err != nil {+		return SubmitResponse{}, err+	}+	defer r.Body.Close()+	body, _ := ioutil.ReadAll(r.Body)

note: another way to read the body and return errors, without ReadAll & Unmarshal:

var sr SubmitResponse
if err := json.NewDecoder(r.Body).Decode(&sr); err != nil {
	return sr, err
}

if r.StatusCode >= 400 {
	return sr, fmt.Errorf("Submission failed. The server returned %d\n%#v", r.StatusCode, sr)
}

return sr, nil
lmtani

comment created time in a month

Pull request review commentlmtani/cromwell-cli

First http request to Cromwell Server

+package commands++import (+	"bytes"+	"encoding/json"+	"errors"+	"fmt"+	"io/ioutil"+	"log"+	"mime/multipart"+	"net/http"+	"os"++	"github.com/mitchellh/mapstructure"+	"go.uber.org/zap"+)++func New(h, t string) Client {+	return Client{host: h, token: t}+}++func FromInterface(i interface{}) Client {+	c := Client{}+	mapstructure.Decode(i, &c)+	return c+}++type Client struct {+	host  string+	token string+}++func (c *Client) makeRequest(req *http.Request) (*http.Response, error) {+	if c.token != "" {+		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token))+	}+	zap.S().Debugw(fmt.Sprintf("%s request to: %s", req.Method, req.URL))+	client := &http.Client{}+	return client.Do(req)+}++func (c *Client) get(u string) (*http.Response, error) {+	uri := fmt.Sprintf("%s%s", c.host, u)+	req, _ := http.NewRequest("GET", uri, nil)+	return c.makeRequest(req)+}++func (c *Client) post(u string, files map[string]string) (*http.Response, error) {+	uri := fmt.Sprintf("%s%s", c.host, u)+	body := new(bytes.Buffer)+	writer := multipart.NewWriter(body)+	for k, v := range files {+		file, _ := os.Open(v)+		content, _ := ioutil.ReadAll(file)+		fi, _ := file.Stat()+		part, _ := writer.CreateFormFile(k, fi.Name())+		_, err := part.Write(content)+		if err != nil {+			return nil, err+		}+	}++	err := writer.Close()+	if err != nil {+		return nil, err+	}+	req, _ := http.NewRequest("POST", uri, body)+	req.Header.Set("Content-Type", writer.FormDataContentType())+	return c.makeRequest(req)+}++func (c *Client) Kill(o string) (SubmitResponse, error) {+	route := fmt.Sprintf("/api/workflows/v1/%s/abort", o)+	r, err := c.post(route, map[string]string{})+	if err != nil {+		return SubmitResponse{}, err+	}+	defer r.Body.Close()+	body, _ := ioutil.ReadAll(r.Body)+	if r.StatusCode >= 400 {+		msg := fmt.Sprintf("Submission failed. The server returned %d\n%s", r.StatusCode, body)+		return SubmitResponse{}, errors.New(msg)

note: you could do it in one-line:

return SubmitResponse{}, fmt.Errorf("Submission failed. The server returned %d\n%s", r.StatusCode, body)
lmtani

comment created time in a month

Pull request review commentlmtani/cromwell-cli

First http request to Cromwell Server

  Command line interface for Cromwell server. +## Quickstart++```bash+# Download+OS=linux+#change to OS=windows  or OS=darwin (macos)+wget https://github.com/lmtani/cromwell-cli/releases/download/v0.1/cromwell-cli-${OS}-amd64++# Put in a directory of your PATH (may require sudo)+mv cromwell-cli-${OS}-amd64 /usr/bin/cromwell-cli

I needed to make it executable as well: sudo chmod +x /usr/bin/cromwell-cli

lmtani

comment created time in a month

PullRequestReviewEvent
PullRequestReviewEvent

issue openedpdfcpu/pdfcpu

Exporting StreamDict internal functions

Hey @hhrutter,

Firstly, thank you for this excellent library. I've started to use it a few months ago, and so far, it's pretty easy to grasp despite the insane PDF spec. But, one issue that I've is regarding encode/decode StreamDict object.

Right now, both encodeStream and decodeStream functions are private, and I needed to copy/paste the source code from the library to work with it on my end.

I think exporting it as a StreamDict method would make it better for anyone who wants to reuse this code, and will add more value to the StreamDict object itself since the API will end up straightforward.

I propose something like this:

func (sd *pdfcpu.StreamDict) Decode() error {
  // decode code goes here
}

func (sd *pdfcpu.StreamDict) Encode() error {
  // encode code goes here
}

so changing those places where we use it is just a matter of:

Decode

// pkg/pdfcpu/attach.go on fileSpecStreamDictInfo function
if err := sd.Decode(); err != nil {
  return nil, desc, modDate, err
}

Encode

// pkg/pdfcpu/readImage.go on createSMaskObject function
if err := sd.Encode(); err != nil {
  return nil, err
}

What do you think about it? Do you see any drawbacks or concerns regarding it? Many thanks for considering my request.

created time in 2 months

startedgosuri/uilive

started time in 2 months

more