Initial commit

jobatzil/rename
Tan Nhu 2022-08-09 12:37:37 -07:00
commit 5891818ce1
304 changed files with 28486 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
NOTES*
_research
.env
*.sqlite
*.sqlite3
web/node_modules
web/dist/files
release
my-app

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"go.testTags": "sqlite_fts5"
}

2
CHANGELOG.md Normal file
View File

@ -0,0 +1,2 @@
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this pipeline adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

93
LICENSE.md Normal file
View File

@ -0,0 +1,93 @@
# PolyForm Free Trial License 1.0.0
<https://polyformproject.org/licenses/free-trial/1.0.0>
## Acceptance
In order to get any license under these terms, you must agree
to them as both strict obligations and conditions to all
your licenses.
## Copyright License
The licensor grants you a copyright license for the software
to do everything you might do with the software that would
otherwise infringe the licensor's copyright in it for any
permitted purpose. However, you may only make changes or
new works based on the software according to [Changes and New
Works License](#changes-and-new-works-license), and you may
not distribute copies of the software.
## Changes and New Works License
The licensor grants you an additional copyright license to
make changes and new works based on the software for any
permitted purpose.
## Patent License
The licensor grants you a patent license for the software that
covers patent claims the licensor can license, or becomes able
to license, that you would infringe by using the software.
## Fair Use
You may have "fair use" rights for the software under the
law. These terms do not limit them.
## Free Trial
Use to evaluate whether the software suits a particular
application for less than 32 consecutive calendar days, on
behalf of you or your company, is use for a permitted purpose.
## No Other Rights
These terms do not allow you to sublicense or transfer any of
your licenses to anyone else, or prevent the licensor from
granting licenses to anyone else. These terms do not imply
any other licenses.
## Patent Defense
If you make any written claim that the software infringes or
contributes to infringement of any patent, your patent license
for the software granted under these terms ends immediately. If
your company makes such a claim, your patent license ends
immediately for work on behalf of your company.
## Violations
If you violate any of these terms, or do anything with the
software not covered by your licenses, all your licenses
end immediately.
## No Liability
***As far as the law allows, the software comes as is, without
any warranty or condition, and the licensor will not be liable
to you for any damages arising out of these terms or the use
or nature of the software, under any kind of legal claim.***
## Definitions
The **licensor** is the individual or entity offering these
terms, and the **software** is the software the licensor makes
available under these terms.
**You** refers to the individual or entity agreeing to these
terms.
**Your company** is any legal entity, sole proprietorship,
or other kind of organization that you work for, plus all
organizations that have control over, are under the control of,
or are under common control with that organization. **Control**
means ownership of substantially all the assets of an entity,
or the power to direct its management and policies by vote,
contract, or otherwise. Control can be direct or indirect.
**Your licenses** are all the licenses granted to you for the
software under these terms.
**Use** means anything you do with the software requiring one
of your licenses.

111
README.md Normal file
View File

@ -0,0 +1,111 @@
# Pre-Requisites
Install the latest stable version of Node and Go version 1.17 or higher, and then install the below Go programs. Ensure the GOPATH [bin directory](https://go.dev/doc/gopath_code#GOPATH) is added to your PATH.
```text
$ go install github.com/golang/mock/mockgen@latest
$ go install github.com/google/wire/cmd/wire@latest
```
# Build
Build the user interface:
```text
$ pushd web
$ npm install
$ npm run build
$ popd
```
Build the server and command line tools:
```text
$ go generate ./...
$ go build -o release/my-app
```
# Test
Execute the unit tests:
```text
$ go generate ./...
$ go test -v -cover ./...
```
# Run
This project supports all operating systems and architectures supported by Go. This means you can build and run the system on your machine; docker containers are not required for local development and testing.
Start the server at `localhost:3000`
```text
$ release/my-app server
```
# User Interface
This project includes a simple user interface for interacting with the system. When you run the application, you can access the user interface by navigating to `http://localhost:3000` in your browser.
# Swagger
This project includes a swagger specification. When you run the application, you can access the swagger specification by navigating to `http://localhost:3000/swagger` in your browser.
# Command Line
This project includes simple command line tools for interacting with the system. Please remember that you must start the server before you can execute commands.
Register a new user:
```text
$ release/my-app register
```
Login to the application:
```text
$ release/my-app login
```
Logout from the application:
```text
$ release/my-app logout
```
View your account details:
```text
$ release/my-app account
```
Generate a peronsal access token:
```text
$ release/my-app token
```
Create a pipeline:
```text
$ release/my-app pipeline create <name>
```
List pipelines:
```text
$ release/my-app pipeline ls
```
Debug and output http responses from the server:
```text
$ DEBUG=true release/my-app pipeline ls
```
View all commands:
```text
$ release/my-app --help
```

60
Taskfile.yml Normal file
View File

@ -0,0 +1,60 @@
version: '3'
tasks:
test:
cmds:
- go test -v -cover ./...
# postgres tests are currently failing due to issues
# with the seed.sql file.
test-postgres:
env:
DATABASE_DRIVER: postgres
DATABASE_CONFIG: host=localhost user=postgres password=postgres dbname=postgres sslmode=disable
GO111MODULE: 'on'
cmds:
- cmd: docker kill postgres
ignore_error: true
silent: false
- silent: false
cmd: >
docker run
-p 5432:5432
--env POSTGRES_PASSWORD=postgres
--env POSTGRES_USER=postgres
--name postgres
--detach
--rm
postgres:9-alpine
- cmd: go test -v -cover github.com/bradrydzewski/my-app/internal/store/database
- cmd: docker kill postgres
silent: true
setup:
cmds:
- cd web; npm install
- cd web; npm run build
- go generate ./...
- go build
teardown:
cmds:
- rm -rf release
- rm -rf web/.cache
- rm -rf web/dist/files
- rm -rf web/node_modules
- rm -rf web/.env.development.local
- rm -rf web/swagger.yaml
- rm -rf my-app
- rm -rf database.sqlite3
- rm -rf .env
docker-build:
cmds:
- docker build -t bradrydzewski/my-app:linux-amd64 -f docker/Dockerfile .
docker-build-all:
cmds:
- docker build -t bradrydzewski/my-app:linux-amd64 -f docker/Dockerfile .
- docker build -t bradrydzewski/my-app:linux-arm64 -f docker/Dockerfile.linux.arm64 .
- docker build -t bradrydzewski/my-app:linux-arm -f docker/Dockerfile.linux.arm .

48
cli/cli.go Normal file
View File

@ -0,0 +1,48 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package cli
import (
"context"
"os"
"github.com/bradrydzewski/my-app/cli/execution"
"github.com/bradrydzewski/my-app/cli/pipeline"
"github.com/bradrydzewski/my-app/cli/server"
"github.com/bradrydzewski/my-app/cli/token"
"github.com/bradrydzewski/my-app/cli/user"
"github.com/bradrydzewski/my-app/cli/users"
"github.com/bradrydzewski/my-app/version"
"gopkg.in/alecthomas/kingpin.v2"
)
// empty context
var nocontext = context.Background()
// application name
var application = "my-app"
// application description
var description = "description goes here" // TODO edit this application description
// Command parses the command line arguments and then executes a
// subcommand program.
func Command() {
app := kingpin.New(application, description)
server.Register(app)
user.Register(app)
pipeline.Register(app)
execution.Register(app)
users.Register(app)
token.Register(app)
registerLogin(app)
registerLogout(app)
registerRegister(app)
registerSwagger(app)
kingpin.Version(version.Version.String())
kingpin.MustParse(app.Parse(os.Args[1:]))
}

82
cli/execution/create.go Normal file
View File

@ -0,0 +1,82 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package execution
import (
"encoding/json"
"os"
"text/template"
"github.com/bradrydzewski/my-app/cli/util"
"github.com/bradrydzewski/my-app/types"
"github.com/drone/funcmap"
"gopkg.in/alecthomas/kingpin.v2"
)
type createCommand struct {
pipeline string
slug string
name string
desc string
tmpl string
json bool
}
func (c *createCommand) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
in := &types.Execution{
Slug: c.slug,
Name: c.name,
Desc: c.desc,
}
item, err := client.ExecutionCreate(c.pipeline, in)
if err != nil {
return err
}
if c.json {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(item)
}
tmpl, err := template.New("_").Funcs(funcmap.Funcs).Parse(c.tmpl)
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, item)
}
// helper function registers the user create command
func registerCreate(app *kingpin.CmdClause) {
c := new(createCommand)
cmd := app.Command("create", "create a execution").
Action(c.run)
cmd.Arg("pipeline ", "pipeline slug").
Required().
StringVar(&c.pipeline)
cmd.Arg("slug ", "execution slug").
Required().
StringVar(&c.slug)
cmd.Flag("name", "execution name").
StringVar(&c.name)
cmd.Flag("desc", "execution description").
StringVar(&c.desc)
cmd.Flag("json", "json encode the output").
BoolVar(&c.json)
cmd.Flag("format", "format the output using a Go template").
Default(executionTmpl).
Hidden().
StringVar(&c.tmpl)
}

40
cli/execution/delete.go Normal file
View File

@ -0,0 +1,40 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package execution
import (
"github.com/bradrydzewski/my-app/cli/util"
"gopkg.in/alecthomas/kingpin.v2"
)
type deleteCommand struct {
pipeline string
slug string
}
func (c *deleteCommand) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
return client.ExecutionDelete(c.pipeline, c.slug)
}
// helper function registers the user delete command
func registerDelete(app *kingpin.CmdClause) {
c := new(deleteCommand)
cmd := app.Command("delete", "delete a execution").
Action(c.run)
cmd.Arg("pipeline ", "pipeline slug").
Required().
StringVar(&c.pipeline)
cmd.Arg("slug ", "execution slug").
Required().
StringVar(&c.slug)
}

View File

@ -0,0 +1,17 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package execution
import "gopkg.in/alecthomas/kingpin.v2"
// Register the command.
func Register(app *kingpin.Application) {
cmd := app.Command("execution", "manage executions")
registerFind(cmd)
registerList(cmd)
registerCreate(cmd)
registerUpdate(cmd)
registerDelete(cmd)
}

68
cli/execution/find.go Normal file
View File

@ -0,0 +1,68 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package execution
import (
"encoding/json"
"os"
"text/template"
"github.com/bradrydzewski/my-app/cli/util"
"github.com/drone/funcmap"
"gopkg.in/alecthomas/kingpin.v2"
)
type findCommand struct {
pipeline string
slug string
tmpl string
json bool
}
func (c *findCommand) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
item, err := client.Execution(c.pipeline, c.slug)
if err != nil {
return err
}
if c.json {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(item)
}
tmpl, err := template.New("_").Funcs(funcmap.Funcs).Parse(c.tmpl + "\n")
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, item)
}
// helper function registers the user find command
func registerFind(app *kingpin.CmdClause) {
c := new(findCommand)
cmd := app.Command("find", "display pipeline details").
Action(c.run)
cmd.Arg("pipeline ", "pipeline slug").
Required().
StringVar(&c.pipeline)
cmd.Arg("slug ", "execution slug").
Required().
StringVar(&c.slug)
cmd.Flag("json", "json encode the output").
BoolVar(&c.json)
cmd.Flag("format", "format the output using a Go template").
Default(executionTmpl).
Hidden().
StringVar(&c.tmpl)
}

85
cli/execution/list.go Normal file
View File

@ -0,0 +1,85 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package execution
import (
"encoding/json"
"os"
"text/template"
"github.com/bradrydzewski/my-app/cli/util"
"github.com/bradrydzewski/my-app/types"
"github.com/drone/funcmap"
"gopkg.in/alecthomas/kingpin.v2"
)
const executionTmpl = `
id: {{ .ID }}
slug: {{ .Slug }}
name: {{ .Name }}
desc: {{ .Desc }}
`
type listCommand struct {
slug string
tmpl string
json bool
page int
size int
}
func (c *listCommand) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
list, err := client.ExecutionList(c.slug, types.Params{
Size: c.size,
Page: c.page,
})
if err != nil {
return err
}
if c.json {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(list)
}
tmpl, err := template.New("_").Funcs(funcmap.Funcs).Parse(c.tmpl + "\n")
if err != nil {
return err
}
for _, item := range list {
tmpl.Execute(os.Stdout, item)
}
return nil
}
// helper function registers the list command
func registerList(app *kingpin.CmdClause) {
c := new(listCommand)
cmd := app.Command("ls", "display a list of executions").
Action(c.run)
cmd.Arg("pipeline ", "pipeline slug").
Required().
StringVar(&c.slug)
cmd.Flag("page", "page number").
IntVar(&c.page)
cmd.Flag("per-page", "page size").
IntVar(&c.size)
cmd.Flag("json", "json encode the output").
BoolVar(&c.json)
cmd.Flag("format", "format the output using a Go template").
Default(executionTmpl).
Hidden().
StringVar(&c.tmpl)
}

87
cli/execution/update.go Normal file
View File

