-
Notifications
You must be signed in to change notification settings - Fork 3
/
client.go
166 lines (142 loc) · 4.21 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//go:generate client-gen -o api.gen.go --package dagger --lang go
package dagger
import (
"context"
"fmt"
"io"
"os"
"github.com/Khan/genqlient/graphql"
"github.com/vektah/gqlparser/v2/gqlerror"
"dagger.io/dagger/internal/engineconn"
"dagger.io/dagger/internal/querybuilder"
)
// Client is the Dagger Engine Client
type Client struct {
conn engineconn.EngineConn
c graphql.Client
q *querybuilder.Selection
}
// ClientOpt holds a client option
type ClientOpt interface {
setClientOpt(cfg *engineconn.Config)
}
type clientOptFunc func(cfg *engineconn.Config)
func (fn clientOptFunc) setClientOpt(cfg *engineconn.Config) {
fn(cfg)
}
// WithWorkdir sets the engine workdir
func WithWorkdir(path string) ClientOpt {
return clientOptFunc(func(cfg *engineconn.Config) {
cfg.Workdir = path
})
}
// WithLogOutput sets the progress writer
func WithLogOutput(writer io.Writer) ClientOpt {
return clientOptFunc(func(cfg *engineconn.Config) {
cfg.LogOutput = writer
})
}
// WithConn sets the engine connection explicitly
func WithConn(conn engineconn.EngineConn) ClientOpt {
return clientOptFunc(func(cfg *engineconn.Config) {
cfg.Conn = conn
})
}
// Connect to a Dagger Engine
func Connect(ctx context.Context, opts ...ClientOpt) (_ *Client, rerr error) {
defer func() {
if rerr != nil {
rerr = withErrorHelp(rerr)
}
}()
cfg := &engineconn.Config{}
for _, o := range opts {
o.setClientOpt(cfg)
}
conn, err := engineconn.Get(ctx, cfg)
if err != nil {
return nil, err
}
gql := errorWrappedClient{graphql.NewClient("http://"+conn.Host()+"/query", conn)}
c := &Client{
c: gql,
conn: conn,
q: querybuilder.Query(),
}
// Call version compatibility.
// If versions are not compatible, a warning will be displayed.
if _, err = c.CheckVersionCompatibility(ctx, engineconn.CLIVersion); err != nil {
fmt.Fprintln(os.Stderr, "failed to check version compatibility:", err)
}
return c, nil
}
// Close the engine connection
func (c *Client) Close() error {
if c.conn != nil {
return c.conn.Close()
}
return nil
}
// Do sends a GraphQL request to the engine
func (c *Client) Do(ctx context.Context, req *Request, resp *Response) error {
r := graphql.Response{}
if resp != nil {
r.Data = resp.Data
r.Errors = resp.Errors
r.Extensions = resp.Extensions
}
return c.c.MakeRequest(ctx, &graphql.Request{
Query: req.Query,
Variables: req.Variables,
OpName: req.OpName,
}, &r)
}
// Request contains all the values required to build queries executed by
// the graphql.Client.
//
// Typically, GraphQL APIs will accept a JSON payload of the form
//
// {"query": "query myQuery { ... }", "variables": {...}}`
//
// and Request marshals to this format. However, MakeRequest may
// marshal the data in some other way desired by the backend.
type Request struct {
// The literal string representing the GraphQL query, e.g.
// `query myQuery { myField }`.
Query string `json:"query"`
// A JSON-marshalable value containing the variables to be sent
// along with the query, or nil if there are none.
Variables interface{} `json:"variables,omitempty"`
// The GraphQL operation name. The server typically doesn't
// require this unless there are multiple queries in the
// document, but genqlient sets it unconditionally anyway.
OpName string `json:"operationName"`
}
// Response that contains data returned by the GraphQL API.
//
// Typically, GraphQL APIs will return a JSON payload of the form
//
// {"data": {...}, "errors": {...}}
//
// It may additionally contain a key named "extensions", that
// might hold GraphQL protocol extensions. Extensions and Errors
// are optional, depending on the values returned by the server.
type Response struct {
Data interface{} `json:"data"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
Errors gqlerror.List `json:"errors,omitempty"`
}
type errorWrappedClient struct {
graphql.Client
}
func (c errorWrappedClient) MakeRequest(ctx context.Context, req *graphql.Request, resp *graphql.Response) error {
err := c.Client.MakeRequest(ctx, req, resp)
if err != nil {
// return custom error without wrapping to enable casting
if e := getCustomError(err); e != nil {
return e
}
return withErrorHelp(err)
}
return nil
}