query logs

This commit is contained in:
Cadey Ratio 2019-12-08 19:13:36 +00:00
parent 8fc1a84b1b
commit 1869285de4
10 changed files with 317 additions and 96 deletions

View File

@ -14,7 +14,9 @@ import (
"syscall"
"time"
"github.com/facebookgo/flagenv"
shell "github.com/ipfs/go-ipfs-api"
nats "github.com/nats-io/nats.go"
"github.com/perlin-network/life/exec"
"github.com/rogpeppe/go-internal/txtar"
"tulpa.dev/within/wasmcloud/cmd/internal"
@ -22,20 +24,26 @@ import (
)
var (
ipfsURL = flag.String("ipfs-host", "localhost:5001", "IPFS host (must have pubsub experiment enabled)")
natsURL = flag.String("nats-url", nats.DefaultURL, "nats URL")
ipfsURL = flag.String("ipfs-host", "localhost:5001", "IPFS host")
workerCount = flag.Int("worker-count", 1, "number of wasm executor workers")
gasLimit = flag.Int("gas-limit", 1048576, "number of wasm instructions per execution")
ramLimit = flag.Int("ram-limit", 128, "number of wasm pages that can be used")
loopTimeout = flag.Duration("loop-timeout", 30*time.Second, "idle time per loop")
)
func main() {
flagenv.Parse()
flag.Parse()
log.SetFlags(log.LstdFlags | log.Lshortfile)
sh := shell.NewShell(*ipfsURL)
subsc, err := sh.PubSubSubscribe(internal.TopicName)
nc, err := nats.Connect(*natsURL)
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -53,15 +61,19 @@ func main() {
wg.Add(*workerCount)
for range make([]struct{}, *workerCount) {
go waitForNewWASM(ctx, &wg, sh, subsc)
go waitForNewWASM(ctx, &wg, sh, nc)
}
log.Printf("waiting for work on %s", internal.TopicName)
wg.Wait()
}
func waitForNewWASM(ctx context.Context, wg *sync.WaitGroup, sh *shell.Shell, subsc *shell.PubSubSubscription) {
func waitForNewWASM(ctx context.Context, wg *sync.WaitGroup, sh *shell.Shell, nc *nats.Conn) {
defer wg.Done()
subsc, err := nc.QueueSubscribeSync(internal.TopicName, "workers")
if err != nil {
return
}
for {
select {
@ -70,8 +82,12 @@ func waitForNewWASM(ctx context.Context, wg *sync.WaitGroup, sh *shell.Shell, su
default:
}
msg, err := subsc.Next()
msg, err := subsc.NextMsg(*loopTimeout)
if err != nil {
if err == nats.ErrTimeout {
continue
}
log.Printf("error getting message: %v", err)
return
}
@ -125,7 +141,7 @@ func waitForNewWASM(ctx context.Context, wg *sync.WaitGroup, sh *shell.Shell, su
}
arc := txtar.Archive{
Comment: []byte(fmt.Sprintf("%s: execution of %s at %s", er.UUID, er.WASMCID, result.StartTime.Format(time.RFC3339))),
Comment: []byte(fmt.Sprintf("%s: execution of %s (%s) at %s", er.UUID, er.Name, er.WASMCID, result.StartTime.Format(time.RFC3339))),
Files: []txtar.File{
{
Name: "logs.txt",
@ -162,6 +178,6 @@ func waitForNewWASM(ctx context.Context, wg *sync.WaitGroup, sh *shell.Shell, su
UUID: er.UUID,
}
repMsg, _ := json.Marshal(resp)
sh.PubSubPublish(er.UUID, string(repMsg))
msg.Respond(repMsg)
}
}

View File

@ -0,0 +1,96 @@
package main
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"time"
"github.com/google/subcommands"
"tulpa.dev/within/wasmcloud/cmd/internal"
)
type handlerCreateCmd struct {
abi string
}
func (handlerCreateCmd) Name() string { return "create" }
func (handlerCreateCmd) Synopsis() string { return "create a new handler" }
func (handlerCreateCmd) Usage() string {
return `wasmcloud create [options] <filename.wasm>
$ wasmcloud create filename.wasm
$ wasmcloud create -abi dagger filename.wasm
Creates a new handler on wasmcloud. Returns a name you can use to invoke the
function.
Flags:
`
}
func (h *handlerCreateCmd) SetFlags(fs *flag.FlagSet) {
fs.StringVar(&h.abi, "abi", "cwa", "WebAssembly ABI to use for the handler")
}
func (h handlerCreateCmd) Execute(ctx context.Context, fs *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
if fs.NArg() != 1 {
fmt.Println("usage: wasmcloud create [options] <filename>")
return subcommands.ExitUsageError
}
fname := fs.Arg(0)
data, err := ioutil.ReadFile(fname)
if err != nil {
log.Fatal(err)
}
hdlr := internal.Handler{
ABI: h.abi,
WASM: data,
}
bodyData, _ := json.Marshal(hdlr)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, *apiServer+"/api/handler/create", bytes.NewBuffer(bodyData))
if err != nil {
log.Fatal(err)
}
withAPI(req)
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
io.Copy(os.Stdout, resp.Body)
return subcommands.ExitFailure
}
type apiResp struct {
ID int `json:"ID"`
CreatedAt time.Time `json:"CreatedAt"`
UpdatedAt time.Time `json:"UpdatedAt"`
Name string `json:"Name"`
Path string `json:"Path"`
}
var result apiResp
err = json.NewDecoder(resp.Body).Decode(&result)
resp.Body.Close()
if err != nil {
log.Fatal(err)
}
fmt.Println("created new handler")
fmt.Printf("name: %s\nIPFS ID: %s\n", result.Name, result.Path)
return subcommands.ExitSuccess
}

View File

@ -35,11 +35,13 @@ func (l *loginCmd) SetFlags(fs *flag.FlagSet) {
fs.StringVar(&l.username, "username", "", "wasmcloud username")
}
func (l *loginCmd) Execute(ctx context.Context, fs *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
func init() {
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
}
func (l *loginCmd) Execute(ctx context.Context, fs *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
cfg, err := loadConfig()
if err != nil {
log.Printf("error loading config: %v", err)

View File

@ -20,8 +20,8 @@ func main() {
subcommands.Register(subcommands.CommandsCommand(), "")
subcommands.Register(&loginCmd{}, "api")
subcommands.Register(&whoamiCmd{}, "api")
subcommands.Register(&handlerCreateCmd{}, "handlers")
subcommands.Register(namegenCmd{}, "utils")
subcommands.Register(&testWorkCommand{}, "utils")
subcommands.Register(&runCmd{}, "utils")
subcommands.ImportantFlag("api-server")
subcommands.ImportantFlag("config")

View File

@ -1,82 +0,0 @@
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"github.com/google/subcommands"
"github.com/google/uuid"
shell "github.com/ipfs/go-ipfs-api"
"github.com/rogpeppe/go-internal/txtar"
"tulpa.dev/within/wasmcloud/cmd/internal"
)
type testWorkCommand struct {
wasmCID, abi, ipfsURL string
}
func (testWorkCommand) Name() string { return "test-work" }
func (testWorkCommand) Synopsis() string { return "sends a test work message over IPFS" }
func (testWorkCommand) Usage() string { return "TODO(within): this" }
func (t *testWorkCommand) SetFlags(fs *flag.FlagSet) {
fs.StringVar(&t.wasmCID, "wasm-cid", "", "CID for the WASM to run")
fs.StringVar(&t.abi, "abi", "cwa", "which ABI to use")
fs.StringVar(&t.ipfsURL, "ipfs-host", "127.0.0.1:5001", "IPFS host")
}
func (t testWorkCommand) Execute(ctx context.Context, fs *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
sh := shell.NewShell(t.ipfsURL)
rdr, err := sh.Cat(t.wasmCID)
if err != nil {
log.Fatalf("error getting %s: %v", t.wasmCID, err)
}
io.Copy(ioutil.Discard, rdr)
rdr.Close()
er := internal.ExecRequest{
WASMCID: t.wasmCID,
ABI: internal.ABI(t.abi),
Data: []byte("hello, world"),
Env: map[string]string{
"MAGIC_CONCH": "yes",
},
UUID: uuid.New().String(),
}
data, err := json.Marshal(er)
if err != nil {
panic(err)
}
err = sh.PubSubPublish(internal.TopicName, string(data))
if err != nil {
log.Printf("can't publish data: %v", err)
return subcommands.ExitFailure
}
subsc, err := sh.PubSubSubscribe(er.UUID)
if err != nil {
log.Fatal(err)
}
msg, err := subsc.Next()
if err != nil {
log.Fatal(err)
}
var execResp internal.ExecResponse
err = json.Unmarshal(msg.Data, &execResp)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(txtar.Format(&execResp.Logs)))
return subcommands.ExitSuccess
}

View File

@ -5,8 +5,12 @@ import (
"encoding/json"
"io/ioutil"
"net/http"
"time"
"github.com/go-interpreter/wagon/wasm"
"github.com/google/uuid"
shell "github.com/ipfs/go-ipfs-api"
"github.com/rogpeppe/go-internal/txtar"
"tulpa.dev/within/wasmcloud/cmd/internal"
"within.website/ln"
"within.website/olin/namegen"
@ -20,7 +24,7 @@ func createHandler(w http.ResponseWriter, r *http.Request, u *User) {
ctx := r.Context()
sh := shell.NewShell(*ipfsURL)
data, err := ioutil.ReadAll(r.Body)
data, err := ioutil.ReadAll(http.MaxBytesReader(w, r.Body, 16*1024*1024))
if err != nil {
ln.Error(ctx, err)
http.Error(w, "invalid data", http.StatusBadRequest)
@ -44,6 +48,13 @@ func createHandler(w http.ResponseWriter, r *http.Request, u *User) {
return
}
buf := bytes.NewBuffer(hdlr.WASM)
_, err = wasm.DecodeModule(buf)
if err != nil {
ln.Error(ctx, err)
http.Error(w, "not webassembly", http.StatusBadRequest)
}
cid, err := sh.Add(bytes.NewBuffer(hdlr.WASM))
if err != nil {
ln.Error(ctx, err)
@ -67,3 +78,138 @@ func createHandler(w http.ResponseWriter, r *http.Request, u *User) {
json.NewEncoder(w).Encode(h)
}
func listHandlers(w http.ResponseWriter, r *http.Request, u *User) {
ctx := r.Context()
var hdlrs []Handler
err := db.Where("user_id = ?", u.ID).Scan(&hdlrs).Error
if err != nil {
ln.Error(ctx, err)
http.Error(w, "can't read handlers", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(hdlrs)
}
func getLogs(w http.ResponseWriter, r *http.Request, u *User) {
ctx := r.Context()
q := r.URL.Query()
name := q.Get("name")
if name == "" {
http.NotFound(w, r)
return
}
var hdlr Handler
err := db.Where("name = ? AND user_id = ?", name, u.ID).First(&hdlr).Error
if err != nil {
ln.Error(ctx, err)
http.NotFound(w, r)
return
}
var elogs []ExecutionLog
err = db.Where("handler_id = ?", hdlr.ID).Find(&elogs).Error
if err != nil {
ln.Error(ctx, err)
http.Error(w, "handler id not found", http.StatusInternalServerError)
return
}
type resultLine struct {
ExecID string `json:"exec_id"`
Comment string `json:"comment"`
Logs map[string]string `json:"logs"`
}
var result []resultLine
for _, elog := range elogs {
arc := txtar.Parse(elog.Data)
logs := map[string]string{}
for _, file := range arc.Files {
logs[file.Name] = string(file.Data)
}
result = append(result, resultLine{
ExecID: elog.RunID,
Comment: string(arc.Comment),
Logs: logs,
})
}
json.NewEncoder(w).Encode(result)
}
func invokeHandler(w http.ResponseWriter, r *http.Request, u *User) {
ctx := r.Context()
q := r.URL.Query()
name := q.Get("name")
if name == "" {
http.NotFound(w, r)
return
}
var hdlr Handler
err := db.Where("name = ?", name).First(&hdlr).Error
if err != nil {
ln.Error(ctx, err)
http.NotFound(w, r)
return
}
data, err := ioutil.ReadAll(http.MaxBytesReader(w, r.Body, 1*1024*1024))
if err != nil {
ln.Error(ctx, err)
http.NotFound(w, r)
}
execID := uuid.New().String()
er := internal.ExecRequest{
WASMCID: hdlr.Path,
Name: hdlr.Name,
Data: data,
Env: map[string]string{
"RUN_ID": execID,
},
UUID: execID,
}
encData, err := json.Marshal(er)
if err != nil {
ln.Error(ctx, err)
http.NotFound(w, r)
}
go func() {
msg, err := nc.Request(internal.TopicName, encData, 5*time.Minute)
if err != nil {
ln.Error(ctx, err)
return
}
var resp internal.ExecResponse
err = json.Unmarshal(msg.Data, &resp)
if err != nil {
ln.Error(ctx, err)
return
}
data := txtar.Format(&resp.Logs)
entry := ExecutionLog{
HandlerID: hdlr.ID,
RunID: execID,
Data: data,
}
err = db.Save(&entry).Error
if err != nil {
ln.Error(ctx, err)
return
}
}()
}

View File

@ -10,6 +10,7 @@ import (
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
nats "github.com/nats-io/nats.go"
)
var (
@ -18,8 +19,10 @@ var (
databaseKind = flag.String("database-kind", "sqlite3", "database kind")
defaultTokenLifetime = flag.Duration("default-token-lifetime", 500*24*time.Hour, "default token lifetime")
ipfsURL = flag.String("ipfs-host", "127.0.0.1:5001", "URL of the IPFS API")
natsURL = flag.String("nats-url", nats.DefaultURL, "URL of the nats server")
db *gorm.DB
nc *nats.Conn
)
func main() {
@ -32,9 +35,15 @@ func main() {
}
log.Println("migrating")
gormDB.AutoMigrate(User{}, Token{}, Handler{})
gormDB.AutoMigrate(User{}, Token{}, Handler{}, ExecutionLog{})
db = gormDB
natsClient, err := nats.Connect(*natsURL)
if err != nil {
log.Fatal(err)
}
nc = natsClient
rtr := mux.NewRouter()
// auth
@ -46,8 +55,14 @@ func main() {
rtr.HandleFunc("/", unauthenticatedShowAPage("index"))
rtr.HandleFunc("/control/", authenticatedShowAPage("controlindex"))
rtr.HandleFunc("/api/whoami", makeHandler(true, apiWhoami))
// API routes
rtr.HandleFunc("/api/handler", makeHandler(true, listHandlers)).Methods(http.MethodGet)
rtr.HandleFunc("/api/handler/create", makeHandler(true, createHandler)).Methods(http.MethodPost)
rtr.HandleFunc("/api/handler/logs", makeHandler(true, getLogs)).Methods(http.MethodGet)
rtr.HandleFunc("/api/whoami", makeHandler(true, apiWhoami))
// invocation
rtr.HandleFunc("/invoke", makeHandler(false, invokeHandler)).Methods(http.MethodPost)
rtr.PathPrefix("/static/").Handler(http.FileServer(http.Dir(".")))

View File

@ -26,10 +26,17 @@ type Token struct {
ExpiresAt time.Time
}
type ExecutionLog struct {
gorm.Model
RunID string `gorm:"unique;not null"`
HandlerID uint
Data []byte `gorm:"not null"`
}
type Handler struct {
gorm.Model
Name string
Path string
Name string `gorm:"unique;not null"`
Path string `gorm:"unique;not null"`
UserID uint
User User
}

3
go.mod
View File

@ -7,6 +7,7 @@ replace github.com/go-interpreter/wagon v0.0.0 => github.com/perlin-network/wago
require (
github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456
github.com/fatih/color v1.7.0 // indirect
github.com/go-interpreter/wagon v0.0.0
github.com/google/subcommands v1.0.1
github.com/google/uuid v1.1.1
github.com/gorilla/mux v1.7.3
@ -15,6 +16,8 @@ require (
github.com/jinzhu/gorm v1.9.11
github.com/manifoldco/promptui v0.6.0
github.com/mattn/go-sqlite3 v2.0.1+incompatible
github.com/nats-io/nats-server/v2 v2.1.2 // indirect
github.com/nats-io/nats.go v1.9.1
github.com/perlin-network/life v0.0.0-20190204091834-d05763d11050
github.com/rogpeppe/go-internal v1.5.0
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392

18
go.sum
View File

@ -100,6 +100,7 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -230,6 +231,21 @@ github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d
github.com/multiformats/go-multihash v0.0.1 h1:HHwN1K12I+XllBCrqKnhX949Orn4oawPkegHMu2vDqQ=
github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0 h1:xdnzwFETV++jNc4W1mw//qFyJGb2ABOombmZJQS4+Qo=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2 h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server v1.4.1 h1:Ul1oSOGNV/L8kjr4v6l2f9Yet6WY+LevH1/7cRZ/qyA=
github.com/nats-io/nats-server/v2 v2.1.2 h1:i2Ly0B+1+rzNZHHWtD4ZwKi+OU5l+uQo1iDHZ2PmiIc=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1 h1:ik3HbLhZ0YABLto7iX80pZLPw/6dx3T+++MZJwLnMrQ=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0 h1:qMd4+pRHgdr1nAClu+2h/2a5F2TmKcCzjCDazVgRoX4=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3 h1:6JrEfig+HzTH85yxzhSVbjHRJv9cn0p6n3IngIcM5/k=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nicksnyder/go-i18n v1.10.1 h1:isfg77E/aCD7+0lD/D00ebR2MV5vgeQ276WYyDaCRQc=
github.com/nicksnyder/go-i18n v1.10.1/go.mod h1:e4Di5xjP9oTVrC6y3C7C0HoSYXjSbhh/dU0eUV32nB4=
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
@ -324,6 +340,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -370,6 +387,7 @@ golang.org/x/sys v0.0.0-20190219203350-90b0e4468f99/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190302025703-b6889370fb10/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=