@ -0,0 +1,87 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package execution
import (
"encoding/json"
"os"
"text/template"
"github.com/bradrydzewski/my-app/cli/util"
"github.com/bradrydzewski/my-app/types"
"github.com/gotidy/ptr"
"github.com/drone/funcmap"
"gopkg.in/alecthomas/kingpin.v2"
)
type updateCommand struct {
pipeline string
slug string
name string
desc string
tmpl string
json bool
}
func (c *updateCommand) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
in := new(types.ExecutionInput)
if v := c.name; v != "" {
in.Name = ptr.String(v)
}
if v := c.desc; v != "" {
in.Desc = ptr.String(v)
}
item, err := client.ExecutionUpdate(c.pipeline, c.slug, in)
if err != nil {
return err
}
if c.json {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(item)
}
tmpl, err := template.New("_").Funcs(funcmap.Funcs).Parse(c.tmpl)
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, item)
}
// helper function registers the update command
func registerUpdate(app *kingpin.CmdClause) {
c := new(updateCommand)
cmd := app.Command("update", "update a execution").
Action(c.run)
cmd.Arg("pipeline ", "pipeline slug").
Required().
StringVar(&c.pipeline)
cmd.Arg("slug ", "execution slug").
Required().
StringVar(&c.slug)
cmd.Flag("name", "update pipeline name").
StringVar(&c.name)
cmd.Flag("desc", "update pipeline description").
StringVar(&c.desc)
cmd.Flag("json", "json encode the output").
BoolVar(&c.json)
cmd.Flag("format", "format the output using a Go template").
Default(executionTmpl).
Hidden().
StringVar(&c.tmpl)
}

50
cli/login.go Normal file
View File

@ -0,0 +1,50 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package cli
import (
"encoding/json"
"io/ioutil"
"github.com/bradrydzewski/my-app/cli/util"
"github.com/bradrydzewski/my-app/client"
"gopkg.in/alecthomas/kingpin.v2"
)
type loginCommand struct {
server string
}
func (c *loginCommand) run(*kingpin.ParseContext) error {
username, password := util.Credentials()
client := client.New(c.server)
token, err := client.Login(username, password)
if err != nil {
return err
}
path, err := util.Config()
if err != nil {
return err
}
token.Address = c.server
data, err := json.Marshal(token)
if err != nil {
return err
}
return ioutil.WriteFile(path, data, 0600)
}
// helper function to register the logout command.
func registerLogin(app *kingpin.Application) {
c := new(loginCommand)
cmd := app.Command("login", "login to the remote server").
Action(c.run)
cmd.Arg("server", "server address").
Default("http://localhost:3000").
StringVar(&c.server)
}

31
cli/logout.go Normal file
View File

@ -0,0 +1,31 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package cli
import (
"os"
"github.com/bradrydzewski/my-app/cli/util"
"gopkg.in/alecthomas/kingpin.v2"
)
type logoutCommand struct{}
func (c *logoutCommand) run(*kingpin.ParseContext) error {
path, err := util.Config()
if err != nil {
return err
}
return os.Remove(path)
}
// helper function to register the logout command.
func registerLogout(app *kingpin.Application) {
c := new(logoutCommand)
app.Command("logout", "logout from the remote server").
Action(c.run)
}

77
cli/pipeline/create.go Normal file
View File

@ -0,0 +1,77 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipeline
import (
"encoding/json"
"os"
"text/template"
"github.com/bradrydzewski/my-app/cli/util"
"github.com/bradrydzewski/my-app/types"
"github.com/drone/funcmap"
"gopkg.in/alecthomas/kingpin.v2"
)
type createCommand struct {
slug string
name string
desc string
tmpl string
json bool
}
func (c *createCommand) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
in := &types.Pipeline{
Slug: c.slug,
Name: c.name,
Desc: c.desc,
}
item, err := client.PipelineCreate(in)
if err != nil {
return err
}
if c.json {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(item)
}
tmpl, err := template.New("_").Funcs(funcmap.Funcs).Parse(c.tmpl)
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, item)
}
// helper function registers the user create command
func registerCreate(app *kingpin.CmdClause) {
c := new(createCommand)
cmd := app.Command("create", "create a pipeline").
Action(c.run)
cmd.Arg("slug", "pipeline slug").
Required().
StringVar(&c.slug)
cmd.Flag("name", "pipeline name").
StringVar(&c.name)
cmd.Flag("desc", "pipeline description").
StringVar(&c.desc)
cmd.Flag("json", "json encode the output").
BoolVar(&c.json)
cmd.Flag("format", "format the output using a Go template").
Default(pipelineTmpl).
Hidden().
StringVar(&c.tmpl)
}

35
cli/pipeline/delete.go Normal file
View File

@ -0,0 +1,35 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipeline
import (
"github.com/bradrydzewski/my-app/cli/util"
"gopkg.in/alecthomas/kingpin.v2"
)
type deleteCommand struct {
slug string
}
func (c *deleteCommand) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
return client.PipelineDelete(c.slug)
}
// helper function registers the user delete command
func registerDelete(app *kingpin.CmdClause) {
c := new(deleteCommand)
cmd := app.Command("delete", "delete a pipeline").
Action(c.run)
cmd.Arg("slug ", "pipeline slug").
Required().
StringVar(&c.slug)
}

63
cli/pipeline/find.go Normal file
View File

@ -0,0 +1,63 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipeline
import (
"encoding/json"
"os"
"text/template"
"github.com/bradrydzewski/my-app/cli/util"
"github.com/drone/funcmap"
"gopkg.in/alecthomas/kingpin.v2"
)
type findCommand struct {
slug string
tmpl string
json bool
}
func (c *findCommand) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
item, err := client.Pipeline(c.slug)
if err != nil {
return err
}
if c.json {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(item)
}
tmpl, err := template.New("_").Funcs(funcmap.Funcs).Parse(c.tmpl + "\n")
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, item)
}
// helper function registers the user find command
func registerFind(app *kingpin.CmdClause) {
c := new(findCommand)
cmd := app.Command("find", "display pipeline details").
Action(c.run)
cmd.Arg("slug", "pipeline slug").
Required().
StringVar(&c.slug)
cmd.Flag("json", "json encode the output").
BoolVar(&c.json)
cmd.Flag("format", "format the output using a Go template").
Default(pipelineTmpl).
Hidden().
StringVar(&c.tmpl)
}

80
cli/pipeline/list.go Normal file
View File

@ -0,0 +1,80 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipeline
import (
"encoding/json"
"os"
"text/template"
"github.com/bradrydzewski/my-app/cli/util"
"github.com/bradrydzewski/my-app/types"
"github.com/drone/funcmap"
"gopkg.in/alecthomas/kingpin.v2"
)
const pipelineTmpl = `
id: {{ .ID }}
slug: {{ .Slug }}
name: {{ .Name }}
desc: {{ .Desc }}
`
type listCommand struct {
page int
size int
json bool
tmpl string
}
func (c *listCommand) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
list, err := client.PipelineList(types.Params{
Size: c.size,
Page: c.page,
})
if err != nil {
return err
}
if c.json {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(list)
}
tmpl, err := template.New("_").Funcs(funcmap.Funcs).Parse(c.tmpl + "\n")
if err != nil {
return err
}
for _, item := range list {
tmpl.Execute(os.Stdout, item)
}
return nil
}
// helper function registers the user list command
func registerList(app *kingpin.CmdClause) {
c := new(listCommand)
cmd := app.Command("ls", "display a list of pipelines").
Action(c.run)
cmd.Flag("page", "page number").
IntVar(&c.page)
cmd.Flag("per-page", "page size").
IntVar(&c.size)
cmd.Flag("json", "json encode the output").
BoolVar(&c.json)
cmd.Flag("format", "format the output using a Go template").
Default(pipelineTmpl).
Hidden().
StringVar(&c.tmpl)
}

17
cli/pipeline/pipeline.go Normal file
View File

@ -0,0 +1,17 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipeline
import "gopkg.in/alecthomas/kingpin.v2"
// Register the command.
func Register(app *kingpin.Application) {
cmd := app.Command("pipeline", "manage pipelines")
registerFind(cmd)
registerList(cmd)
registerCreate(cmd)
registerUpdate(cmd)
registerDelete(cmd)
}

82
cli/pipeline/update.go Normal file
View File

@ -0,0 +1,82 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipeline
import (
"encoding/json"
"os"
"text/template"
"github.com/bradrydzewski/my-app/cli/util"
"github.com/bradrydzewski/my-app/types"
"github.com/gotidy/ptr"
"github.com/drone/funcmap"
"gopkg.in/alecthomas/kingpin.v2"
)
type updateCommand struct {
slug string
name string
desc string
tmpl string
json bool
}
func (c *updateCommand) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
in := new(types.PipelineInput)
if v := c.name; v != "" {
in.Name = ptr.String(v)
}
if v := c.desc; v != "" {
in.Desc = ptr.String(v)
}
item, err := client.PipelineUpdate(c.slug, in)
if err != nil {
return err
}
if c.json {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(item)
}
tmpl, err := template.New("_").Funcs(funcmap.Funcs).Parse(c.tmpl)
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, item)
}
// helper function registers the user update command
func registerUpdate(app *kingpin.CmdClause) {
c := new(updateCommand)
cmd := app.Command("update", "update a pipeline").
Action(c.run)
cmd.Arg("slug", "pipeline slug").
Required().
StringVar(&c.slug)
cmd.Flag("name", "update pipeline name").
StringVar(&c.name)
cmd.Flag("desc", "update pipeline description").
StringVar(&c.desc)
cmd.Flag("json", "json encode the output").
BoolVar(&c.json)
cmd.Flag("format", "format the output using a Go template").
Default(pipelineTmpl).
Hidden().
StringVar(&c.tmpl)
}

49
cli/register.go Normal file
View File

@ -0,0 +1,49 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package cli
import (
"encoding/json"
"io/ioutil"
"github.com/bradrydzewski/my-app/cli/util"
"github.com/bradrydzewski/my-app/client"
"gopkg.in/alecthomas/kingpin.v2"
)
type registerCommand struct {
server string
}
func (c *registerCommand) run(*kingpin.ParseContext) error {
username, password := util.Credentials()
client := client.New(c.server)
token, err := client.Register(username, password)
if err != nil {
return err
}
path, err := util.Config()
if err != nil {
return err
}
token.Address = c.server
data, err := json.Marshal(token)
if err != nil {
return err
}
return ioutil.WriteFile(path, data, 0600)
}
// helper function to register the register command.
func registerRegister(app *kingpin.Application) {
c := new(registerCommand)
cmd := app.Command("register", "register a user").
Action(c.run)
cmd.Arg("server", "server address").
Default("http://localhost:3000").
StringVar(&c.server)
}

38
cli/server/config.go Normal file
View File

@ -0,0 +1,38 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package server
import (
"os"
"github.com/bradrydzewski/my-app/types"
"github.com/kelseyhightower/envconfig"
)
// legacy environment variables. the key is the legacy
// variable name, and the value is the new variable name.
var legacy = map[string]string{
// none defined
}
// load returns the system configuration from the
// host environment.
func load() (*types.Config, error) {
// loop through legacy environment variable and, if set
// rewrite to the new variable name.
for k, v := range legacy {
if s, ok := os.LookupEnv(k); ok {
os.Setenv(v, s)
}
}
config := new(types.Config)
// read the configuration from the environment and
// populate the configuration structure.
err := envconfig.Process("", config)
return config, err
}

105
cli/server/server.go Normal file
View File

@ -0,0 +1,105 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package server
import (
"context"
"os"
"github.com/bradrydzewski/my-app/types"
"github.com/bradrydzewski/my-app/version"
"github.com/joho/godotenv"
"github.com/mattn/go-isatty"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"golang.org/x/sync/errgroup"
"gopkg.in/alecthomas/kingpin.v2"
)
type command struct {
envfile string
}
func (c *command) run(*kingpin.ParseContext) error {
// load environment variables from file.
godotenv.Load(c.envfile)
// create the system configuration store by loading
// data from the environment.
config, err := load()
if err != nil {
log.Fatal().Err(err).
Msg("cannot load configuration")
}
// configure the log level
setupLogger(config)
system, err := initSystem(config)
if err != nil {
log.Fatal().Err(err).
Msg("cannot boot server")
}
var g errgroup.Group
// starts the http server.
g.Go(func() error {
log.Info().
Str("port", config.Server.Bind).
Str("revision", version.GitCommit).
Str("repository", version.GitRepository).
Stringer("version", version.Version).
Msg("server started")
return system.server.ListenAndServe(context.Background())
})
// start the purge routine.
g.Go(func() error {
log.Debug().Msg("starting the nightly subroutine")
system.nightly.Run(context.Background())
return nil
})
return g.Wait()
}
// helper function configures the global logger from
// the loaded configuration.
func setupLogger(config *types.Config) {
// configure the log level
switch {
case config.Trace:
zerolog.SetGlobalLevel(zerolog.TraceLevel)
case config.Debug:
zerolog.SetGlobalLevel(zerolog.DebugLevel)
default:
zerolog.SetGlobalLevel(zerolog.InfoLevel)
}
// if the terminal is a tty we should output the
// logs in pretty format
if isatty.IsTerminal(os.Stdout.Fd()) {
log.Logger = log.Output(
zerolog.ConsoleWriter{
Out: os.Stderr,
NoColor: false,
},
)
}
}
// Register the server command.
func Register(app *kingpin.Application) {
c := new(command)
cmd := app.Command("server", "starts the server").
Action(c.run)
cmd.Arg("envfile", "load the environment variable file").
Default("").
StringVar(&c.envfile)
}

24
cli/server/system.go Normal file
View File

@ -0,0 +1,24 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package server
import (
"github.com/bradrydzewski/my-app/internal/cron"
"github.com/bradrydzewski/my-app/internal/server"
)
// system stores high level system sub-routines.
type system struct {
server *server.Server
nightly *cron.Nightly
}
// newSystem returns a new system structure.
func newSystem(server *server.Server, nightly *cron.Nightly) *system {
return &system{
server: server,
nightly: nightly,
}
}

30
cli/server/wire.go Normal file
View File

@ -0,0 +1,30 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
//+build wireinject
package server
import (
"github.com/bradrydzewski/my-app/internal/cron"
"github.com/bradrydzewski/my-app/internal/router"
"github.com/bradrydzewski/my-app/internal/server"
"github.com/bradrydzewski/my-app/internal/store/database"
"github.com/bradrydzewski/my-app/internal/store/memory"
"github.com/bradrydzewski/my-app/types"
"github.com/google/wire"
)
func initSystem(config *types.Config) (*system, error) {
wire.Build(
database.WireSet,
memory.WireSet,
router.WireSet,
server.WireSet,
cron.WireSet,
newSystem,
)
return &system{}, nil
}

34
cli/server/wire_gen.go Normal file
View File

@ -0,0 +1,34 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package server
import (
"github.com/bradrydzewski/my-app/internal/cron"
"github.com/bradrydzewski/my-app/internal/router"
"github.com/bradrydzewski/my-app/internal/server"
"github.com/bradrydzewski/my-app/internal/store/database"
"github.com/bradrydzewski/my-app/internal/store/memory"
"github.com/bradrydzewski/my-app/types"
)
// Injectors from wire.go:
func initSystem(config *types.Config) (*system, error) {
db, err := database.ProvideDatabase(config)
if err != nil {
return nil, err
}
executionStore := database.ProvideExecutionStore(db)
pipelineStore := database.ProvidePipelineStore(db)
userStore := database.ProvideUserStore(db)
systemStore := memory.New(config)
handler := router.New(executionStore, pipelineStore, userStore, systemStore)
serverServer := server.ProvideServer(config, handler)
nightly := cron.NewNightly()
serverSystem := newSystem(serverServer, nightly)
return serverSystem, nil
}

39
cli/swagger.go Normal file
View File

@ -0,0 +1,39 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package cli
import (
"io/ioutil"
"os"
"github.com/bradrydzewski/my-app/internal/api/openapi"
"gopkg.in/alecthomas/kingpin.v2"
)
type swaggerCommand struct {
path string
}
func (c *swaggerCommand) run(*kingpin.ParseContext) error {
spec := openapi.Generate()
data, _ := spec.MarshalYAML()
if c.path == "" {
os.Stdout.Write(data)
return nil
}
return ioutil.WriteFile(c.path, data, 0600)
}
// helper function to register the swagger command.
func registerSwagger(app *kingpin.Application) {
c := new(swaggerCommand)
cmd := app.Command("swagger", "generate swagger file").
Hidden().
Action(c.run)
cmd.Arg("path", "path to save swagger file").
StringVar(&c.path)
}

48
cli/token/token.go Normal file
View File

@ -0,0 +1,48 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package token
import (
"encoding/json"
"os"
"github.com/bradrydzewski/my-app/cli/util"
"gopkg.in/alecthomas/kingpin.v2"
)
type command struct {
json bool
}
func (c *command) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
token, err := client.Token()
if err != nil {
return err
}
if c.json {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(token)
}
println(token.Value)
return nil
}
// Register the command.
func Register(app *kingpin.Application) {
c := new(command)
cmd := app.Command("token", "generate a personal token").
Action(c.run)
cmd.Flag("json", "json encode the output").
BoolVar(&c.json)
}

63
cli/user/user.go Normal file
View File

@ -0,0 +1,63 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package user
import (
"encoding/json"
"os"
"text/template"
"github.com/bradrydzewski/my-app/cli/util"
"github.com/drone/funcmap"
"gopkg.in/alecthomas/kingpin.v2"
)
const userTmpl = `
email: {{ .Email }}
admin: {{ .Admin }}
`
type command struct {
tmpl string
json bool
}
func (c *command) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
user, err := client.Self()
if err != nil {
return err
}
if c.json {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(user)
}
tmpl, err := template.New("_").Funcs(funcmap.Funcs).Parse(c.tmpl)
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, user)
}
// Register the command.
func Register(app *kingpin.Application) {
c := new(command)
cmd := app.Command("account", "display authenticated user").
Action(c.run)
cmd.Flag("json", "json encode the output").
BoolVar(&c.json)
cmd.Flag("format", "format the output using a Go template").
Default(userTmpl).
Hidden().
StringVar(&c.tmpl)
}

73
cli/users/create.go Normal file
View File

@ -0,0 +1,73 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users
import (
"encoding/json"
"os"
"text/template"
"github.com/bradrydzewski/my-app/cli/util"
"github.com/bradrydzewski/my-app/types"
"github.com/drone/funcmap"
"gopkg.in/alecthomas/kingpin.v2"
)
type createCommand struct {
email string
admin bool
tmpl string
json bool
}
func (c *createCommand) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
in := &types.User{
Admin: c.admin,
Email: c.email,
Password: util.Password(),
}
user, err := client.UserCreate(in)
if err != nil {
return err
}
if c.json {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(user)
}
tmpl, err := template.New("_").Funcs(funcmap.Funcs).Parse(c.tmpl)
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, user)
}
// helper function registers the user create command
func registerCreate(app *kingpin.CmdClause) {
c := new(createCommand)
cmd := app.Command("create", "create a user").
Action(c.run)
cmd.Arg("email", "user email").
Required().
StringVar(&c.email)
cmd.Arg("admin", "user is admin").
BoolVar(&c.admin)
cmd.Flag("json", "json encode the output").
BoolVar(&c.json)
cmd.Flag("format", "format the output using a Go template").
Default(userTmpl).
Hidden().
StringVar(&c.tmpl)
}

35
cli/users/delete.go Normal file
View File

@ -0,0 +1,35 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users
import (
"github.com/bradrydzewski/my-app/cli/util"
"gopkg.in/alecthomas/kingpin.v2"
)
type deleteCommand struct {
email string
}
func (c *deleteCommand) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
return client.UserDelete(c.email)
}
// helper function registers the user delete command
func registerDelete(app *kingpin.CmdClause) {
c := new(deleteCommand)
cmd := app.Command("delete", "delete a user").
Action(c.run)
cmd.Arg("id or email", "user id or email").
Required().
StringVar(&c.email)
}

63
cli/users/find.go Normal file
View File

@ -0,0 +1,63 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users
import (
"encoding/json"
"os"
"text/template"
"github.com/bradrydzewski/my-app/cli/util"
"github.com/drone/funcmap"
"gopkg.in/alecthomas/kingpin.v2"
)
type findCommand struct {
email string
tmpl string
json bool
}
func (c *findCommand) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
user, err := client.User(c.email)
if err != nil {
return err
}
if c.json {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(user)
}
tmpl, err := template.New("_").Funcs(funcmap.Funcs).Parse(c.tmpl + "\n")
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, user)
}
// helper function registers the user find command
func registerFind(app *kingpin.CmdClause) {
c := new(findCommand)
cmd := app.Command("find", "display user details").
Action(c.run)
cmd.Arg("id or email", "user id or email").
Required().
StringVar(&c.email)
cmd.Flag("json", "json encode the output").
BoolVar(&c.json)
cmd.Flag("format", "format the output using a Go template").
Default(userTmpl).
Hidden().
StringVar(&c.tmpl)
}

79
cli/users/list.go Normal file
View File

@ -0,0 +1,79 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users
import (
"encoding/json"
"os"
"text/template"
"github.com/bradrydzewski/my-app/cli/util"
"github.com/bradrydzewski/my-app/types"
"github.com/drone/funcmap"
"gopkg.in/alecthomas/kingpin.v2"
)
const userTmpl = `
id: {{ .ID }}
email: {{ .Email }}
admin: {{ .Admin }}
`
type listCommand struct {
tmpl string
page int
size int
json bool
}
func (c *listCommand) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
list, err := client.UserList(types.Params{
Size: c.size,
Page: c.page,
})
if err != nil {
return err
}
tmpl, err := template.New("_").Funcs(funcmap.Funcs).Parse(c.tmpl + "\n")
if err != nil {
return err
}
if c.json {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(list)
}
for _, item := range list {
tmpl.Execute(os.Stdout, item)
}
return nil
}
// helper function registers the user list command
func registerList(app *kingpin.CmdClause) {
c := new(listCommand)
cmd := app.Command("ls", "display a list of users").
Action(c.run)
cmd.Flag("page", "page number").
IntVar(&c.page)
cmd.Flag("per-page", "page size").
IntVar(&c.size)
cmd.Flag("json", "json encode the output").
BoolVar(&c.json)
cmd.Flag("format", "format the output using a Go template").
Default(userTmpl).
Hidden().
StringVar(&c.tmpl)
}

107
cli/users/update.go Normal file
View File

@ -0,0 +1,107 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users
import (
"encoding/json"
"fmt"
"os"
"text/template"
"github.com/bradrydzewski/my-app/cli/util"
"github.com/bradrydzewski/my-app/types"
"github.com/dchest/uniuri"
"github.com/drone/funcmap"
"github.com/gotidy/ptr"
"gopkg.in/alecthomas/kingpin.v2"
)
type updateCommand struct {
id string
email string
admin bool
demote bool
passgen bool
pass string
tmpl string
json bool
}
func (c *updateCommand) run(*kingpin.ParseContext) error {
client, err := util.Client()
if err != nil {
return err
}
in := new(types.UserInput)
if v := c.email; v != "" {
in.Username = ptr.String(v)
}
if v := c.pass; v != "" {
in.Password = ptr.String(v)
}
if v := c.admin; v {
in.Admin = ptr.Bool(v)
}
if v := c.demote; v {
in.Admin = ptr.Bool(false)
}
if c.passgen {
v := uniuri.NewLen(8)
in.Password = ptr.String(v)
fmt.Printf("generated temporary password: %s\n", v)
}
user, err := client.UserUpdate(c.id, in)
if err != nil {
return err
}
if c.json {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(user)
}
tmpl, err := template.New("_").Funcs(funcmap.Funcs).Parse(c.tmpl)
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, user)
}
// helper function registers the user update command
func registerUpdate(app *kingpin.CmdClause) {
c := new(updateCommand)
cmd := app.Command("update", "update a user").
Action(c.run)
cmd.Arg("id or email", "user id or email").
Required().
StringVar(&c.id)
cmd.Flag("email", "update user email").
StringVar(&c.email)
cmd.Flag("password", "update user password").
StringVar(&c.pass)
cmd.Flag("password-gen", "generate and update user password").
BoolVar(&c.passgen)
cmd.Flag("promote", "promote user to admin").
BoolVar(&c.admin)
cmd.Flag("demote", "demote user from admin").
BoolVar(&c.demote)
cmd.Flag("json", "json encode the output").
BoolVar(&c.json)
cmd.Flag("format", "format the output using a Go template").
Default(userTmpl).
Hidden().
StringVar(&c.tmpl)
}

17
cli/users/users.go Normal file
View File

@ -0,0 +1,17 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users
import "gopkg.in/alecthomas/kingpin.v2"
// Register the command.
func Register(app *kingpin.Application) {
cmd := app.Command("user", "manage users")
registerFind(cmd)
registerList(cmd)
registerCreate(cmd)
registerUpdate(cmd)
registerDelete(cmd)
}

79
cli/util/util.go Normal file
View File

@ -0,0 +1,79 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package util
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/bradrydzewski/my-app/client"
"github.com/bradrydzewski/my-app/types"
"github.com/adrg/xdg"
"golang.org/x/crypto/ssh/terminal"
)
// Client returns a client that is configured from file.
func Client() (*client.HTTPClient, error) {
path, err := Config()
if err != nil {
return nil, err
}
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
token := new(types.Token)
if err := json.Unmarshal(data, token); err != nil {
return nil, err
}
if time.Now().Unix() > token.Expires.Unix() {
return nil, errors.New("token is expired, please login")
}
client := client.NewToken(token.Address, token.Value)
if os.Getenv("DEBUG") == "true" {
client.SetDebug(true)
}
return client, nil
}
// Config returns the configuration file path.
func Config() (string, error) {
return xdg.ConfigFile(
filepath.Join("app", "config.json"),
)
}
// Credentials returns the username and password from stdin.
func Credentials() (string, string) {
return Username(), Password()
}
// Username returns the username from stdin.
func Username() string {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter Username: ")
username, _ := reader.ReadString('\n')
return strings.TrimSpace(username)
}
// Password returns the password from stdin.
func Password() string {
fmt.Print("Enter Password: ")
passwordb, _ := terminal.ReadPassword(int(syscall.Stdin))
password := string(passwordb)
return strings.TrimSpace(password)
}

5
cli/util/util_test.go Normal file
View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package util

336
client/client.go Normal file
View File

@ -0,0 +1,336 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httputil"
"net/url"
"github.com/bradrydzewski/my-app/types"
"github.com/bradrydzewski/my-app/version"
)
// ensure HTTPClient implements Client interface.
var _ Client = (*HTTPClient)(nil)
// HTTPClient provides an HTTP client for interacting
// with the remote API.
type HTTPClient struct {
client *http.Client
base string
token string
debug bool
}
// New returns a client at the specified url.
func New(uri string) *HTTPClient {
return NewToken(uri, "")
}
// NewToken returns a client at the specified url that
// authenticates all outbound requests with the given token.
func NewToken(uri, token string) *HTTPClient {
return &HTTPClient{http.DefaultClient, uri, token, false}
}
// SetClient sets the default http client. This can be
// used in conjunction with golang.org/x/oauth2 to
// authenticate requests to the server.
func (c *HTTPClient) SetClient(client *http.Client) {
c.client = client
}
// SetDebug sets the debug flag. When the debug flag is
// true, the http.Resposne body to stdout which can be
// helpful when debugging.
func (c *HTTPClient) SetDebug(debug bool) {
c.debug = debug
}
// Login authenticates the user and returns a JWT token.
func (c *HTTPClient) Login(username, password string) (*types.Token, error) {
form := &url.Values{}
form.Add("username", username)
form.Add("password", password)
out := new(types.UserToken)
uri := fmt.Sprintf("%s/api/v1/login?return_user=true", c.base)
err := c.post(uri, form, out)
return out.Token, err
}
// Register registers a new user and returns a JWT token.
func (c *HTTPClient) Register(username, password string) (*types.Token, error) {
form := &url.Values{}
form.Add("username", username)
form.Add("password", password)
out := new(types.UserToken)
uri := fmt.Sprintf("%s/api/v1/register?return_user=true", c.base)
err := c.post(uri, form, out)
return out.Token, err
}
//
// User Endpoints
//
// Self returns the currently authenticated user.
func (c *HTTPClient) Self() (*types.User, error) {
out := new(types.User)
uri := fmt.Sprintf("%s/api/v1/user", c.base)
err := c.get(uri, out)
return out, err
}
// Token returns an oauth2 bearer token for the currently
// authenticated user.
func (c *HTTPClient) Token() (*types.Token, error) {
out := new(types.Token)
uri := fmt.Sprintf("%s/api/v1/user/token", c.base)
err := c.post(uri, nil, out)
return out, err
}
// User returns a user by ID or email.
func (c *HTTPClient) User(key string) (*types.User, error) {
out := new(types.User)
uri := fmt.Sprintf("%s/api/v1/users/%s", c.base, key)
err := c.get(uri, out)
return out, err
}
// UserList returns a list of all registered users.
func (c *HTTPClient) UserList(params types.Params) ([]*types.User, error) {
out := []*types.User{}
uri := fmt.Sprintf("%s/api/v1/users?page=%d&per_page=%d", c.base, params.Page, params.Size)
err := c.get(uri, &out)
return out, err
}
// UserCreate creates a new user account.
func (c *HTTPClient) UserCreate(user *types.User) (*types.User, error) {
out := new(types.User)
uri := fmt.Sprintf("%s/api/v1/users", c.base)
err := c.post(uri, user, out)
return out, err
}
// UserUpdate updates a user account by ID or email.
func (c *HTTPClient) UserUpdate(key string, user *types.UserInput) (*types.User, error) {
out := new(types.User)
uri := fmt.Sprintf("%s/api/v1/users/%s", c.base, key)
err := c.patch(uri, user, out)
return out, err
}
// UserDelete deletes a user account by ID or email.
func (c *HTTPClient) UserDelete(key string) error {
uri := fmt.Sprintf("%s/api/v1/users/%s", c.base, key)
err := c.delete(uri)
return err
}
//
// Pipeline endpoints
//
//
// Pipeline endpoints
//
// Pipeline returns a pipeline by slug.
func (c *HTTPClient) Pipeline(slug string) (*types.Pipeline, error) {
out := new(types.Pipeline)
uri := fmt.Sprintf("%s/api/v1/pipelines/%s", c.base, slug)
err := c.get(uri, out)
return out, err
}
// PipelineList returns a list of all pipelines.
func (c *HTTPClient) PipelineList(params types.Params) ([]*types.Pipeline, error) {
out := []*types.Pipeline{}
uri := fmt.Sprintf("%s/api/v1/pipelines?page=%dper_page=%d", c.base, params.Page, params.Size)
err := c.get(uri, &out)
return out, err
}
// PipelineCreate creates a new pipeline.
func (c *HTTPClient) PipelineCreate(pipeline *types.Pipeline) (*types.Pipeline, error) {
out := new(types.Pipeline)
uri := fmt.Sprintf("%s/api/v1/pipelines", c.base)
err := c.post(uri, pipeline, out)
return out, err
}
// PipelineUpdate updates a pipeline.
func (c *HTTPClient) PipelineUpdate(key string, user *types.PipelineInput) (*types.Pipeline, error) {
out := new(types.Pipeline)
uri := fmt.Sprintf("%s/api/v1/pipelines/%s", c.base, key)
err := c.patch(uri, user, out)
return out, err
}
// PipelineDelete deletes a pipeline.
func (c *HTTPClient) PipelineDelete(key string) error {
uri := fmt.Sprintf("%s/api/v1/pipelines/%s", c.base, key)
err := c.delete(uri)
return err
}
//
// Execution endpoints
//
// Execution returns a execution by ID.
func (c *HTTPClient) Execution(pipeline, slug string) (*types.Execution, error) {
out := new(types.Execution)
uri := fmt.Sprintf("%s/api/v1/pipelines/%s/executions/%s", c.base, pipeline, slug)
err := c.get(uri, out)
return out, err
}
// ExecutionList returns a list of all executions by pipeline id.
func (c *HTTPClient) ExecutionList(pipeline string, params types.Params) ([]*types.Execution, error) {
out := []*types.Execution{}
uri := fmt.Sprintf("%s/api/v1/pipelines/%s/executions?page=%dper_page=%d", c.base, pipeline, params.Page, params.Size)
err := c.get(uri, &out)
return out, err
}
// ExecutionCreate creates a new execution.
func (c *HTTPClient) ExecutionCreate(pipeline string, execution *types.Execution) (*types.Execution, error) {
out := new(types.Execution)
uri := fmt.Sprintf("%s/api/v1/pipelines/%s/executions", c.base, pipeline)
err := c.post(uri, execution, out)
return out, err
}
// ExecutionUpdate updates a execution.
func (c *HTTPClient) ExecutionUpdate(pipeline, slug string, execution *types.ExecutionInput) (*types.Execution, error) {
out := new(types.Execution)
uri := fmt.Sprintf("%s/api/v1/pipelines/%s/executions/%s", c.base, pipeline, slug)
err := c.patch(uri, execution, out)
return out, err
}
// ExecutionDelete deletes a execution.
func (c *HTTPClient) ExecutionDelete(pipeline, slug string) error {
uri := fmt.Sprintf("%s/api/v1/pipelines/%s/executions/%s", c.base, pipeline, slug)
err := c.delete(uri)
return err
}
//
// http request helper functions
//
// helper function for making an http GET request.
func (c *HTTPClient) get(rawurl string, out interface{}) error {
return c.do(rawurl, "GET", nil, out)
}
// helper function for making an http POST request.
func (c *HTTPClient) post(rawurl string, in, out interface{}) error {
return c.do(rawurl, "POST", in, out)
}
// helper function for making an http PUT request.
func (c *HTTPClient) put(rawurl string, in, out interface{}) error {
return c.do(rawurl, "PUT", in, out)
}
// helper function for making an http PATCH request.
func (c *HTTPClient) patch(rawurl string, in, out interface{}) error {
return c.do(rawurl, "PATCH", in, out)
}
// helper function for making an http DELETE request.
func (c *HTTPClient) delete(rawurl string) error {
return c.do(rawurl, "DELETE", nil, nil)
}
// helper function to make an http request
func (c *HTTPClient) do(rawurl, method string, in, out interface{}) error {
// executes the http request and returns the body as
// and io.ReadCloser
body, err := c.stream(rawurl, method, in, out)
if body != nil {
defer body.Close()
}
if err != nil {
return err
}
// if a json response is expected, parse and return
// the json response.
if out != nil {
return json.NewDecoder(body).Decode(out)
}
return nil
}
// helper function to stream an http request
func (c *HTTPClient) stream(rawurl, method string, in, out interface{}) (io.ReadCloser, error) {
uri, err := url.Parse(rawurl)
if err != nil {
return nil, err
}
// if we are posting or putting data, we need to
// write it to the body of the request.
var buf io.ReadWriter
if in != nil {
buf = new(bytes.Buffer)
// if posting form data, encode the form values.
if form, ok := in.(*url.Values); ok {
io.WriteString(buf, form.Encode())
} else {
if err := json.NewEncoder(buf).Encode(in); err != nil {
return nil, err
}
}
}
// creates a new http request.
req, err := http.NewRequest(method, uri.String(), buf)
if err != nil {
return nil, err
}
if in != nil {
req.Header.Set("Content-Type", "application/json")
}
if c.token != "" {
req.Header.Set("Authorization", "Bearer "+c.token)
}
if _, ok := in.(*url.Values); ok {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
// include the client version information in the
// http accept header for debugging purposes.
req.Header.Set("Accept", "application/json;version="+version.Version.String())
// send the http request.
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
if c.debug {
dump, _ := httputil.DumpResponse(resp, true)
fmt.Println(method, rawurl)
fmt.Println(string(dump))
}
if resp.StatusCode > 299 {
defer resp.Body.Close()
err := new(remoteError)
json.NewDecoder(resp.Body).Decode(err)
return nil, err
}
return resp.Body, nil
}

79
client/interface.go Normal file
View File

@ -0,0 +1,79 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package client
import "github.com/bradrydzewski/my-app/types"
// Client to access the remote APIs.
type Client interface {
// Login authenticates the user and returns a JWT token.
Login(username, password string) (*types.Token, error)
// Register registers a new user and returns a JWT token.
Register(username, password string) (*types.Token, error)
// Self returns the currently authenticated user.
Self() (*types.User, error)
// Token returns an oauth2 bearer token for the currently
// authenticated user.
Token() (*types.Token, error)
// User returns a user by ID or email.
User(key string) (*types.User, error)
// UserList returns a list of all registered users.
UserList(params types.Params) ([]*types.User, error)
// UserCreate creates a new user account.
UserCreate(user *types.User) (*types.User, error)
// UserUpdate updates a user account by ID or email.
UserUpdate(key string, input *types.UserInput) (*types.User, error)
// UserDelete deletes a user account by ID or email.
UserDelete(key string) error
// Pipeline returns a pipeline by slug.
Pipeline(slug string) (*types.Pipeline, error)
// PipelineList returns a list of all pipelines.
PipelineList(params types.Params) ([]*types.Pipeline, error)
// PipelineCreate creates a new pipeline.
PipelineCreate(user *types.Pipeline) (*types.Pipeline, error)
// PipelineUpdate updates a pipeline.
PipelineUpdate(slug string, input *types.PipelineInput) (*types.Pipeline, error)
// PipelineDelete deletes a pipeline.
PipelineDelete(slug string) error
// Execution returns a execution by pipeline and slug.
Execution(pipeline, slug string) (*types.Execution, error)
// ExecutionList returns a list of all executions by pipeline slug.
ExecutionList(pipeline string, params types.Params) ([]*types.Execution, error)
// ExecutionCreate creates a new execution.
ExecutionCreate(pipeline string, execution *types.Execution) (*types.Execution, error)
// ExecutionUpdate updates a execution.
ExecutionUpdate(pipeline, slug string, input *types.ExecutionInput) (*types.Execution, error)
// ExecutionDelete deletes a execution.
ExecutionDelete(pipeline, slug string) error
}
// remoteError store the error payload returned
// fro the remote API.
type remoteError struct {
Message string `json:"message"`
}
// Error returns the error message.
func (e *remoteError) Error() string {
return e.Message
}

1
contrib/README.md Normal file
View File

@ -0,0 +1 @@
The contrib directory contains scripts, images, and other helpful things which are not part of the core docker distribution. Please note that they could be out of date, since they do not receive the same attention as the rest of the repository.

View File

@ -0,0 +1,58 @@
apiVersion: v1
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: my-app
name: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- image: gcr.io/XXXXX-XXXXXXX/my-app
imagePullPolicy: Never
name: my-app
- apiVersion: v1
kind: Service
metadata:
labels:
app: my-app
name: my-app
spec:
ports:
- port: 3000
protocol: TCP
targetPort: 3000
selector:
app: my-app
- apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: my-app
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- http:
paths:
- path: /pm/(.*)
backend:
serviceName: my-app
servicePort: 3000
kind: List
metadata:
resourceVersion: ""
selfLink: ""

12
docker/Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM alpine:3 as alpine
RUN apk add -U --no-cache ca-certificates
FROM us.gcr.io/platform-205701/busybox:safe
EXPOSE 80
EXPOSE 443
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENV GODEBUG netdns=go
ADD release/linux/amd64/my-app /bin/
ENTRYPOINT ["/bin/my-app", "server"]

12
docker/Dockerfile.alpine Normal file
View File

@ -0,0 +1,12 @@
FROM alpine:3 as alpine
RUN apk add -U --no-cache ca-certificates
FROM alpine:3
EXPOSE 80
EXPOSE 443
COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENV GODEBUG netdns=go
ADD release/linux/amd64/my-app /bin/
ENTRYPOINT ["/bin/my-app", "server"]

51
go.mod Normal file
View File

@ -0,0 +1,51 @@
module github.com/bradrydzewski/my-app
go 1.17
require (
github.com/Masterminds/squirrel v1.5.1
github.com/adrg/xdg v0.3.2
github.com/coreos/go-semver v0.3.0
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/drone/funcmap v0.0.0-20190918184546-d4ef6e88376d
github.com/go-chi/chi v1.5.4
github.com/go-chi/cors v1.2.0
github.com/golang/mock v1.5.0
github.com/google/go-cmp v0.5.5
github.com/google/wire v0.5.0
github.com/gosimple/slug v1.11.2
github.com/gotidy/ptr v1.3.0
github.com/jmoiron/sqlx v1.3.1
github.com/joho/godotenv v1.3.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/lib/pq v1.10.0
github.com/maragudk/migrate v0.4.1
github.com/mattn/go-isatty v0.0.12
github.com/mattn/go-sqlite3 v1.14.10-0.20211026011849-85436841b33e
github.com/rs/zerolog v1.26.0
github.com/swaggest/openapi-go v0.2.13
github.com/swaggest/swgui v1.4.2
github.com/unrolled/secure v1.0.8
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
gopkg.in/alecthomas/kingpin.v2 v2.2.6
)
require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/rs/xid v1.3.0 // indirect
github.com/swaggest/jsonschema-go v0.3.24 // indirect
github.com/swaggest/refl v1.0.1 // indirect
github.com/vearutop/statigz v1.1.5 // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

632
go.sum Normal file
View File

@ -0,0 +1,632 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/squirrel v1.5.1 h1:kWAKlLLJFxZG7N2E0mBMNWVp5AuUX+JUrnhFN74Eg+w=
github.com/Masterminds/squirrel v1.5.1/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/adrg/xdg v0.3.2 h1:GUSGQ5pHdev83AYhDSS1A/CX+0JIsxbiWtow2DSA+RU=
github.com/adrg/xdg v0.3.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4=
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM=
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bool64/dev v0.1.17/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
github.com/bool64/dev v0.1.25/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
github.com/bool64/dev v0.1.41/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
github.com/bool64/dev v0.1.42 h1:Ps0IvNNf/v1MlIXt8Q5YKcKjYsIVLY/fb/5BmA7gepg=
github.com/bool64/dev v0.1.42/go.mod h1:cTHiTDNc8EewrQPy3p1obNilpMpdmlUesDkFTF2zRWU=
github.com/bool64/shared v0.1.3 h1:gj7XZPYa1flQsCg3q9AIju+W2A1jaexK0fdFu2XtaG0=
github.com/bool64/shared v0.1.3/go.mod h1:RF1p1Oi29ofgOvinBpetbF5mceOUP3kpMkvLbWOmtm0=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs=
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/drone/funcmap v0.0.0-20190918184546-d4ef6e88376d h1:/IO7UVVu191Jc0DajV4cDVoO+91cuppvgxg2MZl+AXI=
github.com/drone/funcmap v0.0.0-20190918184546-d4ef6e88376d/go.mod h1:Hph0/pT6ZxbujnE1Z6/08p5I0XXuOsppqF6NQlGOK0E=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs=
github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg=
github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE=
github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
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=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gosimple/slug v1.11.2 h1:MxFR0TmQ/qz0KvIrBbf4phu+G0RBgpwxOn6jPKFKFOw=
github.com/gosimple/slug v1.11.2/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
github.com/gotidy/ptr v1.3.0 h1:5wdrH1G8X4txy6fbWWRznr7k974wMWtePWP3p6s1API=
github.com/gotidy/ptr v1.3.0/go.mod h1:vpltyHhOZE+NGXUiwpVl3wV9AGEBlxhdnaimPDxRLxg=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA=
github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.8.1 h1:ySBX7Q87vOMqKU2bbmKbUvtYhauDFclYbNDYIE1/h6s=
github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.0 h1:h2yg3kjIyAGSZKDijYn1/gXHlYLCwl9ZjEh2PU0yVxE=
github.com/jackc/pgproto3/v2 v2.1.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
github.com/jackc/pgtype v1.7.0 h1:6f4kVsW01QftE38ufBYxKciO6gyioXSC0ABIRLcZrGs=
github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
github.com/jackc/pgx/v4 v4.11.0 h1:J86tSWd3Y7nKjwT/43xZBvpi04keQWx8gNC2YkdJhZI=
github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE=
github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/maragudk/migrate v0.4.1 h1:oAY8bCyaHIreLj3ar9b6cf7PSqOZsCkKXHU8Yn1bkb4=
github.com/maragudk/migrate v0.4.1/go.mod h1:vhmL4s+Xz75KU6DPZWRfqb45YyqjYQfcXliA1DsYzvY=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.10-0.20211026011849-85436841b33e h1:aF8T4W7RUsJjsbSVHyxjqt7aWrGpkCN8m6m8D38SU8Q=
github.com/mattn/go-sqlite3 v1.14.10-0.20211026011849-85436841b33e/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
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/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.15.2/go.mod h1:Dd6YFfwBW84ETqqtL0CPyPXillHgY6XhQH3uuCCTr/o=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE=
github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/swaggest/assertjson v1.6.8 h1:1O/9UI5M+2OJI7BeEWKGj0wTvpRXZt5FkOJ4nRkY4rA=
github.com/swaggest/assertjson v1.6.8/go.mod h1:Euf0upn9Vlaf1/llYHTs+Kx5K3vVbpMbsZhth7zlN7M=
github.com/swaggest/jsonschema-go v0.3.24 h1:vVtFhWQWT9sgsvgMdh0n3O1F0+TnhOWz4+lbz5p5ChY=
github.com/swaggest/jsonschema-go v0.3.24/go.mod h1:B2ZSqrrlkj21zhywlkh8VnKBmuqUwDv3dLQoPjgHk7M=
github.com/swaggest/openapi-go v0.2.13 h1:2u3Im5S6BX6GFqux2I035FSXWn6jgWsXY8SNhc0jHE4=
github.com/swaggest/openapi-go v0.2.13/go.mod h1:Y3+sBULNSPwoUbExcY+0AwjWX0oxFspaBGha7tx/DWk=
github.com/swaggest/refl v1.0.0/go.mod h1:acYd5x8NNxivp+ZHdRZKJYz66n/qjo3Q9Sa/jAivljQ=
github.com/swaggest/refl v1.0.1 h1:YQHb7Ic6EMpdUpxQmTWmf/O4IWN6iIErxJNWA7LwyyM=
github.com/swaggest/refl v1.0.1/go.mod h1:dnx+n9YaI0o+FH+OR2tJZWLABBVIPs9qc4VY9UdrhLE=
github.com/swaggest/swgui v1.4.2 h1:6AT8ICO0+t6WpbIFsACf5vBmviVX0sqspNbZLoe6vgw=
github.com/swaggest/swgui v1.4.2/go.mod h1:xWDsT2h8obEoGHzX/a6FRClUOS8NvkICyInhi7s3fN8=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/unrolled/secure v1.0.8 h1:JaMvKbe4CRt8oyxVXn+xY+6jlqd7pyJNSVkmsBxxQsM=
github.com/unrolled/secure v1.0.8/go.mod h1:fO+mEan+FLB0CdEnHf6Q4ZZVNqG+5fuLFnP8p0BXDPI=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/vearutop/statigz v1.1.5 h1:qWvRgXFsseWVTFCkIvwHQPpaLNf9WI0+dDJE7I9432o=
github.com/vearutop/statigz v1.1.5/go.mod h1:czAv7iXgPv/s+xsgXpVEhhD0NSOQ4wZPgmM/n7LANDI=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yosuke-furukawa/json5 v0.1.2-0.20201207051438-cf7bb3f354ff/go.mod h1:sw49aWDqNdRJ6DYUtIQiaA3xyj2IL9tjeNYmX2ixwcU=
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
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-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211105192438-b53810dc28af/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

5
internal/api/api.go Normal file
View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package api

View File

@ -0,0 +1,75 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package account
import (
"net/http"
"time"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/bradrydzewski/my-app/internal/token"
"github.com/bradrydzewski/my-app/types"
"github.com/rs/zerolog/hlog"
"golang.org/x/crypto/bcrypt"
)
// HandleLogin returns an http.HandlerFunc that authenticates
// the user and returns an authentication token on success.
func HandleLogin(users store.UserStore, system store.SystemStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := hlog.FromRequest(r)
username := r.FormValue("username")
password := r.FormValue("password")
user, err := users.FindEmail(ctx, username)
if err != nil {
render.NotFoundf(w, "Invalid email or password")
log.Debug().Err(err).
Str("user", username).
Msg("cannot find user")
return
}
err = bcrypt.CompareHashAndPassword(
[]byte(user.Password),
[]byte(password),
)
if err != nil {
render.NotFoundf(w, "Invalid email or password")
log.Debug().Err(err).
Str("user", username).
Msg("invalid password")
return
}
expires := time.Now().Add(system.Config(ctx).Token.Expire)
token_, err := token.GenerateExp(user, expires.Unix(), user.Salt)
if err != nil {
render.InternalErrorf(w, "Failed to create session")
log.Debug().Err(err).
Str("user", username).
Msg("failed to generate token")
return
}
// return the token if the with_user boolean
// query parameter is set to true.
if r.FormValue("return_user") == "true" {
render.JSON(w, &types.UserToken{
User: user,
Token: &types.Token{
Value: token_,
Expires: expires.UTC(),
},
}, 200)
} else {
// else return the token only.
render.JSON(w, &types.Token{Value: token_}, 200)
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package account
import "testing"
func TestLogin(t *testing.T) {
t.Skip()
}
func TestLogin_NotFound(t *testing.T) {
t.Skip()
}
func TestLogin_BcryptError(t *testing.T) {
t.Skip()
}
func TestLogin_TokenError(t *testing.T) {
t.Skip()
}

View File

@ -0,0 +1,103 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package account
import (
"net/http"
"time"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/bradrydzewski/my-app/internal/token"
"github.com/bradrydzewski/my-app/types"
"github.com/bradrydzewski/my-app/types/check"
"github.com/dchest/uniuri"
"github.com/rs/zerolog/hlog"
"golang.org/x/crypto/bcrypt"
)
// HandleRegister returns an http.HandlerFunc that processes an http.Request
// to register the named user account with the system.
func HandleRegister(users store.UserStore, system store.SystemStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := hlog.FromRequest(r)
username := r.FormValue("username")
password := r.FormValue("password")
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
render.InternalError(w, err)
log.Debug().Err(err).
Str("email", username).
Msg("cannot hash password")
return
}
user := &types.User{
Name: username,
Email: username,
Password: string(hash),
Salt: uniuri.NewLen(uniuri.UUIDLen),
Created: time.Now().UnixMilli(),
Updated: time.Now().UnixMilli(),
}
if ok, err := check.User(user); !ok {
render.BadRequest(w, err)
log.Debug().Err(err).
Str("email", username).
Msg("invalid user input")
return
}
if err := users.Create(ctx, user); err != nil {
render.InternalError(w, err)
log.Error().Err(err).
Str("email", username).
Msg("cannot create user")
return
}
// if the registered user is the first user of the system,
// assume they are the system administrator and grant the
// user system admin access.
if user.ID == 1 {
user.Admin = true
if err := users.Update(ctx, user); err != nil {
log.Error().Err(err).
Str("email", username).
Msg("cannot enable admin user")
}
}
expires := time.Now().Add(system.Config(ctx).Token.Expire)
token_, err := token.GenerateExp(user, expires.Unix(), user.Salt)
if err != nil {
render.InternalErrorf(w, "Failed to create session")
log.Error().Err(err).
Str("email", username).
Msg("failed to generate token")
return
}
// return the token if the with_user boolean
// query parameter is set to true.
if r.FormValue("return_user") == "true" {
render.JSON(w, &types.UserToken{
User: user,
Token: &types.Token{
Value: token_,
Expires: expires.UTC(),
},
}, 200)
} else {
// else return the token only.
render.JSON(w, &types.Token{Value: token_}, 200)
}
}
}

View File

@ -0,0 +1,27 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package account
import "testing"
func TestRegiser(t *testing.T) {
t.Skip()
}
func TestRegiserAdmin(t *testing.T) {
t.Skip()
}
func TestRegiser_CreateError(t *testing.T) {
t.Skip()
}
func TestRegiser_BcryptError(t *testing.T) {
t.Skip()
}
func TestRegiser_TokenError(t *testing.T) {
t.Skip()
}

View File

@ -0,0 +1,96 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package executions
import (
"encoding/json"
"net/http"
"time"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/bradrydzewski/my-app/types"
"github.com/bradrydzewski/my-app/types/check"
"github.com/go-chi/chi"
"github.com/gosimple/slug"
"github.com/gotidy/ptr"
"github.com/rs/zerolog/hlog"
)
// HandleCreate returns an http.HandlerFunc that creates
// the object and persists to the datastore.
func HandleCreate(pipelines store.PipelineStore, executions store.ExecutionStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
log = hlog.FromRequest(r)
param = chi.URLParam(r, "pipeline")
)
pipeline, err := pipelines.FindSlug(ctx, param)
if err != nil {
render.NotFound(w, err)
log.Debug().Err(err).
Str("pipeline_slug", param).
Msg("pipeline not found")
return
}
sublog := log.With().
Int64("pipeline_id", pipeline.ID).
Str("pipeline_slug", pipeline.Slug).
Logger()
in := new(types.ExecutionInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequest(w, err)
sublog.Debug().Err(err).
Msg("cannot unmarshal json request")
return
}
execution := &types.Execution{
Pipeline: pipeline.ID,
Slug: ptr.ToString(in.Slug),
Name: ptr.ToString(in.Name),
Desc: ptr.ToString(in.Desc),
Created: time.Now().UnixMilli(),
Updated: time.Now().UnixMilli(),
}
// if the slug is empty we can derrive
// the slug from the name.
if execution.Slug == "" {
execution.Slug = slug.Make(execution.Name)
}
// if the name is empty we can derrive
// the name from the slug.
if execution.Name == "" {
execution.Name = execution.Slug
}
if ok, err := check.Execution(execution); !ok {
render.BadRequest(w, err)
sublog.Debug().Err(err).
Int64("execution_id", execution.ID).
Str("execution_slug", execution.Slug).
Msg("cannot validate execution")
return
}
err = executions.Create(ctx, execution)
if err != nil {
render.InternalError(w, err)
sublog.Error().Err(err).
Int64("execution_id", execution.ID).
Str("execution_slug", execution.Slug).
Msg("cannot create execution")
} else {
render.JSON(w, execution, 200)
}
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package executions

View File

@ -0,0 +1,61 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package executions
import (
"net/http"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/go-chi/chi"
"github.com/rs/zerolog/hlog"
)
// HandleDelete returns an http.HandlerFunc that deletes
// the object from the datastore.
func HandleDelete(pipelines store.PipelineStore, executions store.ExecutionStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
log = hlog.FromRequest(r)
pipelineParam = chi.URLParam(r, "pipeline")
executionParam = chi.URLParam(r, "execution")
)
pipeline, err := pipelines.FindSlug(ctx, pipelineParam)
if err != nil {
render.NotFound(w, err)
log.Debug().Err(err).
Str("pipeline_slug", pipelineParam).
Msg("pipeline not found")
return
}
execution, err := executions.FindSlug(ctx, pipeline.ID, executionParam)
if err != nil {
render.NotFound(w, err)
log.Debug().Err(err).
Int64("pipeline_id", pipeline.ID).
Str("pipeline_slug", pipeline.Slug).
Str("execution_slug", executionParam).
Msg("execution not found")
return
}
err = executions.Delete(ctx, execution)
if err != nil {
render.InternalError(w, err)
log.Error().Err(err).
Int64("pipeline_id", pipeline.ID).
Str("pipeline_slug", pipeline.Slug).
Int64("execution_id", execution.ID).
Str("execution_slug", execution.Slug).
Msg("cannot delete execution")
} else {
w.WriteHeader(http.StatusNoContent)
}
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package executions

View File

@ -0,0 +1,50 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package executions
import (
"net/http"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/go-chi/chi"
"github.com/rs/zerolog/hlog"
)
// HandleFind returns an http.HandlerFunc that writes the
// json-encoded execution details to the response body.
func HandleFind(pipelines store.PipelineStore, executions store.ExecutionStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
log = hlog.FromRequest(r)
pipelineParam = chi.URLParam(r, "pipeline")
executionParam = chi.URLParam(r, "execution")
)
pipeline, err := pipelines.FindSlug(ctx, pipelineParam)
if err != nil {
render.NotFound(w, err)
log.Debug().Err(err).
Str("pipeline_slug", pipelineParam).
Msg("pipeline not found")
return
}
execution, err := executions.FindSlug(ctx, pipeline.ID, executionParam)
if err != nil {
render.NotFound(w, err)
log.Debug().Err(err).
Int64("pipeline_id", pipeline.ID).
Str("pipeline_slug", pipeline.Slug).
Str("execution_slug", executionParam).
Msg("execution not found")
return
}
render.JSON(w, execution, 200)
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package executions

View File

@ -0,0 +1,55 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package executions
import (
"net/http"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/api/request"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/bradrydzewski/my-app/types"
"github.com/go-chi/chi"
"github.com/rs/zerolog/hlog"
)
// HandleList returns an http.HandlerFunc that writes a json-encoded
// list of objects to the response body.
func HandleList(pipelines store.PipelineStore, executions store.ExecutionStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
log = hlog.FromRequest(r)
slug = chi.URLParam(r, "pipeline")
page = request.ParsePage(r)
size = request.ParseSize(r)
)
pipeline, err := pipelines.FindSlug(ctx, slug)
if err != nil {
render.NotFound(w, err)
log.Debug().Err(err).
Str("pipeline_slug", slug).
Msg("pipeline not found")
return
}
executions, err := executions.List(ctx, pipeline.ID, types.Params{
Size: size,
Page: page,
})
if err != nil {
render.InternalError(w, err)
log.Error().Err(err).
Int64("pipeline_id", pipeline.ID).
Str("pipeline_slug", pipeline.Slug).
Msg("cannot retrieve list")
} else {
render.Pagination(r, w, page, size, 0)
render.JSON(w, executions, 200)
}
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package executions

View File

@ -0,0 +1,95 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package executions
import (
"encoding/json"
"net/http"
"time"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/bradrydzewski/my-app/types"
"github.com/bradrydzewski/my-app/types/check"
"github.com/go-chi/chi"
"github.com/gotidy/ptr"
"github.com/rs/zerolog/hlog"
)
// HandleUpdate returns an http.HandlerFunc that processes http
// requests to update the object details.
func HandleUpdate(pipelines store.PipelineStore, executions store.ExecutionStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
log = hlog.FromRequest(r)
pipelineParam = chi.URLParam(r, "pipeline")
executionParam = chi.URLParam(r, "execution")
)
pipeline, err := pipelines.FindSlug(ctx, pipelineParam)
if err != nil {
render.NotFound(w, err)
log.Debug().Err(err).
Str("pipeline_slug", pipelineParam).
Msg("pipeline not found")
return
}
sublog := log.With().
Int64("pipeline_id", pipeline.ID).
Str("pipeline_slug", pipeline.Slug).
Logger()
execution, err := executions.FindSlug(ctx, pipeline.ID, executionParam)
if err != nil {
render.NotFound(w, err)
sublog.Debug().Err(err).
Str("execution_slug", executionParam).
Msg("execution not found")
return
}
sublog = sublog.With().
Str("execution_slug", execution.Slug).
Int64("execution_id", execution.ID).
Logger()
in := new(types.ExecutionInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequest(w, err)
sublog.Debug().Err(err).
Msg("cannot unmarshal json request")
return
}
if in.Name != nil {
execution.Name = ptr.ToString(in.Name)
}
if in.Desc != nil {
execution.Desc = ptr.ToString(in.Desc)
}
if ok, err := check.Execution(execution); !ok {
render.BadRequest(w, err)
sublog.Debug().Err(err).
Msg("cannot validate execution")
return
}
execution.Updated = time.Now().UnixMilli()
err = executions.Update(ctx, execution)
if err != nil {
render.InternalError(w, err)
sublog.Error().Err(err).
Msg("cannot update execution")
} else {
render.JSON(w, execution, 200)
}
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package executions

View File

@ -0,0 +1,80 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipelines
import (
"encoding/json"
"net/http"
"time"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/bradrydzewski/my-app/types"
"github.com/bradrydzewski/my-app/types/check"
"github.com/dchest/uniuri"
"github.com/gosimple/slug"
"github.com/gotidy/ptr"
"github.com/rs/zerolog/hlog"
)
// HandleCreate returns an http.HandlerFunc that creates
// a new pipeline.
func HandleCreate(pipelines store.PipelineStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := hlog.FromRequest(r)
in := new(types.PipelineInput)
err := json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequest(w, err)
log.Debug().Err(err).
Msg("cannot unmarshal json request")
return
}
pipeline := &types.Pipeline{
Slug: ptr.ToString(in.Slug),
Name: ptr.ToString(in.Name),
Desc: ptr.ToString(in.Desc),
Token: uniuri.NewLen(uniuri.UUIDLen),
Created: time.Now().UnixMilli(),
Updated: time.Now().UnixMilli(),
}
// if the slug is empty we can derrive
// the slug from the pipeline name.
if pipeline.Slug == "" {
pipeline.Slug = slug.Make(pipeline.Name)
}
// if the name is empty we can derrive
// the name from the pipeline slug.
if pipeline.Name == "" {
pipeline.Name = pipeline.Slug
}
if ok, err := check.Pipeline(pipeline); !ok {
render.BadRequest(w, err)
log.Debug().Err(err).
Str("pipeline_slug", pipeline.Slug).
Msg("cannot create pipeline")
return
}
err = pipelines.Create(ctx, pipeline)
if err != nil {
render.InternalError(w, err)
log.Error().Err(err).
Str("pipeline_name", pipeline.Name).
Str("pipeline_slug", pipeline.Slug).
Msg("cannot create pipeline")
return
}
render.JSON(w, pipeline, 200)
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipelines

View File

@ -0,0 +1,46 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipelines
import (
"net/http"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/go-chi/chi"
"github.com/rs/zerolog/hlog"
)
// HandleDelete returns an http.HandlerFunc that deletes
// the object from the datastore.
func HandleDelete(pipelines store.PipelineStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id := chi.URLParam(r, "pipeline")
pipeline, err := pipelines.FindSlug(ctx, id)
if err != nil {
render.NotFound(w, err)
hlog.FromRequest(r).
Debug().Err(err).
Str("pipeline_slug", id).
Msg("pipeline not found")
return
}
err = pipelines.Delete(ctx, pipeline)
if err != nil {
render.InternalError(w, err)
hlog.FromRequest(r).
Error().Err(err).
Int64("pipeline_id", pipeline.ID).
Str("pipeline_slug", pipeline.Slug).
Msg("cannot delete pipeline")
} else {
w.WriteHeader(http.StatusNoContent)
}
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipelines

View File

@ -0,0 +1,51 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipelines
import (
"net/http"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/bradrydzewski/my-app/types"
"github.com/rs/zerolog/hlog"
"github.com/go-chi/chi"
)
type pipelineToken struct {
*types.Pipeline
Token string `json:"token"`
}
// HandleFind returns an http.HandlerFunc that writes the
// json-encoded pipeline details to the response body.
func HandleFind(pipelines store.PipelineStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id := chi.URLParam(r, "pipeline")
pipeline, err := pipelines.FindSlug(ctx, id)
if err != nil {
render.NotFound(w, err)
hlog.FromRequest(r).
Debug().Err(err).
Str("pipeline_slug", id).
Msg("pipeline not found")
return
}
// if the caller requests the pipeline details without
// the token, return the token object as-is.
if r.FormValue("token") != "true" {
render.JSON(w, pipeline, 200)
return
}
// if the caller requests the pipeline details with
// the token then it can be safely included.
render.JSON(w, &pipelineToken{pipeline, pipeline.Token}, 200)
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipelines

View File

@ -0,0 +1,42 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipelines
import (
"net/http"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/api/request"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/bradrydzewski/my-app/types"
"github.com/rs/zerolog/log"
)
// HandleList returns an http.HandlerFunc that writes a json-encoded
// list of pipelines to the response body.
func HandleList(pipelines store.PipelineStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var (
page = request.ParsePage(r)
size = request.ParseSize(r)
ctx = r.Context()
)
viewer, _ := request.UserFrom(ctx)
list, err := pipelines.List(ctx, viewer.ID, types.Params{
Page: page,
Size: size,
})
if err != nil {
render.InternalError(w, err)
log.Ctx(ctx).Error().
Err(err).Msg("cannot list pipelines")
} else {
render.Pagination(r, w, page, size, 0)
render.JSON(w, list, 200)
}
}
}

View File

@ -0,0 +1,87 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipelines
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/api/request"
"github.com/bradrydzewski/my-app/mocks"
"github.com/bradrydzewski/my-app/types"
"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
)
func TestHandleList(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockUser := &types.User{
ID: 1,
Email: "octocat@github.com",
}
mockList := []*types.Pipeline{
{
Name: "test",
Desc: "desc",
},
}
projs := mocks.NewMockPipelineStore(controller)
projs.EXPECT().List(gomock.Any(), mockUser.ID, gomock.Any()).Return(mockList, nil)
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
r = r.WithContext(
request.WithUser(r.Context(), mockUser),
)
HandleList(projs)(w, r)
if got, want := w.Code, http.StatusOK; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
got, want := []*types.Pipeline{}, mockList
json.NewDecoder(w.Body).Decode(&got)
if diff := cmp.Diff(got, want); len(diff) > 0 {
t.Errorf(diff)
}
}
func TestListErr(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockUser := &types.User{
ID: 1,
Email: "octocat@github.com",
}
projs := mocks.NewMockPipelineStore(controller)
projs.EXPECT().List(gomock.Any(), mockUser.ID, gomock.Any()).Return(nil, render.ErrNotFound)
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
r = r.WithContext(
request.WithUser(r.Context(), mockUser),
)
HandleList(projs)(w, r)
if got, want := w.Code, http.StatusInternalServerError; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
got, want := &render.Error{}, render.ErrNotFound
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) > 0 {
t.Errorf(diff)
}
}

View File

@ -0,0 +1,79 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipelines
import (
"encoding/json"
"net/http"
"time"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/bradrydzewski/my-app/types"
"github.com/bradrydzewski/my-app/types/check"
"github.com/go-chi/chi"
"github.com/gotidy/ptr"
"github.com/rs/zerolog/hlog"
)
// HandleUpdate returns an http.HandlerFunc that processes http
// requests to update the pipeline details.
func HandleUpdate(pipelines store.PipelineStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := hlog.FromRequest(r)
id := chi.URLParam(r, "pipeline")
pipeline, err := pipelines.FindSlug(ctx, id)
if err != nil {
render.NotFound(w, err)
log.Debug().Err(err).
Str("pipeline_slug", id).
Msg("pipeline not found")
return
}
in := new(types.PipelineInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequest(w, err)
log.Debug().Err(err).
Int64("pipeline_id", pipeline.ID).
Str("pipeline_slug", pipeline.Slug).
Msg("cannot unmarshal json request")
return
}
if in.Name != nil {
pipeline.Name = ptr.ToString(in.Name)
}
if in.Desc != nil {
pipeline.Desc = ptr.ToString(in.Desc)
}
if ok, err := check.Pipeline(pipeline); !ok {
render.BadRequest(w, err)
log.Debug().Err(err).
Int64("pipeline_id", pipeline.ID).
Str("pipeline_slug", pipeline.Slug).
Msg("cannot update pipeline")
return
}
pipeline.Updated = time.Now().UnixMilli()
err = pipelines.Update(ctx, pipeline)
if err != nil {
render.InternalError(w, err)
log.Error().Err(err).
Int64("pipeline_id", pipeline.ID).
Str("pipeline_slug", pipeline.Slug).
Msg("cannot update the pipeline")
} else {
render.JSON(w, pipeline, 200)
}
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package pipelines

View File

@ -0,0 +1,32 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package projects
import (
"net/http"
"github.com/bradrydzewski/my-app/internal/api/render/platform"
"github.com/bradrydzewski/my-app/types"
)
// standalone version of the product uses a single,
// hard-coded project as its default.
var defaultProject = &types.Project{
Identifier: "default",
Color: "#0063f7",
Desc: "Default Project",
Name: "Default Project",
Modules: []string{},
Org: "default",
Tags: map[string]string{},
}
// HandleFind returns an http.HandlerFunc that writes json-encoded
// project to the http response body.
func HandleFind() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
platform.RenderResource(w, defaultProject, 200)
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package projects

View File

@ -0,0 +1,32 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package projects
import (
"net/http"
"github.com/bradrydzewski/my-app/internal/api/render/platform"
"github.com/bradrydzewski/my-app/types"
)
// standalone version of the product uses a single,
// hard-coded project as its default.
var defaultProjectList = &types.ProjectList{
Data: []*types.Project{defaultProject},
Empty: false,
PageIndex: 1,
PageItemCount: 1,
PageSize: 1,
TotalItems: 1,
TotalPages: 1,
}
// HandleList returns an http.HandlerFunc that writes json-encoded
// project list to the http response body.
func HandleList() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
platform.RenderResource(w, defaultProjectList, 200)
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package projects

View File

@ -0,0 +1,13 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package system
import "net/http"
// HandleHealth writes a 200 OK status to the http.Response
// if the server is healthy.
func HandleHealth(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}

View File

@ -0,0 +1,11 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package system
import "testing"
func TestHealth(t *testing.T) {
t.Skip()
}

View File

@ -0,0 +1,18 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package system
import (
"fmt"
"net/http"
"github.com/bradrydzewski/my-app/version"
)
// HandleVersion writes the server version number
// to the http.Response body in plain text.
func HandleVersion(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s", version.Version)
}

View File

@ -0,0 +1,11 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package system
import "testing"
func TestVersion(t *testing.T) {
t.Skip()
}

View File

@ -0,0 +1,34 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package user
import (
"net/http"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/api/render/platform"
"github.com/bradrydzewski/my-app/internal/api/request"
)
// HandleFind returns an http.HandlerFunc that writes json-encoded
// account information to the http response body.
func HandleFind() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
viewer, _ := request.UserFrom(ctx)
render.JSON(w, viewer, 200)
}
}
// func returns an http.HandlerFunc that writes json-encoded
// account information to the http response body in platform
// format.
func HandleCurrent() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
viewer, _ := request.UserFrom(ctx)
platform.RenderResource(w, viewer, 200)
}
}

View File

@ -0,0 +1,40 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package user
import (
"encoding/json"
"net/http/httptest"
"testing"
"github.com/bradrydzewski/my-app/internal/api/request"
"github.com/bradrydzewski/my-app/types"
"github.com/google/go-cmp/cmp"
)
func TestFind(t *testing.T) {
mockUser := &types.User{
ID: 1,
Email: "octocat@github.com",
}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/v1/user", nil)
r = r.WithContext(
request.WithUser(r.Context(), mockUser),
)
HandleFind()(w, r)
if got, want := w.Code, 200; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
got, want := &types.User{}, mockUser
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}

View File

@ -0,0 +1,36 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package user
import (
"net/http"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/api/request"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/bradrydzewski/my-app/internal/token"
"github.com/bradrydzewski/my-app/types"
"github.com/rs/zerolog/hlog"
)
// HandleToken returns an http.HandlerFunc that generates and
// writes a json-encoded token to the http.Response body.
func HandleToken(users store.UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
viewer, _ := request.UserFrom(r.Context())
token, err := token.Generate(viewer, viewer.Salt)
if err != nil {
render.InternalErrorf(w, "Failed to generate token")
hlog.FromRequest(r).
Error().Err(err).
Str("user", viewer.Email).
Msg("failed to generate token")
return
}
render.JSON(w, &types.Token{Value: token}, 200)
}
}

View File

@ -0,0 +1,49 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package user
import (
"encoding/json"
"net/http/httptest"
"testing"
"github.com/bradrydzewski/my-app/internal/api/request"
"github.com/bradrydzewski/my-app/types"
"github.com/dgrijalva/jwt-go"
"github.com/golang/mock/gomock"
)
func TestToken(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockUser := &types.User{
ID: 1,
Email: "octocat@github.com",
Salt: "12345",
}
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/", nil)
r = r.WithContext(
request.WithUser(r.Context(), mockUser),
)
HandleToken(nil)(w, r)
if got, want := w.Code, 200; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
result := &types.Token{}
json.NewDecoder(w.Body).Decode(&result)
_, err := jwt.Parse(result.Value, func(token *jwt.Token) (interface{}, error) {
return []byte(mockUser.Salt), nil
})
if err != nil {
t.Error(err)
}
}

View File

@ -0,0 +1,72 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package user
import (
"encoding/json"
"net/http"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/api/request"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/bradrydzewski/my-app/types"
"github.com/rs/zerolog/hlog"
"github.com/gotidy/ptr"
"golang.org/x/crypto/bcrypt"
)
// GenerateFromPassword returns the bcrypt hash of the
// password at the given cost.
var hashPassword = bcrypt.GenerateFromPassword
// HandleUpdate returns an http.HandlerFunc that processes an http.Request
// to update the current user account.
func HandleUpdate(users store.UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := hlog.FromRequest(r)
viewer, _ := request.UserFrom(ctx)
in := new(types.UserInput)
err := json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequest(w, err)
log.Error().Err(err).
Str("email", viewer.Email).
Msg("cannot unmarshal request")
return
}
if in.Password != nil {
hash, err := hashPassword([]byte(ptr.ToString(in.Password)), bcrypt.DefaultCost)
if err != nil {
render.InternalError(w, err)
log.Debug().Err(err).
Msg("cannot hash password")
return
}
viewer.Password = string(hash)
}
if in.Name != nil {
viewer.Name = ptr.ToString(in.Name)
}
if in.Company != nil {
viewer.Company = ptr.ToString(in.Company)
}
err = users.Update(ctx, viewer)
if err != nil {
render.InternalError(w, err)
log.Error().Err(err).
Str("email", viewer.Email).
Msg("cannot update user")
} else {
render.JSON(w, viewer, 200)
}
}
}

View File

@ -0,0 +1,196 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package user
import (
"bytes"
"encoding/json"
"net/http/httptest"
"testing"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/api/request"
"github.com/bradrydzewski/my-app/mocks"
"github.com/bradrydzewski/my-app/types"
"golang.org/x/crypto/bcrypt"
"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
"github.com/gotidy/ptr"
)
// mock bcrypt has function returns an error
// when attepting to has the password.
func bcryptHashErrror(password []byte, cost int) ([]byte, error) {
return nil, bcrypt.ErrHashTooShort
}
// mock bcrypt has function returns a deterministic
// hash value for testing purposes.
func bcryptHashStatic(password []byte, cost int) ([]byte, error) {
return []byte("$2a$10$onMfkmQZtlkOfnZJe7GaiesbPBbXcyB53KyFKllWq829mxlhNoJSi"), nil
}
func TestUpdate(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
hashPassword = bcryptHashStatic
defer func() {
hashPassword = bcrypt.GenerateFromPassword
}()
userInput := &types.UserInput{
Username: ptr.String("octocat@google.com"),
Password: ptr.String("password"),
Company: ptr.String("google"),
}
before := &types.User{
Email: "octocat@google.com",
Password: "acme",
}
users := mocks.NewMockUserStore(controller)
users.EXPECT().Update(gomock.Any(), before)
in := new(bytes.Buffer)
json.NewEncoder(in).Encode(userInput)
w := httptest.NewRecorder()
r := httptest.NewRequest("PATCH", "/api/v1/user", in)
r = r.WithContext(
request.WithUser(r.Context(), before),
)
HandleUpdate(users)(w, r)
if got, want := w.Code, 200; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
if got, want := before.Email, "octocat@google.com"; got != want {
t.Errorf("Want user email %v, got %v", want, got)
}
if got, want := before.Password, "$2a$10$onMfkmQZtlkOfnZJe7GaiesbPBbXcyB53KyFKllWq829mxlhNoJSi"; got != want {
t.Errorf("Want user password %v, got %v", want, got)
}
after := &types.User{
Email: "octocat@google.com",
Company: "google",
// Password hash is not exposecd to JSON
}
got, want := new(types.User), after
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
// the purpose of this unit test is to verify that a
// failure to hash the password will return an internal
// server error.
func TestUpdate_HashError(t *testing.T) {
hashPassword = bcryptHashErrror
defer func() {
hashPassword = bcrypt.GenerateFromPassword
}()
controller := gomock.NewController(t)
defer controller.Finish()
userInput := &types.UserInput{
Username: ptr.String("octocat@github.com"),
Password: ptr.String("password"),
}
user := &types.User{
Email: "octocat@github.com",
}
in := new(bytes.Buffer)
json.NewEncoder(in).Encode(userInput)
w := httptest.NewRecorder()
r := httptest.NewRequest("PATCH", "/api/v1/user", in)
r = r.WithContext(
request.WithUser(r.Context(), user),
)
HandleUpdate(nil)(w, r)
if got, want := w.Code, 500; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
got, want := new(render.Error), &render.Error{Message: bcrypt.ErrHashTooShort.Error()}
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
// the purpose of this unit test is to verify that an invalid
// (in this case missing) request body will result in a bad
// request error returned to the client.
func TestUpdate_BadRequest(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockUser := &types.User{
ID: 1,
Email: "octocat@github.com",
}
in := new(bytes.Buffer)
w := httptest.NewRecorder()
r := httptest.NewRequest("PATCH", "/api/v1/user", in)
r = r.WithContext(
request.WithUser(r.Context(), mockUser),
)
HandleUpdate(nil)(w, r)
if got, want := w.Code, 400; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
got, want := new(render.Error), &render.Error{Message: "EOF"}
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
// the purpose of this unit test is to verify that an error
// updating the database will result in an internal server
// error returned to the client.
func TestUpdate_ServerError(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
userInput := &types.UserInput{
Username: ptr.String("octocat@github.com"),
}
user := &types.User{
Email: "octocat@github.com",
}
users := mocks.NewMockUserStore(controller)
users.EXPECT().Update(gomock.Any(), user).Return(render.ErrNotFound)
in := new(bytes.Buffer)
json.NewEncoder(in).Encode(userInput)
w := httptest.NewRecorder()
r := httptest.NewRequest("PATCH", "/api/v1/user", in)
r = r.WithContext(
request.WithUser(r.Context(), user),
)
HandleUpdate(users)(w, r)
if got, want := w.Code, 500; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}
got, want := new(render.Error), render.ErrNotFound
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}

View File

@ -0,0 +1,80 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users
import (
"encoding/json"
"net/http"
"time"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/bradrydzewski/my-app/types"
"github.com/bradrydzewski/my-app/types/check"
"github.com/rs/zerolog/hlog"
"golang.org/x/crypto/bcrypt"
"github.com/dchest/uniuri"
)
type userCreateInput struct {
Username string `json:"email"`
Password string `json:"password"`
Admin bool `json:"admin"`
}
// HandleCreate returns an http.HandlerFunc that processes an http.Request
// to create the named user account in the system.
func HandleCreate(users store.UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := hlog.FromRequest(r)
in := new(userCreateInput)
err := json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequest(w, err)
log.Debug().Err(err).
Msg("cannot unmarshal json request")
return
}
hash, err := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost)
if err != nil {
render.InternalError(w, err)
log.Debug().Err(err).
Msg("cannot hash password")
return
}
user := &types.User{
Email: in.Username,
Admin: in.Admin,
Password: string(hash),
Salt: uniuri.NewLen(uniuri.UUIDLen),
Created: time.Now().UnixMilli(),
Updated: time.Now().UnixMilli(),
}
if ok, err := check.User(user); !ok {
render.BadRequest(w, err)
log.Debug().Err(err).
Str("user_email", user.Email).
Msg("cannot validate user")
return
}
err = users.Create(ctx, user)
if err != nil {
render.InternalError(w, err)
log.Error().Err(err).
Int64("user_id", user.ID).
Str("user_email", user.Email).
Msg("cannot create user")
} else {
render.JSON(w, user, 200)
}
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users

View File

@ -0,0 +1,45 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users
import (
"net/http"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/rs/zerolog/hlog"
"github.com/go-chi/chi"
)
// HandleDelete returns an http.HandlerFunc that processes an http.Request
// to delete the named user account from the system.
func HandleDelete(users store.UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := hlog.FromRequest(r)
key := chi.URLParam(r, "user")
user, err := users.FindKey(ctx, key)
if err != nil {
render.NotFound(w, err)
log.Debug().Err(err).
Str("user_key", key).
Msg("cannot find user")
return
}
err = users.Delete(ctx, user)
if err != nil {
render.InternalError(w, err)
log.Error().Err(err).
Int64("user_id", user.ID).
Str("user_email", user.Email).
Msg("cannot delete user")
} else {
w.WriteHeader(http.StatusNoContent)
}
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users

View File

@ -0,0 +1,35 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users
import (
"net/http"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/rs/zerolog/hlog"
"github.com/go-chi/chi"
)
// HandleFind returns an http.HandlerFunc that writes json-encoded
// user account information to the the response body.
func HandleFind(users store.UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := hlog.FromRequest(r)
key := chi.URLParam(r, "user")
user, err := users.FindKey(ctx, key)
if err != nil {
render.NotFound(w, err)
log.Debug().Err(err).
Str("user_key", key).
Msg("cannot find user")
} else {
render.JSON(w, user, 200)
}
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users

View File

@ -0,0 +1,49 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users
import (
"net/http"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/api/request"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/bradrydzewski/my-app/types/enum"
"github.com/rs/zerolog/hlog"
)
// HandleList returns an http.HandlerFunc that writes a json-encoded
// list of all registered system users to the response body.
func HandleList(users store.UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
log = hlog.FromRequest(r)
)
params := request.ParseUserFilter(r)
if params.Order == enum.OrderDefault {
params.Order = enum.OrderAsc
}
count, err := users.Count(ctx)
if err != nil {
log.Error().Err(err).
Msg("cannot retrieve user count")
}
list, err := users.List(ctx, params)
if err != nil {
render.InternalError(w, err)
log.Error().Err(err).
Msg("cannot retrieve user list")
return
}
render.Pagination(r, w, params.Page, params.Size, int(count))
render.JSON(w, list, 200)
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users

View File

@ -0,0 +1,112 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users
import (
"encoding/json"
"net/http"
"time"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/bradrydzewski/my-app/types"
"github.com/bradrydzewski/my-app/types/check"
"github.com/gotidy/ptr"
"github.com/rs/zerolog/hlog"
"github.com/go-chi/chi"
"golang.org/x/crypto/bcrypt"
)
// GenerateFromPassword returns the bcrypt hash of the
// password at the given cost.
var hashPassword = bcrypt.GenerateFromPassword
// HandleUpdate returns an http.HandlerFunc that processes an http.Request
// to update a user account.
func HandleUpdate(users store.UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := hlog.FromRequest(r)
key := chi.URLParam(r, "user")
user, err := users.FindKey(ctx, key)
if err != nil {
render.NotFound(w, err)
log.Debug().Err(err).
Str("user_key", key).
Msg("cannot find user")
return
}
in := new(types.UserInput)
if err := json.NewDecoder(r.Body).Decode(in); err != nil {
render.BadRequest(w, err)
log.Debug().Err(err).
Int64("user_id", user.ID).
Str("user_email", user.Email).
Msg("cannot unmarshal request")
return
}
if in.Password != nil {
hash, err := hashPassword([]byte(ptr.ToString(in.Password)), bcrypt.DefaultCost)
if err != nil {
render.InternalError(w, err)
log.Debug().Err(err).
Int64("user_id", user.ID).
Str("user_email", user.Email).
Msg("cannot hash password")
return
}
user.Password = string(hash)
}
if in.Name != nil {
user.Name = ptr.ToString(in.Name)
}
if in.Company != nil {
user.Company = ptr.ToString(in.Company)
}
if in.Admin != nil {
user.Admin = ptr.ToBool(in.Admin)
}
if in.Password != nil {
hash, err := bcrypt.GenerateFromPassword([]byte(ptr.ToString(in.Password)), bcrypt.DefaultCost)
if err != nil {
render.InternalError(w, err)
log.Debug().Err(err).
Msg("cannot hash password")
return
}
user.Password = string(hash)
}
if ok, err := check.User(user); !ok {
render.BadRequest(w, err)
log.Debug().Err(err).
Int64("user_id", user.ID).
Str("user_email", user.Email).
Msg("cannot update user")
return
}
user.Updated = time.Now().UnixMilli()
err = users.Update(ctx, user)
if err != nil {
render.InternalError(w, err)
log.Error().Err(err).
Int64("user_id", user.ID).
Str("user_email", user.Email).
Msg("cannot update user")
} else {
render.JSON(w, user, 200)
}
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package users

View File

@ -0,0 +1,33 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package access
import (
"errors"
"net/http"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/api/request"
)
// SystemAdmin returns an http.HandlerFunc middleware that authorizes
// the user access to system administration capabilities.
func SystemAdmin() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user, ok := request.UserFrom(ctx)
if !ok {
render.ErrorCode(w, errors.New("Requires authentication"), 401)
return
}
if !user.Admin {
render.ErrorCode(w, errors.New("Forbidden"), 403)
return
}
next.ServeHTTP(w, r)
})
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package access

View File

@ -0,0 +1,81 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package address
import (
"net/http"
"strings"
)
// Handler returns an http.HandlerFunc middleware that sets
// the http.Request scheme and hostname.
func Handler(scheme, host string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// update the scheme and host for the inbound
// http.Request so they are available to subsequent
// handlers in the chain.
r.URL.Scheme = scheme
r.URL.Host = host
// if the scheme is not configured, attempt to ascertain
// the scheme from the inbound http.Request.
if r.URL.Scheme == "" {
r.URL.Scheme = resolveScheme(r)
}
// if the host is not configured, attempt to ascertain
// the host from the inbound http.Request.
if r.URL.Host == "" {
r.URL.Host = resolveHost(r)
}
// invoke the next handler in the chain.
next.ServeHTTP(w, r)
})
}
}
// resolveScheme is a helper function that evaluates the http.Request
// and returns the scheme, HTTP or HTTPS. It is able to detect,
// using the X-Forwarded-Proto, if the original request was HTTPS
// and routed through a reverse proxy with SSL termination.
func resolveScheme(r *http.Request) string {
switch {
case r.URL.Scheme == "https":
return "https"
case r.TLS != nil:
return "https"
case strings.HasPrefix(r.Proto, "HTTPS"):
return "https"
case r.Header.Get("X-Forwarded-Proto") == "https":
return "https"
default:
return "http"
}
}
// resolveHost is a helper function that evaluates the http.Request
// and returns the hostname. It is able to detect, using the
// X-Forarded-For header, the original hostname when routed
// through a reverse proxy.
func resolveHost(r *http.Request) string {
switch {
case len(r.Host) != 0:
return r.Host
case len(r.URL.Host) != 0:
return r.URL.Host
case len(r.Header.Get("X-Forwarded-For")) != 0:
return r.Header.Get("X-Forwarded-For")
case len(r.Header.Get("X-Host")) != 0:
return r.Header.Get("X-Host")
case len(r.Header.Get("XFF")) != 0:
return r.Header.Get("XFF")
case len(r.Header.Get("X-Real-IP")) != 0:
return r.Header.Get("X-Real-IP")
default:
return "localhost:3000"
}
}

View File

@ -0,0 +1,5 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package address

View File

@ -0,0 +1,101 @@
// Copyright 2021 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package token
import (
"errors"
"net/http"
"strconv"
"strings"
"time"
"github.com/bradrydzewski/my-app/internal/api/render"
"github.com/bradrydzewski/my-app/internal/api/request"
"github.com/bradrydzewski/my-app/internal/store"
"github.com/bradrydzewski/my-app/internal/token"
"github.com/bradrydzewski/my-app/types"
"github.com/dgrijalva/jwt-go"
"github.com/rs/zerolog"
"github.com/rs/zerolog/hlog"
"github.com/rs/zerolog/log"
)
// Must returns an http.HandlerFunc middleware that authenticates
// the http.Request and errors if the account cannot be authenticated.
func Must(users store.UserStore) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
str := extractToken(r)
if len(str) == 0 {
render.ErrorCode(w, errors.New("Requires authentication"), 401)
return
}
var user *types.User
parsed, err := jwt.ParseWithClaims(str, &token.Claims{}, func(token_ *jwt.Token) (interface{}, error) {
sub := token_.Claims.(*token.Claims).Subject
id, err := strconv.ParseInt(sub, 10, 64)
if err != nil {
return nil, err
}
user, err = users.Find(ctx, id)
if err != nil {
hlog.FromRequest(r).
Error().Err(err).
Int64("user", id).
Msg("cannot find user")
return nil, err
}
return []byte(user.Salt), nil
})
if err != nil {
render.ErrorCode(w, err, 401)
return
}
if parsed.Valid == false {
render.ErrorCode(w, errors.New("Invalid token"), 401)
return
}
if _, ok := parsed.Method.(*jwt.SigningMethodHMAC); !ok {
render.ErrorCode(w, errors.New("Invalid token"), 401)
return
}
// this code should be deprecated, since the jwt.ParseWithClaims
// should fail if the token is expired. TODO remove once we have
// proper unit tests in place.
if claims, ok := parsed.Claims.(*token.Claims); ok {
if claims.ExpiresAt > 0 {
if time.Now().Unix() > claims.ExpiresAt {
render.ErrorCode(w, errors.New("Expired token"), 401)
return
}
}
}
log.Ctx(ctx).UpdateContext(func(c zerolog.Context) zerolog.Context {
return c.Str("session_email", user.Email).Bool("session_admin", user.Admin)
})
next.ServeHTTP(w, r.WithContext(
request.WithUser(ctx, user),
))
})
}
}
func extractToken(r *http.Request) string {
bearer := r.Header.Get("Authorization")
if bearer == "" {
return r.FormValue("access_token")
}
bearer = strings.TrimPrefix(bearer, "Bearer ")
bearer = strings.TrimPrefix(bearer, "IdentityService ")
return bearer
}

Some files were not shown because too many files have changed in this diff Show More