From 663f995238dfde012cf5fc5bf8453477ce735583 Mon Sep 17 00:00:00 2001 From: nav Date: Tue, 10 May 2022 14:14:02 -0700 Subject: [PATCH 01/48] Try things --- api/api.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- go.mod | 7 +++-- go.sum | 20 ++++++++++---- 3 files changed, 96 insertions(+), 11 deletions(-) diff --git a/api/api.go b/api/api.go index 313b834c9..e1f2e383a 100644 --- a/api/api.go +++ b/api/api.go @@ -10,6 +10,10 @@ import ( "sort" "strings" + "github.com/charmbracelet/bubbles/spinner" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/CircleCI-Public/circleci-cli/api/graphql" "github.com/CircleCI-Public/circleci-cli/pipeline" "github.com/CircleCI-Public/circleci-cli/references" @@ -451,6 +455,78 @@ type Orb struct { Categories []OrbCategory } +type errMsg error + +type model struct { + spinner spinner.Model + cl *graphql.Client + req *graphql.Request + res interface{} + quitting bool + err error +} + +type respMsg struct { + err error +} + +func initialModel(cl *graphql.Client, req *graphql.Request, res interface{}) model { + s := spinner.New() + s.Spinner = spinner.Dot + s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) + return model{spinner: s, cl: cl, req: req, res: &res} +} + +func (m model) Init() tea.Cmd { + checkServer := func() tea.Msg { + m.err = m.cl.Run(m.req, &m.res) + return respMsg{err: m.err} + } + return tea.Batch( + m.spinner.Tick, + checkServer, + ) +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + + case tea.KeyMsg: + switch msg.String() { + case "q", "esc", "ctrl+c": + m.quitting = true + return m, tea.Quit + default: + return m, nil + } + + case respMsg: + m.quitting = true + return m, tea.Quit + + case errMsg: + m.err = msg + return m, nil + + default: + var cmd tea.Cmd + m.spinner, cmd = m.spinner.Update(msg) + return m, cmd + } + +} + +func (m model) View() string { + if m.err != nil { + return m.err.Error() + } + str := fmt.Sprintf("\n\n %s Loading forever...press q to quit\n\n", m.spinner.View()) + if m.quitting { + return str + "\n" + } + return str +} + // Shortname returns the orb's name without its associated namespace. func (o *Orb) Shortname() string { _, orbName, err := references.SplitIntoOrbAndNamespace(o.Name) @@ -1731,9 +1807,9 @@ func IntrospectionQuery(cl *graphql.Client) (*IntrospectionResponse, error) { request := graphql.NewRequest(query) request.SetToken(cl.Token) - err := cl.Run(request, &response) + m := initialModel(cl, request, &response) - return &response, err + return &response, m.err } // OrbCategoryID fetches an orb returning the ID diff --git a/go.mod b/go.mod index 89db47c77..9a1cab5d0 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/onsi/ginkgo v1.12.1 github.com/onsi/gomega v1.10.4 github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 - github.com/pkg/errors v0.8.1 + github.com/pkg/errors v0.9.1 github.com/rhysd/go-github-selfupdate v0.0.0-20180520142321-41c1bbb0804a github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.5 @@ -30,8 +30,9 @@ require ( ) require ( - github.com/charmbracelet/bubbletea v0.20.0 // indirect - github.com/charmbracelet/lipgloss v0.5.0 // indirect + github.com/charmbracelet/bubbles v0.10.3 + github.com/charmbracelet/bubbletea v0.20.0 + github.com/charmbracelet/lipgloss v0.5.0 github.com/containerd/console v1.0.3 // indirect github.com/emirpasic/gods v1.12.0 // indirect github.com/fsnotify/fsnotify v1.4.7 // indirect diff --git a/go.sum b/go.sum index 6da023ed1..91e6d3a4a 100644 --- a/go.sum +++ b/go.sum @@ -12,14 +12,21 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/briandowns/spinner v0.0.0-20181018151057-dd69c579ff20 h1:kWWOFAhyzkpi4/+L3++mYiZbuxh1TqYkDMHfFjk6ZfE= github.com/briandowns/spinner v0.0.0-20181018151057-dd69c579ff20/go.mod h1:hw/JEQBIE+c/BLI4aKM8UU8v+ZqrD3h7HC27kKt8JQU= +github.com/charmbracelet/bubbles v0.10.3 h1:fKarbRaObLn/DCsZO4Y3vKCwRUzynQD9L+gGev1E/ho= +github.com/charmbracelet/bubbles v0.10.3/go.mod h1:jOA+DUF1rjZm7gZHcNyIVW+YrBPALKfpGVdJu8UiJsA= +github.com/charmbracelet/bubbletea v0.19.3/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA= github.com/charmbracelet/bubbletea v0.20.0 h1:/b8LEPgCbNr7WWZ2LuE/BV1/r4t5PyYJtDb+J3vpwxc= github.com/charmbracelet/bubbletea v0.20.0/go.mod h1:zpkze1Rioo4rJELjRyGlm9T2YNou1Fm4LIJQSa5QMEM= +github.com/charmbracelet/harmonica v0.1.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/lipgloss v0.4.0/go.mod h1:vmdkHvce7UzX6xkyf4cca8WlwdQ5RQr8fzta+xl7BOM= github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8= github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/coreos/etcd v3.3.24+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -90,6 +97,7 @@ github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -98,11 +106,9 @@ github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaa github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -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-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= @@ -116,11 +122,10 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= -github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY= +github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw= github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI= github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= @@ -139,8 +144,9 @@ github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuK github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rhysd/go-github-selfupdate v0.0.0-20180520142321-41c1bbb0804a h1:YNh/SV+Z0p7kQDUE9Ux+46ruTucvQP43XB06DfZa8Es= @@ -149,6 +155,7 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -205,6 +212,7 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/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-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= From 7d6bc7eb2e0698b6ce87c305854ec3df4ff823ea Mon Sep 17 00:00:00 2001 From: nav Date: Wed, 11 May 2022 10:50:17 -0700 Subject: [PATCH 02/48] Feat: modify all graphql calls to use loading spinner and batch --- api/api.go | 214 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 127 insertions(+), 87 deletions(-) diff --git a/api/api.go b/api/api.go index e1f2e383a..ade233a60 100644 --- a/api/api.go +++ b/api/api.go @@ -470,13 +470,22 @@ type respMsg struct { err error } -func initialModel(cl *graphql.Client, req *graphql.Request, res interface{}) model { +func initializeModel(cl *graphql.Client, req *graphql.Request, res interface{}) model { s := spinner.New() s.Spinner = spinner.Dot s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) return model{spinner: s, cl: cl, req: req, res: &res} } +func initializeTeaProgram(m model) { + p := tea.NewProgram(m) + + if p.Start() != nil { + fmt.Println("Error: Could not start Tea Program") + os.Exit(1) + } +} + func (m model) Init() tea.Cmd { checkServer := func() tea.Msg { m.err = m.cl.Run(m.req, &m.res) @@ -520,9 +529,9 @@ func (m model) View() string { if m.err != nil { return m.err.Error() } - str := fmt.Sprintf("\n\n %s Loading forever...press q to quit\n\n", m.spinner.View()) + str := fmt.Sprintf("%s Fetching data… (press q to quit)", m.spinner.View()) if m.quitting { - return str + "\n" + return "" } return str } @@ -581,9 +590,10 @@ func WhoamiQuery(cl *graphql.Client) (*WhoamiResponse, error) { request := graphql.NewRequest(query) request.SetToken(cl.Token) - err := cl.Run(request, &response) - if err != nil { - return nil, err + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) + if m.err != nil { + return nil, m.err } return &response, nil @@ -637,10 +647,11 @@ func ConfigQuery(cl *graphql.Client, configPath string, orgSlug string, params p } request.SetToken(cl.Token) - err = cl.Run(request, &response) + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) - if err != nil { - return nil, errors.Wrap(err, "Unable to validate config") + if m.err != nil { + return nil, errors.Wrap(m.err, "Unable to validate config") } if len(response.BuildConfig.ConfigResponse.Errors) > 0 { @@ -673,10 +684,11 @@ func OrbQuery(cl *graphql.Client, configPath string) (*ConfigResponse, error) { request.Var("config", config) request.SetToken(cl.Token) - err = cl.Run(request, &response) + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) - if err != nil { - return nil, errors.Wrap(err, "Unable to validate config") + if m.err != nil { + return nil, errors.Wrap(m.err, "Unable to validate config") } if len(response.OrbConfig.ConfigResponse.Errors) > 0 { @@ -712,9 +724,10 @@ func OrbImportVersion(cl *graphql.Client, orbSrc string, orbID string, orbVersio request.Var("orbId", orbID) request.Var("version", orbVersion) - err := cl.Run(request, &response) - if err != nil { - return nil, errors.Wrap(err, "unable to import orb version") + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) + if m.err != nil { + return nil, errors.Wrap(m.err, "unable to import orb version") } if len(response.ImportOrbVersion.Errors) > 0 { @@ -758,10 +771,11 @@ func OrbPublishByName(cl *graphql.Client, configPath, orbName, namespaceName, or request.Var("namespaceName", namespaceName) request.Var("version", orbVersion) - err = cl.Run(request, &response) + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) - if err != nil { - return nil, errors.Wrap(err, "Unable to publish orb") + if m.err != nil { + return nil, errors.Wrap(m.err, "Unable to publish orb") } if len(response.PublishOrb.Errors) > 0 { @@ -794,9 +808,10 @@ func OrbExists(cl *graphql.Client, namespace string, orb string) (bool, bool, er request.Var("name", name) request.Var("namespace", namespace) - err := cl.Run(request, &response) - if err != nil { - return false, false, err + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) + if m.err != nil { + return false, false, m.err } return response.Orb.ID != "", response.Orb.IsPrivate, nil @@ -825,11 +840,12 @@ func OrbID(cl *graphql.Client, namespace string, orb string) (*OrbIDResponse, er request.Var("name", name) request.Var("namespace", namespace) - err := cl.Run(request, &response) + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) // If there is an error, or the request was successful, return now. - if err != nil || response.Orb.ID != "" { - return &response, err + if m.err != nil || response.Orb.ID != "" { + return &response, m.err } // Otherwise, we want to generate a nice error message for the user. @@ -866,9 +882,10 @@ func CreateImportedNamespace(cl *graphql.Client, name string) (*ImportNamespaceR request.Var("name", name) - err := cl.Run(request, &response) - if err != nil { - return nil, err + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) + if m.err != nil { + return nil, m.err } if len(response.ImportNamespace.Errors) > 0 { @@ -903,14 +920,15 @@ func CreateNamespaceWithOwnerID(cl *graphql.Client, name string, ownerID string) request.Var("name", name) request.Var("organizationId", ownerID) - err := cl.Run(request, &response) + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) if len(response.CreateNamespace.Errors) > 0 { return nil, response.CreateNamespace.Errors } - if err != nil { - return nil, err + if m.err != nil { + return nil, m.err } return &response, nil @@ -934,10 +952,11 @@ func getOrganization(cl *graphql.Client, organizationName string, organizationVc request.Var("organizationName", organizationName) request.Var("organizationVcs", strings.ToUpper(organizationVcs)) - err := cl.Run(request, &response) + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) - if err != nil { - return nil, errors.Wrapf(err, "Unable to find organization %s of vcs-type %s", organizationName, organizationVcs) + if m.err != nil { + return nil, errors.Wrapf(m.err, "Unable to find organization %s of vcs-type %s", organizationName, organizationVcs) } return &response, nil @@ -973,9 +992,10 @@ mutation($name: String!) { request.SetToken(cl.Token) request.Var("name", name) - err := cl.Run(request, &response) - if err != nil { - return err + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) + if m.err != nil { + return m.err } if len(response.DeleteNamespaceAlias.Errors) > 0 { @@ -1011,9 +1031,10 @@ mutation($id: UUID!) { request.SetToken(cl.Token) request.Var("id", id) - err := cl.Run(request, &response) - if err != nil { - return err + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) + if m.err != nil { + return m.err } if len(response.DeleteNamespace.Errors) > 0 { @@ -1061,8 +1082,10 @@ func GetNamespace(cl *graphql.Client, name string) (*GetNamespaceResponse, error request.Var("name", name) - if err := cl.Run(request, &response); err != nil { - return nil, errors.Wrapf(err, "failed to load namespace '%s'", err) + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) + if m.err != nil { + return nil, errors.Wrapf(m.err, "failed to load namespace '%s'", m.err) } if response.RegistryNamespace.ID == "" { @@ -1089,8 +1112,10 @@ func NamespaceExists(cl *graphql.Client, namespace string) (bool, error) { request.SetToken(cl.Token) request.Var("name", namespace) - if err := cl.Run(request, &response); err != nil { - return false, errors.Wrapf(err, "failed to load namespace '%s'", err) + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) + if m.err != nil { + return false, errors.Wrapf(m.err, "failed to load namespace '%s'", m.err) } if response.RegistryNamespace.ID != "" { @@ -1125,14 +1150,15 @@ func renameNamespaceWithNsID(cl *graphql.Client, id, newName string) (*RenameNam request.Var("namespaceId", id) request.Var("newName", newName) - err := cl.Run(request, &response) + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) if len(response.RenameNamespace.Errors) > 0 { return nil, response.RenameNamespace.Errors } - if err != nil { - return nil, err + if m.err != nil { + return nil, m.err } return &response, nil @@ -1172,14 +1198,15 @@ func createOrbWithNsID(cl *graphql.Client, name string, namespaceID string, isPr request.Var("registryNamespaceId", namespaceID) request.Var("isPrivate", isPrivate) - err := cl.Run(request, &response) + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) if len(response.CreateOrb.Errors) > 0 { return nil, response.CreateOrb.Errors } - if err != nil { - return nil, err + if m.err != nil { + return nil, m.err } return &response, nil @@ -1225,9 +1252,10 @@ func CreateImportedOrb(cl *graphql.Client, namespace string, name string) (*Impo request.Var("name", name) request.Var("registryNamespaceId", res.RegistryNamespace.ID) - err = cl.Run(request, &response) - if err != nil { - return nil, err + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) + if m.err != nil { + return nil, m.err } if len(response.ImportOrb.Errors) > 0 { @@ -1298,9 +1326,10 @@ func OrbLatestVersion(cl *graphql.Client, namespace string, orb string) (string, request.Var("name", name) - err := cl.Run(request, &response) - if err != nil { - return "", err + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) + if m.err != nil { + return "", m.err } if len(response.Orb.Versions) != 1 { @@ -1349,14 +1378,15 @@ func OrbPromoteByName(cl *graphql.Client, namespaceName, orbName, label, segment request.Var("devVersion", label) request.Var("semanticVersion", v2) - err = cl.Run(request, &response) + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) if len(response.PromoteOrb.Errors) > 0 { return nil, response.PromoteOrb.Errors } - if err != nil { - return nil, errors.Wrap(err, "Unable to promote orb") + if m.err != nil { + return nil, errors.Wrap(m.err, "Unable to promote orb") } return &response.PromoteOrb.Orb, nil @@ -1392,14 +1422,15 @@ func OrbSetOrbListStatus(cl *graphql.Client, namespace string, orb string, list request.Var("orbId", id.Orb.ID) request.Var("list", list) - err = cl.Run(request, &response) + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) if len(response.SetOrbListStatus.Errors) > 0 { return nil, response.SetOrbListStatus.Errors } - if err != nil { - return nil, errors.Wrap(err, "Unable to set orb list status") + if m.err != nil { + return nil, errors.Wrap(m.err, "Unable to set orb list status") } return &response.SetOrbListStatus.Listed, nil @@ -1443,9 +1474,10 @@ func OrbSource(cl *graphql.Client, orbRef string) (string, error) { request.SetToken(cl.Token) request.Var("orbVersionRef", ref) - err := cl.Run(request, &response) - if err != nil { - return "", err + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) + if m.err != nil { + return "", m.err } if response.OrbVersion.ID == "" { @@ -1512,9 +1544,10 @@ func OrbInfo(cl *graphql.Client, orbRef string) (*OrbVersion, error) { request.SetToken(cl.Token) request.Var("orbVersionRef", ref) - err := cl.Run(request, &response) - if err != nil { - return nil, err + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) + if m.err != nil { + return nil, m.err } if response.OrbVersion.ID == "" { @@ -1532,7 +1565,7 @@ func OrbInfo(cl *graphql.Client, orbRef string) (*OrbVersion, error) { } // Parse the orb source to get its commands, executors and jobs - err = yaml.Unmarshal([]byte(response.OrbVersion.Source), &response.OrbVersion.Orb) + err := yaml.Unmarshal([]byte(response.OrbVersion.Source), &response.OrbVersion.Orb) if err != nil { return nil, errors.Wrapf(err, "Corrupt Orb %s %s", response.OrbVersion.Orb.Name, response.OrbVersion.Version) } @@ -1582,9 +1615,10 @@ query ListOrbs ($after: String!, $certifiedOnly: Boolean!) { request.Var("after", currentCursor) request.Var("certifiedOnly", !uncertified) - err := cl.Run(request, &result) - if err != nil { - return nil, errors.Wrap(err, "GraphQL query failed") + m := initializeModel(cl, request, &result) + initializeTeaProgram(m) + if m.err != nil { + return nil, errors.Wrap(m.err, "GraphQL query failed") } Orbs: @@ -1655,9 +1689,10 @@ query namespaceOrbs ($namespace: String, $after: String!) { request.Var("after", currentCursor) request.Var("namespace", namespace) - err := cl.Run(request, &result) - if err != nil { - return nil, errors.Wrap(err, "GraphQL query failed") + m := initializeModel(cl, request, &result) + initializeTeaProgram(m) + if m.err != nil { + return nil, errors.Wrap(m.err, "GraphQL query failed") } if result.RegistryNamespace.ID == "" { @@ -1741,9 +1776,10 @@ query namespaceOrbs ($namespace: String, $after: String!, $view: OrbListViewType orbs.Namespace = namespace - err := cl.Run(request, &result) - if err != nil { - return nil, errors.Wrap(err, "GraphQL query failed") + m := initializeModel(cl, request, &result) + initializeTeaProgram(m) + if m.err != nil { + return nil, errors.Wrap(m.err, "GraphQL query failed") } if result.RegistryNamespace.ID == "" { @@ -1807,7 +1843,8 @@ func IntrospectionQuery(cl *graphql.Client) (*IntrospectionResponse, error) { request := graphql.NewRequest(query) request.SetToken(cl.Token) - m := initialModel(cl, request, &response) + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) return &response, m.err } @@ -1827,11 +1864,12 @@ func OrbCategoryID(cl *graphql.Client, name string) (*OrbCategoryIDResponse, err request.Var("name", name) - err := cl.Run(request, &response) + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) // If there is an error, or the request was successful, return now. - if err != nil || response.OrbCategoryByName.ID != "" { - return &response, err + if m.err != nil || response.OrbCategoryByName.ID != "" { + return &response, m.err } return nil, fmt.Errorf("the '%s' category does not exist. Did you misspell the category name? To see the list of category names, please run 'circleci orb list-categories'.", name) @@ -1884,7 +1922,8 @@ func AddOrRemoveOrbCategorization(cl *graphql.Client, namespace string, orb stri request.Var("orbId", orbId.Orb.ID) request.Var("categoryId", categoryId.OrbCategoryByName.ID) - err = cl.Run(request, &response) + m := initializeModel(cl, request, &response) + initializeTeaProgram(m) responseData := response[mutationName] @@ -1892,8 +1931,8 @@ func AddOrRemoveOrbCategorization(cl *graphql.Client, namespace string, orb stri return &responseData.Errors } - if err != nil { - return errors.Wrap(err, "Unable to add/remove orb categorization") + if m.err != nil { + return errors.Wrap(m.err, "Unable to add/remove orb categorization") } return nil @@ -1930,9 +1969,10 @@ func ListOrbCategories(cl *graphql.Client) (*OrbCategoriesForListing, error) { request := graphql.NewRequest(query) request.Var("after", currentCursor) - err := cl.Run(request, &result) - if err != nil { - return nil, errors.Wrap(err, "GraphQL query failed") + m := initializeModel(cl, request, &result) + initializeTeaProgram(m) + if m.err != nil { + return nil, errors.Wrap(m.err, "GraphQL query failed") } for i := range result.OrbCategories.Edges { From 6f39b9c84dbc4b4ac225b86d65761afb54c9bfec Mon Sep 17 00:00:00 2001 From: nav Date: Thu, 12 May 2022 15:48:25 -0700 Subject: [PATCH 03/48] Refactor: separate helper file --- api/api.go | 316 ++++++++++++++++---------------------------- api/helper/charm.go | 118 +++++++++++++++++ 2 files changed, 234 insertions(+), 200 deletions(-) create mode 100644 api/helper/charm.go diff --git a/api/api.go b/api/api.go index ade233a60..71fb7a8fa 100644 --- a/api/api.go +++ b/api/api.go @@ -10,11 +10,8 @@ import ( "sort" "strings" - "github.com/charmbracelet/bubbles/spinner" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/CircleCI-Public/circleci-cli/api/graphql" + "github.com/CircleCI-Public/circleci-cli/api/helper" "github.com/CircleCI-Public/circleci-cli/pipeline" "github.com/CircleCI-Public/circleci-cli/references" "github.com/CircleCI-Public/circleci-cli/settings" @@ -455,87 +452,6 @@ type Orb struct { Categories []OrbCategory } -type errMsg error - -type model struct { - spinner spinner.Model - cl *graphql.Client - req *graphql.Request - res interface{} - quitting bool - err error -} - -type respMsg struct { - err error -} - -func initializeModel(cl *graphql.Client, req *graphql.Request, res interface{}) model { - s := spinner.New() - s.Spinner = spinner.Dot - s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) - return model{spinner: s, cl: cl, req: req, res: &res} -} - -func initializeTeaProgram(m model) { - p := tea.NewProgram(m) - - if p.Start() != nil { - fmt.Println("Error: Could not start Tea Program") - os.Exit(1) - } -} - -func (m model) Init() tea.Cmd { - checkServer := func() tea.Msg { - m.err = m.cl.Run(m.req, &m.res) - return respMsg{err: m.err} - } - return tea.Batch( - m.spinner.Tick, - checkServer, - ) -} - -func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - - case tea.KeyMsg: - switch msg.String() { - case "q", "esc", "ctrl+c": - m.quitting = true - return m, tea.Quit - default: - return m, nil - } - - case respMsg: - m.quitting = true - return m, tea.Quit - - case errMsg: - m.err = msg - return m, nil - - default: - var cmd tea.Cmd - m.spinner, cmd = m.spinner.Update(msg) - return m, cmd - } - -} - -func (m model) View() string { - if m.err != nil { - return m.err.Error() - } - str := fmt.Sprintf("%s Fetching data… (press q to quit)", m.spinner.View()) - if m.quitting { - return "" - } - return str -} - // Shortname returns the orb's name without its associated namespace. func (o *Orb) Shortname() string { _, orbName, err := references.SplitIntoOrbAndNamespace(o.Name) @@ -590,10 +506,10 @@ func WhoamiQuery(cl *graphql.Client) (*WhoamiResponse, error) { request := graphql.NewRequest(query) request.SetToken(cl.Token) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) - if m.err != nil { - return nil, m.err + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return nil, m.Err } return &response, nil @@ -647,11 +563,11 @@ func ConfigQuery(cl *graphql.Client, configPath string, orgSlug string, params p } request.SetToken(cl.Token) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) - if m.err != nil { - return nil, errors.Wrap(m.err, "Unable to validate config") + if m.Err != nil { + return nil, errors.Wrap(m.Err, "Unable to validate config") } if len(response.BuildConfig.ConfigResponse.Errors) > 0 { @@ -684,11 +600,11 @@ func OrbQuery(cl *graphql.Client, configPath string) (*ConfigResponse, error) { request.Var("config", config) request.SetToken(cl.Token) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) - if m.err != nil { - return nil, errors.Wrap(m.err, "Unable to validate config") + if m.Err != nil { + return nil, errors.Wrap(m.Err, "Unable to validate config") } if len(response.OrbConfig.ConfigResponse.Errors) > 0 { @@ -724,10 +640,10 @@ func OrbImportVersion(cl *graphql.Client, orbSrc string, orbID string, orbVersio request.Var("orbId", orbID) request.Var("version", orbVersion) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) - if m.err != nil { - return nil, errors.Wrap(m.err, "unable to import orb version") + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return nil, errors.Wrap(m.Err, "unable to import orb version") } if len(response.ImportOrbVersion.Errors) > 0 { @@ -771,11 +687,11 @@ func OrbPublishByName(cl *graphql.Client, configPath, orbName, namespaceName, or request.Var("namespaceName", namespaceName) request.Var("version", orbVersion) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) - if m.err != nil { - return nil, errors.Wrap(m.err, "Unable to publish orb") + if m.Err != nil { + return nil, errors.Wrap(m.Err, "Unable to publish orb") } if len(response.PublishOrb.Errors) > 0 { @@ -808,10 +724,10 @@ func OrbExists(cl *graphql.Client, namespace string, orb string) (bool, bool, er request.Var("name", name) request.Var("namespace", namespace) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) - if m.err != nil { - return false, false, m.err + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return false, false, m.Err } return response.Orb.ID != "", response.Orb.IsPrivate, nil @@ -840,12 +756,12 @@ func OrbID(cl *graphql.Client, namespace string, orb string) (*OrbIDResponse, er request.Var("name", name) request.Var("namespace", namespace) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) // If there is an error, or the request was successful, return now. - if m.err != nil || response.Orb.ID != "" { - return &response, m.err + if m.Err != nil || response.Orb.ID != "" { + return &response, m.Err } // Otherwise, we want to generate a nice error message for the user. @@ -882,10 +798,10 @@ func CreateImportedNamespace(cl *graphql.Client, name string) (*ImportNamespaceR request.Var("name", name) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) - if m.err != nil { - return nil, m.err + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return nil, m.Err } if len(response.ImportNamespace.Errors) > 0 { @@ -920,15 +836,15 @@ func CreateNamespaceWithOwnerID(cl *graphql.Client, name string, ownerID string) request.Var("name", name) request.Var("organizationId", ownerID) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) if len(response.CreateNamespace.Errors) > 0 { return nil, response.CreateNamespace.Errors } - if m.err != nil { - return nil, m.err + if m.Err != nil { + return nil, m.Err } return &response, nil @@ -952,11 +868,11 @@ func getOrganization(cl *graphql.Client, organizationName string, organizationVc request.Var("organizationName", organizationName) request.Var("organizationVcs", strings.ToUpper(organizationVcs)) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) - if m.err != nil { - return nil, errors.Wrapf(m.err, "Unable to find organization %s of vcs-type %s", organizationName, organizationVcs) + if m.Err != nil { + return nil, errors.Wrapf(m.Err, "Unable to find organization %s of vcs-type %s", organizationName, organizationVcs) } return &response, nil @@ -992,10 +908,10 @@ mutation($name: String!) { request.SetToken(cl.Token) request.Var("name", name) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) - if m.err != nil { - return m.err + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return m.Err } if len(response.DeleteNamespaceAlias.Errors) > 0 { @@ -1031,10 +947,10 @@ mutation($id: UUID!) { request.SetToken(cl.Token) request.Var("id", id) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) - if m.err != nil { - return m.err + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return m.Err } if len(response.DeleteNamespace.Errors) > 0 { @@ -1082,10 +998,10 @@ func GetNamespace(cl *graphql.Client, name string) (*GetNamespaceResponse, error request.Var("name", name) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) - if m.err != nil { - return nil, errors.Wrapf(m.err, "failed to load namespace '%s'", m.err) + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return nil, errors.Wrapf(m.Err, "failed to load namespace '%s'", m.Err) } if response.RegistryNamespace.ID == "" { @@ -1112,10 +1028,10 @@ func NamespaceExists(cl *graphql.Client, namespace string) (bool, error) { request.SetToken(cl.Token) request.Var("name", namespace) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) - if m.err != nil { - return false, errors.Wrapf(m.err, "failed to load namespace '%s'", m.err) + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return false, errors.Wrapf(m.Err, "failed to load namespace '%s'", m.Err) } if response.RegistryNamespace.ID != "" { @@ -1150,15 +1066,15 @@ func renameNamespaceWithNsID(cl *graphql.Client, id, newName string) (*RenameNam request.Var("namespaceId", id) request.Var("newName", newName) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) if len(response.RenameNamespace.Errors) > 0 { return nil, response.RenameNamespace.Errors } - if m.err != nil { - return nil, m.err + if m.Err != nil { + return nil, m.Err } return &response, nil @@ -1198,15 +1114,15 @@ func createOrbWithNsID(cl *graphql.Client, name string, namespaceID string, isPr request.Var("registryNamespaceId", namespaceID) request.Var("isPrivate", isPrivate) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) if len(response.CreateOrb.Errors) > 0 { return nil, response.CreateOrb.Errors } - if m.err != nil { - return nil, m.err + if m.Err != nil { + return nil, m.Err } return &response, nil @@ -1252,10 +1168,10 @@ func CreateImportedOrb(cl *graphql.Client, namespace string, name string) (*Impo request.Var("name", name) request.Var("registryNamespaceId", res.RegistryNamespace.ID) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) - if m.err != nil { - return nil, m.err + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return nil, m.Err } if len(response.ImportOrb.Errors) > 0 { @@ -1326,10 +1242,10 @@ func OrbLatestVersion(cl *graphql.Client, namespace string, orb string) (string, request.Var("name", name) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) - if m.err != nil { - return "", m.err + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return "", m.Err } if len(response.Orb.Versions) != 1 { @@ -1378,15 +1294,15 @@ func OrbPromoteByName(cl *graphql.Client, namespaceName, orbName, label, segment request.Var("devVersion", label) request.Var("semanticVersion", v2) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) if len(response.PromoteOrb.Errors) > 0 { return nil, response.PromoteOrb.Errors } - if m.err != nil { - return nil, errors.Wrap(m.err, "Unable to promote orb") + if m.Err != nil { + return nil, errors.Wrap(m.Err, "Unable to promote orb") } return &response.PromoteOrb.Orb, nil @@ -1422,15 +1338,15 @@ func OrbSetOrbListStatus(cl *graphql.Client, namespace string, orb string, list request.Var("orbId", id.Orb.ID) request.Var("list", list) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) if len(response.SetOrbListStatus.Errors) > 0 { return nil, response.SetOrbListStatus.Errors } - if m.err != nil { - return nil, errors.Wrap(m.err, "Unable to set orb list status") + if m.Err != nil { + return nil, errors.Wrap(m.Err, "Unable to set orb list status") } return &response.SetOrbListStatus.Listed, nil @@ -1474,10 +1390,10 @@ func OrbSource(cl *graphql.Client, orbRef string) (string, error) { request.SetToken(cl.Token) request.Var("orbVersionRef", ref) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) - if m.err != nil { - return "", m.err + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return "", m.Err } if response.OrbVersion.ID == "" { @@ -1544,10 +1460,10 @@ func OrbInfo(cl *graphql.Client, orbRef string) (*OrbVersion, error) { request.SetToken(cl.Token) request.Var("orbVersionRef", ref) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) - if m.err != nil { - return nil, m.err + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return nil, m.Err } if response.OrbVersion.ID == "" { @@ -1615,10 +1531,10 @@ query ListOrbs ($after: String!, $certifiedOnly: Boolean!) { request.Var("after", currentCursor) request.Var("certifiedOnly", !uncertified) - m := initializeModel(cl, request, &result) - initializeTeaProgram(m) - if m.err != nil { - return nil, errors.Wrap(m.err, "GraphQL query failed") + m := helper.InitializeGqlModel(cl, request, &result) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return nil, errors.Wrap(m.Err, "GraphQL query failed") } Orbs: @@ -1689,10 +1605,10 @@ query namespaceOrbs ($namespace: String, $after: String!) { request.Var("after", currentCursor) request.Var("namespace", namespace) - m := initializeModel(cl, request, &result) - initializeTeaProgram(m) - if m.err != nil { - return nil, errors.Wrap(m.err, "GraphQL query failed") + m := helper.InitializeGqlModel(cl, request, &result) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return nil, errors.Wrap(m.Err, "GraphQL query failed") } if result.RegistryNamespace.ID == "" { @@ -1776,10 +1692,10 @@ query namespaceOrbs ($namespace: String, $after: String!, $view: OrbListViewType orbs.Namespace = namespace - m := initializeModel(cl, request, &result) - initializeTeaProgram(m) - if m.err != nil { - return nil, errors.Wrap(m.err, "GraphQL query failed") + m := helper.InitializeGqlModel(cl, request, &result) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return nil, errors.Wrap(m.Err, "GraphQL query failed") } if result.RegistryNamespace.ID == "" { @@ -1843,10 +1759,10 @@ func IntrospectionQuery(cl *graphql.Client) (*IntrospectionResponse, error) { request := graphql.NewRequest(query) request.SetToken(cl.Token) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) - return &response, m.err + return &response, m.Err } // OrbCategoryID fetches an orb returning the ID @@ -1864,12 +1780,12 @@ func OrbCategoryID(cl *graphql.Client, name string) (*OrbCategoryIDResponse, err request.Var("name", name) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) // If there is an error, or the request was successful, return now. - if m.err != nil || response.OrbCategoryByName.ID != "" { - return &response, m.err + if m.Err != nil || response.OrbCategoryByName.ID != "" { + return &response, m.Err } return nil, fmt.Errorf("the '%s' category does not exist. Did you misspell the category name? To see the list of category names, please run 'circleci orb list-categories'.", name) @@ -1922,8 +1838,8 @@ func AddOrRemoveOrbCategorization(cl *graphql.Client, namespace string, orb stri request.Var("orbId", orbId.Orb.ID) request.Var("categoryId", categoryId.OrbCategoryByName.ID) - m := initializeModel(cl, request, &response) - initializeTeaProgram(m) + m := helper.InitializeGqlModel(cl, request, &response) + helper.InitializeTeaProgram(m) responseData := response[mutationName] @@ -1931,8 +1847,8 @@ func AddOrRemoveOrbCategorization(cl *graphql.Client, namespace string, orb stri return &responseData.Errors } - if m.err != nil { - return errors.Wrap(m.err, "Unable to add/remove orb categorization") + if m.Err != nil { + return errors.Wrap(m.Err, "Unable to add/remove orb categorization") } return nil @@ -1969,10 +1885,10 @@ func ListOrbCategories(cl *graphql.Client) (*OrbCategoriesForListing, error) { request := graphql.NewRequest(query) request.Var("after", currentCursor) - m := initializeModel(cl, request, &result) - initializeTeaProgram(m) - if m.err != nil { - return nil, errors.Wrap(m.err, "GraphQL query failed") + m := helper.InitializeGqlModel(cl, request, &result) + helper.InitializeTeaProgram(m) + if m.Err != nil { + return nil, errors.Wrap(m.Err, "GraphQL query failed") } for i := range result.OrbCategories.Edges { diff --git a/api/helper/charm.go b/api/helper/charm.go new file mode 100644 index 000000000..e4de169d7 --- /dev/null +++ b/api/helper/charm.go @@ -0,0 +1,118 @@ +package helper + +import ( + "fmt" + "net/http" + "os" + + "github.com/CircleCI-Public/circleci-cli/api/graphql" + "github.com/charmbracelet/bubbles/spinner" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +type errMsg error + +type model struct { + spinner spinner.Model + restClient *http.Client + restReq *http.Request + RestRes *http.Response + gqlClient *graphql.Client + gqlReq *graphql.Request + gqlRes interface{} + quitting bool + Err error +} + +type respMsg struct { + err error +} + +func initializeModel() model { + s := spinner.New() + s.Spinner = spinner.Dot + s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) + return model{spinner: s} +} + +func InitializeGqlModel(cl *graphql.Client, req *graphql.Request, res interface{}) model { + m := initializeModel() + m.gqlClient = cl + m.gqlReq = req + m.gqlRes = &res + return m +} + +func InitializeRestModel(cl *http.Client, req *http.Request) model { + m := initializeModel() + m.restClient = cl + m.restReq = req + return m +} + +func InitializeTeaProgram(m model) { + p := tea.NewProgram(m) + + if p.Start() != nil { + fmt.Println("Error: Could not start Tea Program") + os.Exit(1) + } +} + +func (m model) Init() tea.Cmd { + checkServer := func() tea.Msg { + if m.gqlClient != nil { + m.Err = m.gqlClient.Run(m.gqlReq, &m.gqlRes) + } else { + // fmt.Println(m.restReq) + m.RestRes, m.Err = m.restClient.Do(m.restReq) + fmt.Println(1, m.RestRes) + } + + return respMsg{err: m.Err} + } + return tea.Batch( + m.spinner.Tick, + checkServer, + ) +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + + case tea.KeyMsg: + switch msg.String() { + case "q", "esc", "ctrl+c": + m.quitting = true + return m, tea.Quit + default: + return m, nil + } + + case respMsg: + m.quitting = true + return m, tea.Quit + + case errMsg: + m.Err = msg + return m, nil + + default: + var cmd tea.Cmd + m.spinner, cmd = m.spinner.Update(msg) + return m, cmd + } + +} + +func (m model) View() string { + if m.Err != nil { + return m.Err.Error() + } + str := fmt.Sprintf("%s Fetching data… (press q to quit)", m.spinner.View()) + if m.quitting { + return "" + } + return str +} From a229deba47e724363b34aa8ce9092206918bb931 Mon Sep 17 00:00:00 2001 From: Robin Schroer Date: Tue, 10 May 2022 12:02:52 +0000 Subject: [PATCH 04/48] Add API bindings for the pipeline scheduling REST API This is actually meant to be used in the CircleCI terraform provider, which uses the CLI as a library. As such I'm not adding any CLI-user-visible way of interacting with schedules as part of this. --- api/schedule.go | 42 ++++ api/schedule_rest.go | 487 +++++++++++++++++++++++++++++++++++++++++++ api/schedule_test.go | 206 ++++++++++++++++++ 3 files changed, 735 insertions(+) create mode 100644 api/schedule.go create mode 100644 api/schedule_rest.go create mode 100644 api/schedule_test.go diff --git a/api/schedule.go b/api/schedule.go new file mode 100644 index 000000000..9452195b1 --- /dev/null +++ b/api/schedule.go @@ -0,0 +1,42 @@ +package api + +import ( + "time" +) + +type Timetable struct { + PerHour uint `json:"per-hour"` + HoursOfDay []uint `json:"hours-of-day"` + DaysOfWeek []string `json:"days-of-week"` +} + +type Actor struct { + ID string `json:"id"` + Login string `json:"login"` + Name string `json:"name"` +} + +type Schedule struct { + ID string `json:"id"` + ProjectSlug string `json:"project-slug"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Timetable Timetable `json:"timetable"` + Actor Actor `json:"actor"` + Parameters map[string]string `json:"parameters"` + CreatedAt time.Time `json:"created-at"` + UpdatedAt time.Time `json:"updated-at"` +} + +type ScheduleInterface interface { + Schedules(vcs, org, project string) (*[]Schedule, error) + ScheduleByID(scheduleID string) (*Schedule, error) + ScheduleByName(vcs, org, project, name string) (*Schedule, error) + DeleteSchedule(scheduleID string) error + CreateSchedule(vcs, org, project, name, description string, + useSchedulingSystem bool, timetable Timetable, + parameters map[string]string) (*Schedule, error) + UpdateSchedule(scheduleID, name, description string, + useSchedulingSystem bool, timetable Timetable, + parameters map[string]string) (*Schedule, error) +} diff --git a/api/schedule_rest.go b/api/schedule_rest.go new file mode 100644 index 000000000..638f63c1d --- /dev/null +++ b/api/schedule_rest.go @@ -0,0 +1,487 @@ +package api + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" + + "github.com/CircleCI-Public/circleci-cli/api/header" + "github.com/CircleCI-Public/circleci-cli/settings" + "github.com/CircleCI-Public/circleci-cli/version" + "github.com/pkg/errors" +) + +// Communicates with the CircleCI REST API to ask questions about +// schedules. It satisfies api.ScheduleInterface. +type ScheduleRestClient struct { + token string + server string + client *http.Client +} + +type listSchedulesResponse struct { + Items []Schedule + NextPageToken *string `json:"next_page_token"` + client *ScheduleRestClient + params *listSchedulesParams +} + +type listSchedulesParams struct { + PageToken *string +} + +// Creates a new schedule in the supplied project. +func (c *ScheduleRestClient) CreateSchedule(vcs, org, project, name, description string, + useSchedulingSystem bool, timetable Timetable, parameters map[string]string) (*Schedule, error) { + + req, err := c.newCreateScheduleRequest(vcs, org, project, name, description, useSchedulingSystem, timetable, parameters) + if err != nil { + return nil, err + } + + resp, err := c.client.Do(req) + + if err != nil { + return nil, err + } + + bodyBytes, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return nil, err + } + + if resp.StatusCode != 201 { + var dest errorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + return nil, errors.New(*dest.Message) + } + + var schedule Schedule + if err := json.Unmarshal(bodyBytes, &schedule); err != nil { + return nil, err + } + + return &schedule, nil +} + +// Updates an existing schedule. +func (c *ScheduleRestClient) UpdateSchedule(scheduleID, name, description string, + useSchedulingSystem bool, timetable Timetable, parameters map[string]string) (*Schedule, error) { + + req, err := c.newUpdateScheduleRequest(scheduleID, name, description, useSchedulingSystem, timetable, parameters) + if err != nil { + return nil, err + } + + resp, err := c.client.Do(req) + + if err != nil { + return nil, err + } + + bodyBytes, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return nil, err + } + + if resp.StatusCode != 200 { + var dest errorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + return nil, errors.New(*dest.Message) + } + + var schedule Schedule + if err := json.Unmarshal(bodyBytes, &schedule); err != nil { + return nil, err + } + + return &schedule, nil +} + +// Deletes the schedule with the given ID. +func (c *ScheduleRestClient) DeleteSchedule(scheduleID string) error { + req, err := c.newDeleteScheduleRequest(scheduleID) + + if err != nil { + return err + } + + resp, err := c.client.Do(req) + if err != nil { + return err + } + + bodyBytes, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return err + } + if resp.StatusCode != 200 { + var dest errorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return err + } + return errors.New(*dest.Message) + } + return nil +} + +// Returns all of the schedules for a given project. Note that +// pagination is not currently supported - we get all pages of +// schedules and return them all. +func (c *ScheduleRestClient) Schedules(vcs, org, project string) (*[]Schedule, error) { + schedules, err := c.listAllSchedules(vcs, org, project, &listSchedulesParams{}) + return &schedules, err +} + +// Returns the schedule with the given ID. +func (c *ScheduleRestClient) ScheduleByID(scheduleID string) (*Schedule, error) { + req, err := c.newGetScheduleRequest(scheduleID) + + if err != nil { + return nil, err + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + + bodyBytes, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return nil, err + } + if resp.StatusCode != 200 { + var dest errorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + return nil, errors.New(*dest.Message) + } + + schedule := Schedule{} + if err := json.Unmarshal(bodyBytes, &schedule); err != nil { + return nil, err + } + + return &schedule, err +} + +// Finds a single schedule by its name and returns it. +func (c *ScheduleRestClient) ScheduleByName(vcs, org, project, name string) (*Schedule, error) { + params := &listSchedulesParams{} + for { + resp, err := c.listSchedules(vcs, org, project, params) + if err != nil { + return nil, err + } + for _, schedule := range resp.Items { + if schedule.Name == name { + return &schedule, nil + } + } + if resp.NextPageToken == nil { + return nil, nil + } + params.PageToken = resp.NextPageToken + } +} + +// Fetches all pages of the schedule list API and returns a single +// list with all the schedules. +func (c *ScheduleRestClient) listAllSchedules(vcs, org, project string, params *listSchedulesParams) (schedules []Schedule, err error) { + var resp *listSchedulesResponse + for { + resp, err = c.listSchedules(vcs, org, project, params) + if err != nil { + return nil, err + } + + schedules = append(schedules, resp.Items...) + + if resp.NextPageToken == nil { + break + } + + params.PageToken = resp.NextPageToken + } + return schedules, nil +} + +// Fetches and returns one page of schedules. +func (c *ScheduleRestClient) listSchedules(vcs, org, project string, params *listSchedulesParams) (*listSchedulesResponse, error) { + req, err := c.newListSchedulesRequest(vcs, org, project, params) + if err != nil { + return nil, err + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + + bodyBytes, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return nil, err + } + if resp.StatusCode != 200 { + var dest errorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + + } + return nil, errors.New(*dest.Message) + + } + + dest := listSchedulesResponse{ + client: c, + params: params, + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + return &dest, nil +} + +// Builds a request to fetch a schedule by ID. +func (c *ScheduleRestClient) newGetScheduleRequest(scheduleID string) (*http.Request, error) { + queryURL, err := url.Parse(c.server) + if err != nil { + return nil, err + } + + queryURL, err = queryURL.Parse(fmt.Sprintf("schedule/%s", scheduleID)) + if err != nil { + return nil, err + } + + return c.newHTTPRequest("GET", queryURL.String(), nil) +} + +// Builds a request to create a new schedule. +func (c *ScheduleRestClient) newCreateScheduleRequest(vcs, org, project, name, description string, + useSchedulingSystem bool, timetable Timetable, parameters map[string]string) (*http.Request, error) { + + var err error + queryURL, err := url.Parse(c.server) + if err != nil { + return nil, err + } + queryURL, err = queryURL.Parse(fmt.Sprintf("project/%s/%s/%s/schedule", vcs, org, project)) + if err != nil { + return nil, err + } + + actor := "current" + if useSchedulingSystem { + actor = "system" + } + + var bodyReader io.Reader + + var body = struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + AttributionActor string `json:"attribution-actor"` + Parameters map[string]string `json:"parameters"` + Timetable Timetable `json:"timetable"` + }{ + Name: name, + Description: description, + AttributionActor: actor, + Parameters: parameters, + Timetable: timetable, + } + buf, err := json.Marshal(body) + + if err != nil { + return nil, err + } + + bodyReader = bytes.NewReader(buf) + + return c.newHTTPRequest("POST", queryURL.String(), bodyReader) +} + +// Builds a request to update an existing schedule. +func (c *ScheduleRestClient) newUpdateScheduleRequest(scheduleID, name, description string, + useSchedulingSystem bool, timetable Timetable, parameters map[string]string) (*http.Request, error) { + + var err error + queryURL, err := url.Parse(c.server) + if err != nil { + return nil, err + } + queryURL, err = queryURL.Parse(fmt.Sprintf("schedule/%s", scheduleID)) + if err != nil { + return nil, err + } + + actor := "current" + if useSchedulingSystem { + actor = "system" + } + + var bodyReader io.Reader + + var body = struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + AttributionActor string `json:"attribution-actor,omitempty"` + Parameters map[string]string `json:"parameters,omitempty"` + Timetable Timetable `json:"timetable,omitempty"` + }{ + Name: name, + Description: description, + AttributionActor: actor, + Parameters: parameters, + Timetable: timetable, + } + buf, err := json.Marshal(body) + + if err != nil { + return nil, err + } + + bodyReader = bytes.NewReader(buf) + + return c.newHTTPRequest("PATCH", queryURL.String(), bodyReader) +} + +// Builds a request to delete an existing schedule. +func (c *ScheduleRestClient) newDeleteScheduleRequest(scheduleID string) (*http.Request, error) { + var err error + queryURL, err := url.Parse(c.server) + if err != nil { + return nil, err + } + queryURL, err = queryURL.Parse(fmt.Sprintf("schedule/%s", scheduleID)) + if err != nil { + return nil, err + } + return c.newHTTPRequest("DELETE", queryURL.String(), nil) +} + +// Builds a requeest to list schedules according to params. +func (c *ScheduleRestClient) newListSchedulesRequest(vcs, org, project string, params *listSchedulesParams) (*http.Request, error) { + var err error + queryURL, err := url.Parse(c.server) + if err != nil { + return nil, err + } + queryURL, err = queryURL.Parse(fmt.Sprintf("project/%s/%s/%s/schedule", vcs, org, project)) + if err != nil { + return nil, err + } + + urlParams := url.Values{} + if params.PageToken != nil { + urlParams.Add("page-token", *params.PageToken) + } + + queryURL.RawQuery = urlParams.Encode() + + return c.newHTTPRequest("GET", queryURL.String(), nil) +} + +// Returns a new blank API request with boilerplate headers. +func (c *ScheduleRestClient) newHTTPRequest(method, url string, body io.Reader) (*http.Request, error) { + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, err + } + req.Header.Add("circle-token", c.token) + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + req.Header.Add("User-Agent", version.UserAgent()) + commandStr := header.GetCommandStr() + if commandStr != "" { + req.Header.Add("Circleci-Cli-Command", commandStr) + } + return req, nil +} + +// Verifies that the REST API exists and has the necessary endpoints +// to interact with schedules. +func (c *ScheduleRestClient) EnsureExists() error { + queryURL, err := url.Parse(c.server) + if err != nil { + return err + } + queryURL, err = queryURL.Parse("openapi.json") + if err != nil { + return err + } + req, err := c.newHTTPRequest("GET", queryURL.String(), nil) + if err != nil { + return err + } + + resp, err := c.client.Do(req) + if err != nil { + return err + } + if resp.StatusCode != 200 { + return errors.New("API v2 test request failed.") + } + + bodyBytes, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return err + } + var respBody struct { + Paths struct { + ScheduleEndpoint interface{} `json:"/schedule"` + } + } + if err := json.Unmarshal(bodyBytes, &respBody); err != nil { + return err + } + + if respBody.Paths.ScheduleEndpoint == nil { + return errors.New("No schedule endpoint exists") + } + + return nil +} + +// Returns a new client satisfying the api.ScheduleInterface interface +// via the REST API. +func NewScheduleRestClient(config settings.Config) (*ScheduleRestClient, error) { + // Ensure server ends with a slash + if !strings.HasSuffix(config.RestEndpoint, "/") { + config.RestEndpoint += "/" + } + serverURL, err := url.Parse(config.Host) + if err != nil { + return nil, err + } + + serverURL, err = serverURL.Parse(config.RestEndpoint) + if err != nil { + return nil, err + } + + client := &ScheduleRestClient{ + token: config.Token, + server: serverURL.String(), + client: config.HTTPClient, + } + + return client, nil +} diff --git a/api/schedule_test.go b/api/schedule_test.go new file mode 100644 index 000000000..432595e85 --- /dev/null +++ b/api/schedule_test.go @@ -0,0 +1,206 @@ +package api + +import ( + "encoding/json" + "fmt" + "net/http" + "testing" + + "gotest.tools/v3/assert" + + "github.com/CircleCI-Public/circleci-cli/mock" +) + +// Returns a static mock schedule. +func mockSchedule() Schedule { + return Schedule{ + ID: "07f08dea-de06-48d4-9b47-9639229b7d24", + ProjectSlug: "github/test-org/test-project", + Name: "test-schedule", + Description: "A test schedule", + Timetable: Timetable{ + PerHour: 1, + HoursOfDay: []uint{4, 8, 15, 16, 23}, + DaysOfWeek: []string{"MON", "THU", "SAT"}, + }, + Actor: Actor{ + ID: "807d18e2-a8e2-4b1e-8a54-18da6e0cc478", + Login: "test-actor", + Name: "T. Actor", + }, + Parameters: map[string]string{ + "test": "parameter", + }, + } +} + +// Returns the string representation of the static mock schedule. +func mockScheduleString() string { + schedule := mockSchedule() + rv, err := json.Marshal(schedule) + if err != nil { + panic("Failed to serialise mock schedule") + } + return string(rv) +} + +// Takes a number of schedules and formats them as if they were +// returned as a page of our paginated API. No next page tokens +// included at this point. +func formatListResponse(schedules []Schedule) string { + schedule := mockSchedule() + var resp = struct { + Items []Schedule `json:"items"` + NextPageToken string `json:"next_page_token,omitempty"` + }{ + Items: []Schedule{schedule}, + } + serialized, err := json.Marshal(resp) + if err != nil { + panic("Failed to serialise mock list response") + } + return string(serialized) +} + +func TestSchedules(t *testing.T) { + mockFn := func(r *http.Request) (*http.Response, error) { + if r.URL.String() != "https://circleci.com/api/v2/project/github/test-org/test-project/schedule" { + panic(fmt.Sprintf("unexpected url: %s", r.URL.String())) + } + if r.Method != "GET" { + panic(fmt.Sprintf("unexpected method: %s", r.Method)) + } + return mock.NewHTTPResponse(200, formatListResponse([]Schedule{mockSchedule()})), nil + } + httpClient := mock.NewHTTPClient(mockFn) + restClient := ScheduleRestClient{ + server: "https://circleci.com/api/v2/", + client: httpClient, + } + + t.Run("Get all schedules for a project", func(t *testing.T) { + schedules, err := restClient.Schedules("github", "test-org", "test-project") + assert.NilError(t, err) + assert.DeepEqual(t, mockSchedule(), (*schedules)[0]) + }) +} + +func TestScheduleByID(t *testing.T) { + mockFn := func(r *http.Request) (*http.Response, error) { + if r.URL.String() != "https://circleci.com/api/v2/schedule/07f08dea-de06-48d4-9b47-9639229b7d24" { + panic(fmt.Sprintf("unexpected url: %s", r.URL.String())) + } + if r.Method != "GET" { + panic(fmt.Sprintf("unexpected method: %s", r.Method)) + } + return mock.NewHTTPResponse(200, mockScheduleString()), nil + } + httpClient := mock.NewHTTPClient(mockFn) + restClient := ScheduleRestClient{ + server: "https://circleci.com/api/v2/", + client: httpClient, + } + + t.Run("Get a schedule by ID", func(t *testing.T) { + schedule := mockSchedule() + gotten, err := restClient.ScheduleByID(schedule.ID) + assert.NilError(t, err) + assert.DeepEqual(t, schedule, *gotten) + }) +} + +func TestScheduleByName(t *testing.T) { + mockFn := func(r *http.Request) (*http.Response, error) { + if r.URL.String() != "https://circleci.com/api/v2/project/github/test-org/test-project/schedule" { + panic(fmt.Sprintf("unexpected url: %s", r.URL.String())) + } + if r.Method != "GET" { + panic(fmt.Sprintf("unexpected method: %s", r.Method)) + } + return mock.NewHTTPResponse(200, formatListResponse([]Schedule{mockSchedule()})), nil + } + httpClient := mock.NewHTTPClient(mockFn) + restClient := ScheduleRestClient{ + server: "https://circleci.com/api/v2/", + client: httpClient, + } + + t.Run("Get a schedule by name", func(t *testing.T) { + schedule := mockSchedule() + gotten, err := restClient.ScheduleByName("github", "test-org", "test-project", schedule.Name) + assert.NilError(t, err) + assert.DeepEqual(t, schedule, *gotten) + }) +} + +func TestDeleteSchedule(t *testing.T) { + mockFn := func(r *http.Request) (*http.Response, error) { + if r.URL.String() != "https://circleci.com/api/v2/schedule/07f08dea-de06-48d4-9b47-9639229b7d24" { + panic(fmt.Sprintf("unexpected url: %s", r.URL.String())) + } + if r.Method != "DELETE" { + panic(fmt.Sprintf("unexpected method: %s", r.Method)) + } + return mock.NewHTTPResponse(200, "{\"message\": \"okay\"}"), nil + } + httpClient := mock.NewHTTPClient(mockFn) + restClient := ScheduleRestClient{ + server: "https://circleci.com/api/v2/", + client: httpClient, + } + + t.Run("Delete a schedule", func(t *testing.T) { + err := restClient.DeleteSchedule("07f08dea-de06-48d4-9b47-9639229b7d24") + assert.NilError(t, err) + }) +} + +func TestCreateSchedule(t *testing.T) { + mockFn := func(r *http.Request) (*http.Response, error) { + if r.URL.String() != "https://circleci.com/api/v2/project/github/test-org/test-project/schedule" { + panic(fmt.Sprintf("unexpected url: %s", r.URL.String())) + } + if r.Method != "POST" { + panic(fmt.Sprintf("unexpected method: %s", r.Method)) + } + return mock.NewHTTPResponse(201, mockScheduleString()), nil + } + httpClient := mock.NewHTTPClient(mockFn) + restClient := ScheduleRestClient{ + server: "https://circleci.com/api/v2/", + client: httpClient, + } + + t.Run("Create a schedule", func(t *testing.T) { + schedule := mockSchedule() + created, err := restClient.CreateSchedule("github", "test-org", "test-project", + schedule.Name, schedule.Description, true, schedule.Timetable, schedule.Parameters) + assert.NilError(t, err) + assert.DeepEqual(t, schedule, *created) + }) +} + +func TestUpdateSchedule(t *testing.T) { + mockFn := func(r *http.Request) (*http.Response, error) { + if r.URL.String() != "https://circleci.com/api/v2/schedule/07f08dea-de06-48d4-9b47-9639229b7d24" { + panic(fmt.Sprintf("unexpected url: %s", r.URL.String())) + } + if r.Method != "PATCH" { + panic(fmt.Sprintf("unexpected method: %s", r.Method)) + } + return mock.NewHTTPResponse(200, mockScheduleString()), nil + } + httpClient := mock.NewHTTPClient(mockFn) + restClient := ScheduleRestClient{ + server: "https://circleci.com/api/v2/", + client: httpClient, + } + + t.Run("Update a schedule", func(t *testing.T) { + schedule := mockSchedule() + updated, err := restClient.UpdateSchedule(schedule.ID, schedule.Name, schedule.Description, + false, schedule.Timetable, schedule.Parameters) + assert.NilError(t, err) + assert.DeepEqual(t, schedule, *updated) + }) +} From af626c38c57a014119aad8387e671ae0657b0838 Mon Sep 17 00:00:00 2001 From: corinnesollows Date: Wed, 18 May 2022 11:49:18 -0300 Subject: [PATCH 05/48] Ext 358 pr template (#708) * Test * revert * Adding PR template to CLI * Moved file out of folder as many online sources state that this could be why this file is not working Added to the template with our standard categories --- .github/PULL_REQUEST_TEMPLATE.md | 49 +++++++++++++++++++ .github/PULL_REQUEST_TEMPLATE/PULL_REQUEST.md | 13 ----- 2 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE/PULL_REQUEST.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..df2e28356 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,49 @@ +# Checklist +========= + +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have checked for similar issues and haven't found anything relevant. +- [ ] This is not a security issue (which should be reported here: https://circleci.com/security/) +- [ ] I have read [Contribution Guidelines](https://github.com/CircleCI-Public/circleci-cli/blob/master/CONTRIBUTING.md). + +## Changes +======= + +- Put itemized changes here, preferably in imperative mood, i.e. "Add + `functionA` to `fileB`" + +## Rationale +========= + +What was the overarching product goal of this PR as well as any pertinent +history of changes + +## Considerations +============== + +Why you made some of the technical decisions that you made, especially if the +reasoning is not immediately obvious + +## Analytics Events Added +====================== + +- `event-name` + +## Screenshots +============ +

Before

+Image or [gif](https://giphy.com/apps/giphycapture) + +

After

+Image or gif where change can be clearly seen + +## **Here are some helpful tips you can follow when submitting a pull request:** + +1. Fork [the repository](https://github.com/CircleCI-Public/circleci-cli) and create your branch from `master`. +2. Run `make build` in the repository root. +3. If you've fixed a bug or added code that should be tested, add tests! +4. Ensure the test suite passes (`make test`). +5. The `--debug` flag is often helpful for debugging HTTP client requests and responses. +6. Format your code with [gofmt](https://golang.org/cmd/gofmt/). +7. Make sure your code lints (`make lint`). Note: This requires Docker to run inside a local job. diff --git a/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST.md b/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST.md deleted file mode 100644 index 27d86a315..000000000 --- a/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST.md +++ /dev/null @@ -1,13 +0,0 @@ -- [ ] I have read [Contribution Guidelines](https://github.com/CircleCI-Public/circleci-cli/blob/master/CONTRIBUTING.md). -- [ ] I have checked for similar issues and haven't found anything relevant. -- [ ] This is not a security issue (which should be reported here: https://circleci.com/security/) - -**Here are some helpful tips you can follow when submitting a pull request:** - -1. Fork [the repository](https://github.com/CircleCI-Public/circleci-cli) and create your branch from `master`. -2. Run `make build` in the repository root. -3. If you've fixed a bug or added code that should be tested, add tests! -4. Ensure the test suite passes (`make test`). -5. The `--debug` flag is often helpful for debugging HTTP client requests and responses. -6. Format your code with [gofmt](https://golang.org/cmd/gofmt/). -7. Make sure your code lints (`make lint`). Note: This requires Docker to run inside a local job. From e68e80c6a708926fe7970bdf0a58f1119f4c5b9e Mon Sep 17 00:00:00 2001 From: corinnesollows Date: Wed, 4 May 2022 10:41:04 -0300 Subject: [PATCH 06/48] Added in org id and made vcs and org name optional cont changes Fixed some of those changes up Co-authored-by: corinnesollows Co-authored-by: corinnesollows Added in org id and made vcs and org name optional Co-authored-by: corinnesollows Vendor Add AppendRestPostHandler to clitest to be able to test rest endpoints. Add integration test flag to context to ensure that the rest client is used Tests created Co-authored-by: corinnesollows create context with VCS and org Name tests test Fix tests Add API bindings for the pipeline scheduling REST API This is actually meant to be used in the CircleCI terraform provider, which uses the CLI as a library. As such I'm not adding any CLI-user-visible way of interacting with schedules as part of this. Added comments to changes and added functions Attempting to add some new tests test fixes test var Fix API context tests setup rest unit tests and make one succesful test Add should handle an error request with createContextWithOrgID test --- api/context.go | 12 ++-- api/context_graphql.go | 16 ++++- api/context_rest.go | 72 +++++++++++++++++++++ api/context_rest_test.go | 98 ++++++++++++++++++++++++++++ api/context_test.go | 49 ++++++++++++++ clitest/clitest.go | 24 +++++++ cmd/context.go | 47 ++++++++++++-- cmd/context_test.go | 136 +++++++++++++++++++++++++++++++++------ go.mod | 2 +- go.sum | 4 +- 10 files changed, 421 insertions(+), 39 deletions(-) create mode 100644 api/context_rest_test.go diff --git a/api/context.go b/api/context.go index c97572186..3921e54cc 100644 --- a/api/context.go +++ b/api/context.go @@ -7,16 +7,16 @@ import ( // An EnvironmentVariable has a Variable, a ContextID (its owner), and a // CreatedAt date. type EnvironmentVariable struct { - Variable string + Variable string ContextID string CreatedAt time.Time } // A Context is the owner of EnvironmentVariables. -type Context struct{ +type Context struct { CreatedAt time.Time `json:"created_at"` - ID string `json:"id"` - Name string `json:"name"` + ID string `json:"id"` + Name string `json:"name"` } // ContextInterface is the interface to interact with contexts and environment @@ -25,8 +25,8 @@ type ContextInterface interface { Contexts(vcs, org string) (*[]Context, error) ContextByName(vcs, org, name string) (*Context, error) DeleteContext(contextID string) error - CreateContext(vcs, org, name string) (error) - + CreateContext(vcs, org, name string) error + CreateContextWithOrgID(orgID *string, name string) error EnvironmentVariables(contextID string) (*[]EnvironmentVariable, error) CreateEnvironmentVariable(contextID, variable, value string) error DeleteEnvironmentVariable(contextID, variable string) error diff --git a/api/context_graphql.go b/api/context_graphql.go index 1b6a07aa8..48d13c94b 100644 --- a/api/context_graphql.go +++ b/api/context_graphql.go @@ -52,11 +52,22 @@ func (c *GraphQLContextClient) CreateContext(vcsType, orgName, contextName strin cl := c.Client org, err := getOrganization(cl, orgName, vcsType) + if err != nil { + return err + } + err = c.CreateContextWithOrgID(&org.Organization.ID, contextName) if err != nil { return err } + return nil +} + +// CreateContextWithOrgID creates a new Context in the supplied organization. +func (c *GraphQLContextClient) CreateContextWithOrgID(orgID *string, contextName string) error { + cl := c.Client + query := ` mutation CreateContext($input: CreateContextInput!) { createContext(input: $input) { @@ -78,7 +89,7 @@ func (c *GraphQLContextClient) CreateContext(vcsType, orgName, contextName strin ContextName string `json:"contextName"` } - input.OwnerId = org.Organization.ID + input.OwnerId = *orgID input.OwnerType = "ORGANIZATION" input.ContextName = contextName @@ -94,14 +105,13 @@ func (c *GraphQLContextClient) CreateContext(vcsType, orgName, contextName strin } } - if err = cl.Run(request, &response); err != nil { + if err := cl.Run(request, &response); err != nil { return improveVcsTypeError(err) } if response.CreateContext.Error.Type != "" { return fmt.Errorf("Error creating context: %s", response.CreateContext.Error.Type) } - return nil } diff --git a/api/context_rest.go b/api/context_rest.go index afae72d5e..7b942faf6 100644 --- a/api/context_rest.go +++ b/api/context_rest.go @@ -88,6 +88,37 @@ func (c *ContextRestClient) DeleteEnvironmentVariable(contextID, variable string return nil } +func (c *ContextRestClient) CreateContextWithOrgID(orgID *string, name string) error { + req, err := c.newCreateContextRequestWithOrgID(orgID, name) + if err != nil { + return err + } + + resp, err := c.client.Do(req) + + if err != nil { + return err + } + + bodyBytes, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return err + } + if resp.StatusCode != 200 { + var dest errorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return err + } + return errors.New(*dest.Message) + } + var dest Context + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return err + } + return nil +} + // CreateContext creates a new context in the supplied organization. func (c *ContextRestClient) CreateContext(vcs, org, name string) error { req, err := c.newCreateContextRequest(vcs, org, name) @@ -329,6 +360,7 @@ func (c *ContextRestClient) listContexts(params *listContextsParams) (*listConte return &dest, nil } +//newCreateContextRequest posts a new context creation with orgname and vcs type using a slug func (c *ContextRestClient) newCreateContextRequest(vcs, org, name string) (*http.Request, error) { var err error queryURL, err := url.Parse(c.server) @@ -352,6 +384,7 @@ func (c *ContextRestClient) newCreateContextRequest(vcs, org, name string) (*htt Owner: struct { Slug *string `json:"slug,omitempty"` }{ + Slug: toSlug(vcs, org), }, } @@ -366,6 +399,45 @@ func (c *ContextRestClient) newCreateContextRequest(vcs, org, name string) (*htt return c.newHTTPRequest("POST", queryURL.String(), bodyReader) } +//newCreateContextRequestWithOrgID posts a new context creation with an orgID +func (c *ContextRestClient) newCreateContextRequestWithOrgID(orgID *string, name string) (*http.Request, error) { + var err error + queryURL, err := url.Parse(c.server) + if err != nil { + return nil, err + } + queryURL, err = queryURL.Parse("context") + if err != nil { + return nil, err + } + + var bodyReader io.Reader + + var body = struct { + Name string `json:"name"` + Owner struct { + ID *string `json:"id,omitempty"` + } `json:"owner"` + }{ + Name: name, + Owner: struct { + ID *string `json:"id,omitempty"` + }{ + + ID: orgID, + }, + } + buf, err := json.Marshal(body) + + if err != nil { + return nil, err + } + + bodyReader = bytes.NewReader(buf) + + return c.newHTTPRequest("POST", queryURL.String(), bodyReader) +} + func (c *ContextRestClient) newCreateEnvironmentVariableRequest(contextID, variable, value string) (*http.Request, error) { var err error queryURL, err := url.Parse(c.server) diff --git a/api/context_rest_test.go b/api/context_rest_test.go new file mode 100644 index 000000000..1c969721d --- /dev/null +++ b/api/context_rest_test.go @@ -0,0 +1,98 @@ +package api + +import ( + "fmt" + "io/ioutil" + "net/http" + + "github.com/CircleCI-Public/circleci-cli/settings" + "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/ghttp" +) + +type MockRequestResponse struct { + Request string + Status int + Response string + ErrorResponse string +} + +// Uses Ginkgo http handler to mock out http requests and make assertions off the results. +// If ErrorResponse is defined in the passed handler it will override the Response. +func appendRESTPostHandler(server *ghttp.Server, combineHandlers ...MockRequestResponse) { + for _, handler := range combineHandlers { + responseBody := handler.Response + if handler.ErrorResponse != "" { + responseBody = handler.ErrorResponse + } + + server.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest("POST", "/api/v2/context"), + ghttp.VerifyContentType("application/json"), + func(w http.ResponseWriter, req *http.Request) { + body, err := ioutil.ReadAll(req.Body) + Expect(err).ShouldNot(HaveOccurred()) + err = req.Body.Close() + Expect(err).ShouldNot(HaveOccurred()) + Expect(handler.Request).Should(MatchJSON(body), "JSON Mismatch") + }, + ghttp.RespondWith(handler.Status, responseBody), + ), + ) + } +} + +func getContextRestClient(server *ghttp.Server) (*ContextRestClient, error) { + client := &http.Client{} + + return NewContextRestClient(settings.Config{ + RestEndpoint: "api/v2", + Host: server.URL(), + HTTPClient: client, + Token: "token", + }) +} + +var _ = ginkgo.Describe("Context Rest Tests", func() { + ginkgo.It("Should handle a successful request with createContextWithOrgID", func() { + server := ghttp.NewServer() + + defer server.Close() + + name := "name" + orgID := "497f6eca-6276-4993-bfeb-53cbbbba6f08" + client, err := getContextRestClient(server) + Expect(err).To(BeNil()) + + appendRESTPostHandler(server, MockRequestResponse{ + Status: http.StatusOK, + Request: fmt.Sprintf(`{"name": "%s","owner":{"id":"%s"}}`, name, orgID), + Response: fmt.Sprintf(`{"id": "%s", "name": "%s", "created_at": "2015-09-21T17:29:21.042Z" }`, orgID, name), + }) + + err = client.CreateContextWithOrgID(&orgID, name) + Expect(err).To(BeNil()) + }) + + ginkgo.It("Should handle an error request with createContextWithOrgID", func() { + server := ghttp.NewServer() + + defer server.Close() + + name := "name" + orgID := "497f6eca-6276-4993-bfeb-53cbbbba6f08" + client, err := getContextRestClient(server) + Expect(err).To(BeNil()) + + appendRESTPostHandler(server, MockRequestResponse{ + Status: http.StatusInternalServerError, + Request: fmt.Sprintf(`{"name": "%s","owner":{"id":"%s"}}`, name, orgID), + ErrorResponse: `{"message": "🍎"}`, + }) + + err = client.CreateContextWithOrgID(&orgID, name) + Expect(err).ToNot(BeNil()) + }) +}) diff --git a/api/context_test.go b/api/context_test.go index 607acdfe3..980a18b8e 100644 --- a/api/context_test.go +++ b/api/context_test.go @@ -42,6 +42,8 @@ func createSingleUseGraphQLServer(result interface{}, requestAssertions func(req } var _ = ginkgo.Describe("API", func() { + orgID := "bb604b45-b6b0-4b81-ad80-796f15eddf87" + ginkgo.Describe("FooBar", func() { ginkgo.It("improveVcsTypeError", func() { @@ -94,6 +96,30 @@ var _ = ginkgo.Describe("API", func() { }) + ginkgo.It("can handles failure creating contexts", func() { + + var result struct { + CreateContext struct { + Error struct { + Type string + } + } + } + + result.CreateContext.Error.Type = "force-this-error" + + server, client := createSingleUseGraphQLServer(result, func(count uint64, req *graphQLRequest) { + switch count { + case 1: + Expect(req.Variables["input"].(map[string]interface{})["ownerId"]).To(Equal(orgID)) + } + }) + defer server.Close() + err := client.CreateContextWithOrgID(&orgID, "foo-bar") + Expect(err).To(MatchError("Error creating context: force-this-error")) + + }) + }) ginkgo.It("can handles success creating contexts", func() { @@ -126,6 +152,29 @@ var _ = ginkgo.Describe("API", func() { }) + ginkgo.It("can handles success creating contexts with create context with orgID", func() { + + var result struct { + CreateContext struct { + Error struct { + Type string + } + } + } + + result.CreateContext.Error.Type = "" + + server, client := createSingleUseGraphQLServer(result, func(count uint64, req *graphQLRequest) { + switch count { + case 1: + Expect(req.Variables["input"].(map[string]interface{})["ownerId"]).To(Equal(orgID)) + } + }) + defer server.Close() + Expect(client.CreateContextWithOrgID(&orgID, "foo-bar")).To(Succeed()) + + }) + ginkgo.Describe("List Contexts", func() { ginkgo.It("can list contexts", func() { diff --git a/clitest/clitest.go b/clitest/clitest.go index 19e1c4702..e6ae4de59 100644 --- a/clitest/clitest.go +++ b/clitest/clitest.go @@ -89,6 +89,30 @@ type MockRequestResponse struct { ErrorResponse string } +func (tempSettings *TempSettings) AppendRESTPostHandler(combineHandlers ...MockRequestResponse) { + for _, handler := range combineHandlers { + responseBody := handler.Response + if handler.ErrorResponse != "" { + responseBody = handler.ErrorResponse + } + + tempSettings.TestServer.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest("POST", "/api/v2/context"), + ghttp.VerifyContentType("application/json"), + func(w http.ResponseWriter, req *http.Request) { + body, err := ioutil.ReadAll(req.Body) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + err = req.Body.Close() + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(handler.Request).Should(gomega.MatchJSON(body), "JSON Mismatch") + }, + ghttp.RespondWith(handler.Status, responseBody), + ), + ) + } +} + // AppendPostHandler stubs out the provided MockRequestResponse. // When authToken is an empty string no token validation is performed. func (tempSettings *TempSettings) AppendPostHandler(authToken string, combineHandlers ...MockRequestResponse) { diff --git a/cmd/context.go b/cmd/context.go index 1e0efb014..67f8b1aae 100644 --- a/cmd/context.go +++ b/cmd/context.go @@ -9,6 +9,7 @@ import ( "time" "github.com/CircleCI-Public/circleci-cli/api" + "github.com/google/uuid" "github.com/olekukonko/tablewriter" "github.com/pkg/errors" @@ -16,6 +17,11 @@ import ( "github.com/spf13/cobra" ) +var ( + orgID *string + integrationTesting bool +) + func newContextCommand(config *settings.Config) *cobra.Command { var contextClient api.ContextInterface @@ -25,6 +31,11 @@ func newContextCommand(config *settings.Config) *cobra.Command { return e } + // Ensure does not fallback to graph for testing. + if integrationTesting { + return validateToken(config) + } + // If we're on cloud, we're good. if config.Host == defaultHost || contextClient.(*api.ContextRestClient).EnsureExists() == nil { return validateToken(config) @@ -82,13 +93,18 @@ func newContextCommand(config *settings.Config) *cobra.Command { createContextCommand := &cobra.Command{ Short: "Create a new context", - Use: "create ", + Use: "create [] [] ", PreRunE: initClient, RunE: func(cmd *cobra.Command, args []string) error { - return createContext(contextClient, args[0], args[1], args[2]) + return createContext(cmd, contextClient, args) }, - Args: cobra.ExactArgs(3), + Args: cobra.RangeArgs(1, 3), + Annotations: make(map[string]string), + Example: ` circleci context create github OrgName contextName +circleci context create contextName --org-id "your-org-id-here"`, } + createContextCommand.Annotations["[]"] = `Your VCS provider, can be either "github" or "bitbucket". Optional when passing org-id flag.` + createContextCommand.Annotations["[]"] = `The name used for your organization. Optional when passing org-id flag.` force := false deleteContextCommand := &cobra.Command{ @@ -103,6 +119,12 @@ func newContextCommand(config *settings.Config) *cobra.Command { deleteContextCommand.Flags().BoolVarP(&force, "force", "f", false, "Delete the context without asking for confirmation.") + orgID = createContextCommand.Flags().String("org-id", "", "The id of your organization.") + createContextCommand.Flags().BoolVar(&integrationTesting, "integration-testing", false, "Enable test mode to setup rest API") + if err := createContextCommand.Flags().MarkHidden("integration-testing"); err != nil { + panic(err) + } + command.AddCommand(listCommand) command.AddCommand(showContextCommand) command.AddCommand(storeCommand) @@ -174,9 +196,22 @@ func readSecretValue() (string, error) { } } -func createContext(client api.ContextInterface, vcsType, orgName, contextName string) error { - err := client.CreateContext(vcsType, orgName, contextName) - return err +//createContext determines if the context is being created via orgid or vcs and org name +//and navigates to corresponding function accordingly +func createContext(cmd *cobra.Command, client api.ContextInterface, args []string) error { + //skip if no orgid provided + if orgID != nil && strings.TrimSpace(*orgID) != "" && len(args) == 1 { + _, err := uuid.Parse(*orgID) + + if err == nil { + return client.CreateContextWithOrgID(orgID, args[0]) + } + + //skip if no vcs type and org name provided + } else if len(args) == 3 { + return client.CreateContext(args[0], args[1], args[2]) + } + return cmd.Help() } func removeEnvVar(client api.ContextInterface, vcsType, orgName, contextName, varName string) error { diff --git a/cmd/context_test.go b/cmd/context_test.go index 934b8b42c..619ed2089 100644 --- a/cmd/context_test.go +++ b/cmd/context_test.go @@ -1,6 +1,8 @@ package cmd_test import ( + "fmt" + "net/http" "os/exec" "github.com/CircleCI-Public/circleci-cli/clitest" @@ -11,32 +13,124 @@ import ( ) var _ = Describe("Context integration tests", func() { - Describe("when listing contexts without a token", func() { - var ( - command *exec.Cmd - tempSettings *clitest.TempSettings - ) - - BeforeEach(func() { - tempSettings = clitest.WithTempSettings() - command = commandWithHome(pathCLI, tempSettings.Home, - "context", "list", "github", "foo", - "--skip-update-check", - "--token", "", - ) - }) + var ( + tempSettings *clitest.TempSettings + token string = "testtoken" + command *exec.Cmd + contextName string = "foo-context" + orgID string = "bb604b45-b6b0-4b81-ad80-796f15eddf87" + vcsType string = "BITBUCKET" + orgName string = "test-org" + ) + + BeforeEach(func() { + tempSettings = clitest.WithTempSettings() + }) + + AfterEach(func() { + tempSettings.Close() + }) - It("instructs the user to run 'circleci setup' and create a new token", func() { - By("running the command") - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + Context("create, with interactive prompts", func() { - Expect(err).ShouldNot(HaveOccurred()) - Eventually(session.Err).Should(gbytes.Say(`Error: please set a token with 'circleci setup' + Describe("when listing contexts without a token", func() { + BeforeEach(func() { + command = commandWithHome(pathCLI, tempSettings.Home, + "context", "list", "github", "foo", + "--skip-update-check", + "--token", "", + ) + }) + + It("instructs the user to run 'circleci setup' and create a new token", func() { + By("running the command") + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session.Err).Should(gbytes.Say(`Error: please set a token with 'circleci setup' You can create a new personal API token here: https://circleci.com/account/api`)) - Eventually(session).Should(clitest.ShouldFail()) + Eventually(session).Should(clitest.ShouldFail()) + }) }) }) - // TODO: add integration tests for happy path cases + Context("create, with interactive prompts", func() { + //tests context creation via orgid + Describe("using an org id to create a context", func() { + + BeforeEach(func() { + command = commandWithHome(pathCLI, tempSettings.Home, + "context", "create", + "--skip-update-check", + "--token", token, + "--host", tempSettings.TestServer.URL(), + "--integration-testing", + contextName, + "--org-id", fmt.Sprintf(`"%s"`, orgID), + ) + }) + + It("should create new context using an org id", func() { + By("setting up a mock server") + tempSettings.AppendRESTPostHandler(clitest.MockRequestResponse{ + Status: http.StatusOK, + Request: fmt.Sprintf(`{"name": "%s","owner":{"id":"\"%s\""}}`, contextName, orgID), + Response: fmt.Sprintf(`{"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "%s", "created_at": "2015-09-21T17:29:21.042Z" }`, contextName), + }) + + By("running the command") + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session).Should(gexec.Exit(0)) + }) + }) + //tests context creation via orgname and vcs type + Describe("using an vcs and org name to create a context", func() { + BeforeEach(func() { + command = exec.Command(pathCLI, + "context", "create", + "--skip-update-check", + "--token", token, + "--host", tempSettings.TestServer.URL(), + "--integration-testing", + vcsType, + orgName, + contextName, + ) + }) + + It("user creating new context", func() { + By("setting up a mock server") + + tempSettings.AppendRESTPostHandler(clitest.MockRequestResponse{ + Status: http.StatusOK, + Request: fmt.Sprintf(`{"name": "%s","owner":{"slug":"%s"}}`, contextName, vcsType+"/"+orgName), + Response: fmt.Sprintf(`{"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "%s", "created_at": "2015-09-21T17:29:21.042Z" }`, contextName), + }) + + By("running the command") + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session).Should(gexec.Exit(0)) + }) + + It("prints all in-band errors returned by the API", func() { + By("setting up a mock server") + tempSettings.AppendRESTPostHandler(clitest.MockRequestResponse{ + Status: http.StatusInternalServerError, + Request: fmt.Sprintf(`{"name": "%s","owner":{"slug":"%s"}}`, contextName, vcsType+"/"+orgName), + Response: fmt.Sprintf(`{"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "%s", "created_at": "2015-09-21T17:29:21.042Z" }`, contextName), + ErrorResponse: `{ "message": "ignored error" }`, + }) + By("running the command") + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session.Err).Should(gbytes.Say(`Error: ignored error`)) + Eventually(session).ShouldNot(gexec.Exit(0)) + }) + }) + }) }) diff --git a/go.mod b/go.mod index 405fbe22e..ff2b46be5 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( github.com/xanzy/ssh-agent v0.2.1 // indirect golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb // indirect - golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect + golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba // indirect golang.org/x/text v0.3.3 // indirect golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect google.golang.org/protobuf v1.23.0 // indirect diff --git a/go.sum b/go.sum index 9183d84a8..8bdb71f34 100644 --- a/go.sum +++ b/go.sum @@ -177,8 +177,8 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba h1:AyHWHCBVlIYI5rgEM3o+1PLd0sLPcIAoaUckGQMaWtw= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= From 2922e6c590bf57087cc2256a2d39d25476b7aeb4 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Wed, 18 May 2022 12:58:29 -0400 Subject: [PATCH 07/48] remove "analytics events added" from PR template Also some markdown auto-formatting changes that got applied. --- .github/PULL_REQUEST_TEMPLATE.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index df2e28356..7d4cba85f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,5 @@ # Checklist + ========= - [ ] I have performed a self-review of my own code @@ -8,30 +9,30 @@ - [ ] I have read [Contribution Guidelines](https://github.com/CircleCI-Public/circleci-cli/blob/master/CONTRIBUTING.md). ## Changes + ======= - Put itemized changes here, preferably in imperative mood, i.e. "Add `functionA` to `fileB`" ## Rationale + ========= What was the overarching product goal of this PR as well as any pertinent history of changes ## Considerations + ============== Why you made some of the technical decisions that you made, especially if the reasoning is not immediately obvious -## Analytics Events Added -====================== - -- `event-name` - ## Screenshots + ============ +

Before

Image or [gif](https://giphy.com/apps/giphycapture) From c3ad28b4f0beb3954152ccb77faa4bc92a26ce9d Mon Sep 17 00:00:00 2001 From: Ruben Vicario Gonzalez Date: Thu, 26 May 2022 14:27:45 +0200 Subject: [PATCH 08/48] Update CODEOWNERS (#714) --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 04772c924..7509d0655 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ -* @CircleCI-Public/EcoSystem -*orb*.go @CircleCI-Public/CPEng @CircleCI-Public/EcoSystem \ No newline at end of file +* @CircleCI-Public/Extensibility +*orb*.go @CircleCI-Public/CPEng @CircleCI-Public/Extensibility From e96ff599fd236c5ec0e81a77649a3bf4a9ab8849 Mon Sep 17 00:00:00 2001 From: corinnesollows Date: Mon, 6 Jun 2022 16:16:39 -0300 Subject: [PATCH 09/48] Ext 283 remove legacy org slug (#717) * Org UUID support * Made two methods * Test for OrgId * Comments --- api/api.go | 68 ++++++++++++++++++++++++++++++++++++++++++---- cmd/build.go | 1 + cmd/config.go | 43 ++++++++++++++++++++++------- cmd/config_test.go | 62 ++++++++++++++++++++++++++++++++++++++++++ local/local.go | 25 +++++++++++++---- 5 files changed, 178 insertions(+), 21 deletions(-) diff --git a/api/api.go b/api/api.go index 313b834c9..674ae112c 100644 --- a/api/api.go +++ b/api/api.go @@ -513,16 +513,14 @@ func WhoamiQuery(cl *graphql.Client) (*WhoamiResponse, error) { return &response, nil } -// ConfigQuery calls the GQL API to validate and process config -func ConfigQuery(cl *graphql.Client, configPath string, orgSlug string, params pipeline.Parameters, values pipeline.Values) (*ConfigResponse, error) { +// ConfigQueryLegacy calls the GQL API to validate and process config with the legacy orgSlug +func ConfigQueryLegacy(cl *graphql.Client, configPath string, orgSlug string, params pipeline.Parameters, values pipeline.Values) (*ConfigResponse, error) { var response BuildConfigResponse var query string - config, err := loadYaml(configPath) if err != nil { return nil, err } - // GraphQL isn't forwards-compatible, so we are unusually selective here about // passing only non-empty fields on to the API, to minimize user impact if the // backend is out of date. @@ -546,6 +544,7 @@ func ConfigQuery(cl *graphql.Client, configPath string, orgSlug string, params p request := graphql.NewRequest(query) request.Var("config", config) + if values != nil { request.Var("pipelineValues", pipeline.PrepareForGraphQL(values)) } @@ -556,17 +555,76 @@ func ConfigQuery(cl *graphql.Client, configPath string, orgSlug string, params p } request.Var("pipelineParametersJson", string(pipelineParameters)) } + if orgSlug != "" { request.Var("orgSlug", orgSlug) } + request.SetToken(cl.Token) err = cl.Run(request, &response) - if err != nil { return nil, errors.Wrap(err, "Unable to validate config") } + if len(response.BuildConfig.ConfigResponse.Errors) > 0 { + return nil, &response.BuildConfig.ConfigResponse.Errors + } + + return &response.BuildConfig.ConfigResponse, nil +} +// ConfigQuery calls the GQL API to validate and process config with the org id +func ConfigQuery(cl *graphql.Client, configPath string, orgId string, params pipeline.Parameters, values pipeline.Values) (*ConfigResponse, error) { + var response BuildConfigResponse + var query string + config, err := loadYaml(configPath) + if err != nil { + return nil, err + } + // GraphQL isn't forwards-compatible, so we are unusually selective here about + // passing only non-empty fields on to the API, to minimize user impact if the + // backend is out of date. + var fieldAddendums string + if orgId != "" { + fieldAddendums += ", orgId: $orgId" + } + if len(params) > 0 { + fieldAddendums += ", pipelineParametersJson: $pipelineParametersJson" + } + query = fmt.Sprintf( + `query ValidateConfig ($config: String!, $pipelineParametersJson: String, $pipelineValues: [StringKeyVal!], $orgId: UUID!) { + buildConfig(configYaml: $config, pipelineValues: $pipelineValues%s) { + valid, + errors { message }, + sourceYaml, + outputYaml + } + }`, + fieldAddendums) + + request := graphql.NewRequest(query) + request.Var("config", config) + + if values != nil { + request.Var("pipelineValues", pipeline.PrepareForGraphQL(values)) + } + if params != nil { + pipelineParameters, err := json.Marshal(params) + if err != nil { + return nil, fmt.Errorf("unable to serialize pipeline values: %s", err.Error()) + } + request.Var("pipelineParametersJson", string(pipelineParameters)) + } + + if orgId != "" { + request.Var("orgId", orgId) + } + request.SetToken(cl.Token) + + err = cl.Run(request, &response) + if err != nil { + return nil, errors.Wrap(err, "Unable to validate config") + } if len(response.BuildConfig.ConfigResponse.Errors) > 0 { return nil, &response.BuildConfig.ConfigResponse.Errors } diff --git a/cmd/build.go b/cmd/build.go index 96fa77510..002e9682f 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -17,6 +17,7 @@ func newLocalExecuteCommand(config *settings.Config) *cobra.Command { local.AddFlagsForDocumentation(buildCommand.Flags()) buildCommand.Flags().StringP("org-slug", "o", "", "organization slug (for example: github/example-org), used when a config depends on private orbs belonging to that org") + buildCommand.Flags().String("org-id", "", "organization id, used when a config depends on private orbs belonging to that org") return buildCommand } diff --git a/cmd/config.go b/cmd/config.go index b293ff787..be7523c84 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "io/ioutil" + "strings" "github.com/CircleCI-Public/circleci-cli/api" "github.com/CircleCI-Public/circleci-cli/api/graphql" @@ -77,6 +78,7 @@ func newConfigCommand(config *settings.Config) *cobra.Command { panic(err) } validateCommand.Flags().StringP("org-slug", "o", "", "organization slug (for example: github/example-org), used when a config depends on private orbs belonging to that org") + validateCommand.Flags().String("org-id", "", "organization id used when a config depends on private orbs belonging to that org") processCommand := &cobra.Command{ Use: "process ", @@ -93,6 +95,7 @@ func newConfigCommand(config *settings.Config) *cobra.Command { } processCommand.Annotations[""] = configAnnotations[""] processCommand.Flags().StringP("org-slug", "o", "", "organization slug (for example: github/example-org), used when a config depends on private orbs belonging to that org") + processCommand.Flags().String("org-id", "", "organization id used when a config depends on private orbs belonging to that org") processCommand.Flags().StringP("pipeline-parameters", "", "", "YAML/JSON map of pipeline parameters, accepts either YAML/JSON directly or file path (for example: my-params.yml)") migrateCommand := &cobra.Command{ @@ -121,6 +124,8 @@ func newConfigCommand(config *settings.Config) *cobra.Command { // The arg is actually optional, in order to support compatibility with the --path flag. func validateConfig(opts configOptions, flags *pflag.FlagSet) error { + var err error + var response *api.ConfigResponse path := local.DefaultConfigPath // First, set the path to configPath set by --path flag for compatibility if configPath != "" { @@ -132,11 +137,19 @@ func validateConfig(opts configOptions, flags *pflag.FlagSet) error { path = opts.args[0] } - orgSlug, _ := flags.GetString("org-slug") - - response, err := api.ConfigQuery(opts.cl, path, orgSlug, nil, pipeline.LocalPipelineValues()) - if err != nil { - return err + //if no orgId provided use org slug + orgID, _ := flags.GetString("org-id") + if strings.TrimSpace(orgID) != "" { + response, err = api.ConfigQuery(opts.cl, path, orgID, nil, pipeline.LocalPipelineValues()) + if err != nil { + return err + } + } else { + orgSlug, _ := flags.GetString("org-slug") + response, err = api.ConfigQueryLegacy(opts.cl, path, orgSlug, nil, pipeline.LocalPipelineValues()) + if err != nil { + return err + } } // check if a deprecated Linux VM image is being used @@ -159,10 +172,10 @@ func validateConfig(opts configOptions, flags *pflag.FlagSet) error { } func processConfig(opts configOptions, flags *pflag.FlagSet) error { - orgSlug, _ := flags.GetString("org-slug") paramsYaml, _ := flags.GetString("pipeline-parameters") - + var response *api.ConfigResponse var params pipeline.Parameters + var err error if len(paramsYaml) > 0 { // The 'src' value can be a filepath, or a yaml string. If the file cannot be read sucessfully, @@ -178,9 +191,19 @@ func processConfig(opts configOptions, flags *pflag.FlagSet) error { } } - response, err := api.ConfigQuery(opts.cl, opts.args[0], orgSlug, params, pipeline.LocalPipelineValues()) - if err != nil { - return err + //if no orgId provided use org slug + orgID, _ := flags.GetString("org-id") + if strings.TrimSpace(orgID) != "" { + response, err = api.ConfigQuery(opts.cl, opts.args[0], orgID, params, pipeline.LocalPipelineValues()) + if err != nil { + return err + } + } else { + orgSlug, _ := flags.GetString("org-slug") + response, err = api.ConfigQueryLegacy(opts.cl, opts.args[0], orgSlug, params, pipeline.LocalPipelineValues()) + if err != nil { + return err + } } fmt.Print(response.OutputYaml) diff --git a/cmd/config_test.go b/cmd/config_test.go index 7363145c4..e03177f8d 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -287,6 +287,68 @@ var _ = Describe("Config", func() { }) Describe("validating configs with private orbs", func() { + config := "version: 2.1" + orgId := "bb604b45-b6b0-4b81-ad80-796f15eddf87" + var expReq string + + BeforeEach(func() { + command = exec.Command(pathCLI, + "config", "validate", + "--skip-update-check", + "--token", token, + "--host", tempSettings.TestServer.URL(), + "--org-id", orgId, + "-", + ) + + stdin, err := command.StdinPipe() + Expect(err).ToNot(HaveOccurred()) + _, err = io.WriteString(stdin, config) + Expect(err).ToNot(HaveOccurred()) + stdin.Close() + + query := `query ValidateConfig ($config: String!, $pipelineParametersJson: String, $pipelineValues: [StringKeyVal!], $orgId: UUID!) { + buildConfig(configYaml: $config, pipelineValues: $pipelineValues, orgId: $orgId) { + valid, + errors { message }, + sourceYaml, + outputYaml + } + }` + + r := graphql.NewRequest(query) + r.Variables["config"] = config + r.Variables["orgId"] = orgId + r.Variables["pipelineValues"] = pipeline.PrepareForGraphQL(pipeline.LocalPipelineValues()) + + req, err := r.Encode() + Expect(err).ShouldNot(HaveOccurred()) + expReq = req.String() + }) + + It("returns an error when validating a config with a private orb", func() { + expResp := `{ + "buildConfig": { + "errors": [ + {"message": "permission denied"} + ] + } + }` + + tempSettings.AppendPostHandler(token, clitest.MockRequestResponse{ + Status: http.StatusOK, + Request: expReq, + Response: expResp, + }) + + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + Expect(err).ShouldNot(HaveOccurred()) + Eventually(session.Err, time.Second*3).Should(gbytes.Say("Error: permission denied")) + Eventually(session).Should(clitest.ShouldFail()) + }) + }) + + Describe("validating configs with private orbs Legacy", func() { config := "version: 2.1" orgSlug := "circleci" var expReq string diff --git a/local/local.go b/local/local.go index 4fda8b773..e139be8f4 100644 --- a/local/local.go +++ b/local/local.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "regexp" + "strings" "syscall" "github.com/CircleCI-Public/circleci-cli/api" @@ -22,13 +23,25 @@ var picardRepo = "circleci/picard" const DefaultConfigPath = ".circleci/config.yml" func Execute(flags *pflag.FlagSet, cfg *settings.Config) error { - processedArgs, configPath := buildAgentArguments(flags) - orgSlug, _ := flags.GetString("org-slug") + var err error + var configResponse *api.ConfigResponse cl := graphql.NewClient(cfg.HTTPClient, cfg.Host, cfg.Endpoint, cfg.Token, cfg.Debug) - configResponse, err := api.ConfigQuery(cl, configPath, orgSlug, nil, pipeline.LocalPipelineValues()) - if err != nil { - return err + processedArgs, configPath := buildAgentArguments(flags) + + //if no orgId provided use org slug + orgID, _ := flags.GetString("org-id") + if strings.TrimSpace(orgID) != "" { + configResponse, err = api.ConfigQuery(cl, configPath, orgID, nil, pipeline.LocalPipelineValues()) + if err != nil { + return err + } + } else { + orgSlug, _ := flags.GetString("org-slug") + configResponse, err = api.ConfigQueryLegacy(cl, configPath, orgSlug, nil, pipeline.LocalPipelineValues()) + if err != nil { + return err + } } if !configResponse.Valid { @@ -118,7 +131,7 @@ func buildAgentArguments(flags *pflag.FlagSet) ([]string, string) { // build a list of all supplied flags, that we will pass on to build-agent flags.Visit(func(flag *pflag.Flag) { - if flag.Name != "org-slug" && flag.Name != "config" && flag.Name != "debug" { + if flag.Name != "org-slug" && flag.Name != "config" && flag.Name != "debug" && flag.Name != "org-id" { result = append(result, unparseFlag(flags, flag)...) } }) From bf986a08b92893a6318b65e59c9463cb41be3a02 Mon Sep 17 00:00:00 2001 From: Ruben Vicario Gonzalez Date: Tue, 7 Jun 2022 12:51:01 +0200 Subject: [PATCH 10/48] Increase http client timeout to 60s --- settings/settings.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings/settings.go b/settings/settings.go index 936376cbe..330f15c38 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -223,7 +223,7 @@ func (cfg *Config) WithHTTPClient() error { } cfg.HTTPClient = &http.Client{ - Timeout: 30 * time.Second, + Timeout: 60 * time.Second, Transport: &http.Transport{ ExpectContinueTimeout: 1 * time.Second, IdleConnTimeout: 90 * time.Second, From 479631bf822bb061e2efc43f7aeefa7e899fbbd8 Mon Sep 17 00:00:00 2001 From: Sagar Gupta Date: Tue, 7 Jun 2022 10:27:03 -0400 Subject: [PATCH 11/48] Added `policy` command (#731) --- .gitignore | 1 + api/policy/policy.go | 249 ++++++++ api/policy/policy_test.go | 539 ++++++++++++++++++ cmd/policy/policy.go | 242 ++++++++ cmd/policy/policy_test.go | 522 +++++++++++++++++ cmd/policy/testdata/policy-expected-usage.txt | 15 + .../testdata/policy/create-expected-usage.txt | 14 + .../testdata/policy/delete-expected-usage.txt | 9 + .../testdata/policy/get-expected-usage.txt | 9 + .../testdata/policy/list-expected-usage.txt | 12 + .../testdata/policy/update-expected-usage.txt | 15 + cmd/policy/testdata/test.rego | 1 + cmd/policy/usage_test.go | 28 + cmd/root.go | 2 + cmd/root_test.go | 7 +- 15 files changed, 1662 insertions(+), 3 deletions(-) create mode 100644 api/policy/policy.go create mode 100644 api/policy/policy_test.go create mode 100644 cmd/policy/policy.go create mode 100644 cmd/policy/policy_test.go create mode 100644 cmd/policy/testdata/policy-expected-usage.txt create mode 100644 cmd/policy/testdata/policy/create-expected-usage.txt create mode 100644 cmd/policy/testdata/policy/delete-expected-usage.txt create mode 100644 cmd/policy/testdata/policy/get-expected-usage.txt create mode 100644 cmd/policy/testdata/policy/list-expected-usage.txt create mode 100644 cmd/policy/testdata/policy/update-expected-usage.txt create mode 100644 cmd/policy/testdata/test.rego create mode 100644 cmd/policy/usage_test.go diff --git a/.gitignore b/.gitignore index c14cdc97f..9b900d350 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ docs/ out/ vendor/ .vscode +.idea # For supporting generated config of 2.1 syntax for local testing .circleci/processed.yml diff --git a/api/policy/policy.go b/api/policy/policy.go new file mode 100644 index 000000000..e2ff28cf5 --- /dev/null +++ b/api/policy/policy.go @@ -0,0 +1,249 @@ +package policy + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/CircleCI-Public/circleci-cli/api/header" + "github.com/CircleCI-Public/circleci-cli/settings" + "github.com/CircleCI-Public/circleci-cli/version" +) + +// Policy-service endpoints documentation : https://github.com/circleci/policy-service/blob/main/cmd/server/openapi.yaml + +// Client communicates with the CircleCI policy-service to ask questions +// about policies. It satisfies policy.ClientInterface. +type Client struct { + serverUrl string + client *http.Client +} + +// httpError represents error response json payload as sent by the policy-server: internal/error.go +type httpError struct { + Error string `json:"error"` + Context map[string]interface{} `json:"context,omitempty"` +} + +// ListPolicies calls the view policy-service list policy API. If the active filter is nil, all policies are returned. If +// activeFilter is not nil it will only return active or inactive policies based on the value of *activeFilter. +func (c Client) ListPolicies(ownerID string, activeFilter *bool) (interface{}, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/owner/%s/policy", c.serverUrl, ownerID), nil) + if err != nil { + return nil, fmt.Errorf("failed to construct request: %v", err) + } + + query := make(url.Values) + if activeFilter != nil { + query.Set("active", fmt.Sprint(*activeFilter)) + } + + req.URL.RawQuery = query.Encode() + + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + var payload httpError + if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { + return nil, fmt.Errorf("unexected status-code: %d", resp.StatusCode) + } + return nil, fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, payload.Error) + } + + var body interface{} + if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { + return nil, fmt.Errorf("failed to decode response body: %v", err) + } + + return body, nil +} + +// Creation types taken from policy-service: internal/policy/api.go + +// CreationRequest represents the json payload to create a Policy in the Policy-Service +type CreationRequest struct { + Name string `json:"name"` + Context string `json:"context"` + Content string `json:"content"` +} + +// CreatePolicy call the Create Policy API in the Policy-Service. It creates a policy for the specified owner and returns the created +// policy resonse as an interface{}. +func (c Client) CreatePolicy(ownerID string, policy CreationRequest) (interface{}, error) { + data, err := json.Marshal(policy) + if err != nil { + return nil, fmt.Errorf("failed to encode policy payload: %w", err) + } + + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/owner/%s/policy", c.serverUrl, ownerID), bytes.NewReader(data)) + if err != nil { + return nil, fmt.Errorf("failed to construct request: %v", err) + } + + req.Header.Set("Content-Length", strconv.Itoa(len(data))) + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to get response from policy-service: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusCreated { + var response httpError + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, fmt.Errorf("unexpected status-code: %d", resp.StatusCode) + } + return nil, fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, response.Error) + } + + var response interface{} + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, fmt.Errorf("failed to parse response: %w", err) + } + + return &response, nil +} + +type UpdateRequest struct { + Name *string `json:"name,omitempty"` + Context *string `json:"context,omitempty"` + Content *string `json:"content,omitempty"` + Active *bool `json:"active,omitempty"` +} + +// UpdatePolicy calls the UPDATE policy API in the policy-service. It updates a policy in the policy-service matching the given owner-id and policy-id. +func (c Client) UpdatePolicy(ownerID string, policyID string, policy UpdateRequest) (interface{}, error) { + data, err := json.Marshal(policy) + if err != nil { + return nil, fmt.Errorf("failed to encode policy payload: %w", err) + } + + req, err := http.NewRequest( + "PATCH", + fmt.Sprintf("%s/api/v1/owner/%s/policy/%s", c.serverUrl, ownerID, policyID), + bytes.NewReader(data), + ) + if err != nil { + return nil, fmt.Errorf("failed to construct request: %v", err) + } + + req.Header.Set("Content-Length", strconv.Itoa(len(data))) + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to get response from policy-service: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + var response httpError + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, fmt.Errorf("unexpected status-code: %d", resp.StatusCode) + } + return nil, fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, response.Error) + } + + var response interface{} + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, fmt.Errorf("failed to parse response: %w", err) + } + + return &response, nil +} + +// GetPolicy calls the GET policy API in the policy-service.It fetches the policy from policy-service matching the given owner-id and policy-id. +// It returns an error if the call fails or the policy could not be found. +func (c Client) GetPolicy(ownerID string, policyID string) (interface{}, error) { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/owner/%s/policy/%s", c.serverUrl, ownerID, policyID), nil) + if err != nil { + return nil, fmt.Errorf("failed to construct request: %v", err) + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + var payload httpError + if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { + return nil, fmt.Errorf("unexected status-code: %d", resp.StatusCode) + } + return nil, fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, payload.Error) + } + + var body interface{} + if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { + return nil, fmt.Errorf("failed to decode response body: %v", err) + } + + return body, nil +} + +// DeletePolicy calls the DELETE Policy API in the policy-service. +// It attempts to delete the policy matching the given policy-id and belonging to the given ownerID. +// It returns an error if the call fails or the policy could not be deleted. +func (c Client) DeletePolicy(ownerID string, policyID string) error { + req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/api/v1/owner/%s/policy/%s", c.serverUrl, ownerID, policyID), nil) + if err != nil { + return fmt.Errorf("failed to construct request: %v", err) + } + + resp, err := c.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusNoContent { + var payload httpError + if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { + return fmt.Errorf("unexected status-code: %d", resp.StatusCode) + } + return fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, payload.Error) + } + + return nil +} + +// NewClient returns a new policy client that will use the provided settings.Config to automatically inject appropriate +// Circle-Token authentication and other relevant CLI headers. +func NewClient(baseURL string, config *settings.Config) *Client { + transport := config.HTTPClient.Transport + if transport == nil { + transport = http.DefaultTransport + } + + config.HTTPClient.Transport = transportFunc(func(r *http.Request) (*http.Response, error) { + r.Header.Add("circle-token", config.Token) + r.Header.Add("Accept", "application/json") + r.Header.Add("Content-Type", "application/json") + r.Header.Add("User-Agent", version.UserAgent()) + if commandStr := header.GetCommandStr(); commandStr != "" { + r.Header.Add("Circleci-Cli-Command", commandStr) + } + return transport.RoundTrip(r) + }) + + return &Client{ + serverUrl: strings.TrimSuffix(baseURL, "/"), + client: config.HTTPClient, + } +} + +// transportFunc is utility type for declaring a http.RoundTripper as a function literal +type transportFunc func(*http.Request) (*http.Response, error) + +// RoundTrip implements the http.RoundTripper interface +func (fn transportFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return fn(req) +} diff --git a/api/policy/policy_test.go b/api/policy/policy_test.go new file mode 100644 index 000000000..61a6ea324 --- /dev/null +++ b/api/policy/policy_test.go @@ -0,0 +1,539 @@ +package policy + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "gotest.tools/v3/assert" + + "github.com/CircleCI-Public/circleci-cli/settings" + "github.com/CircleCI-Public/circleci-cli/version" +) + +func TestClientListPolicies(t *testing.T) { + t.Run("expected request", func(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + assert.Equal(t, r.Header.Get("accept"), "application/json") + assert.Equal(t, r.Header.Get("content-type"), "application/json") + assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/policy") + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("[]")) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + _, err := client.ListPolicies("ownerId", nil) + assert.NilError(t, err) + }) + + t.Run("List Policies - Bad Request", func(t *testing.T) { + expectedResponse := `{"error": "active: query string not a boolean."}` + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + policies, err := client.ListPolicies("ownerId", nil) + assert.Equal(t, policies, nil) + assert.Error(t, err, "unexpected status-code: 400 - active: query string not a boolean.") + }) + + t.Run("List Policies - Forbidden", func(t *testing.T) { + expectedResponse := `{"error": "Forbidden"}` + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusForbidden) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + policies, err := client.ListPolicies("ownerId", nil) + assert.Equal(t, policies, nil) + assert.Error(t, err, "unexpected status-code: 403 - Forbidden") + }) + + t.Run("List Policies - no policies", func(t *testing.T) { + expectedResponse := "[]" + + var expectedResponseValue interface{} + assert.NilError(t, json.Unmarshal([]byte(expectedResponse), &expectedResponseValue)) + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + policies, err := client.ListPolicies("ownerId", nil) + assert.DeepEqual(t, policies, expectedResponseValue) + assert.NilError(t, err) + }) + + t.Run("List Policies - some policies", func(t *testing.T) { + expectedResponse := `[ + { + "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", + "name": "policy_1", + "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f", + "context": "config", + "active": false, + "created_at": "2022-05-31T14:15:10.86097Z", + "modified_at": null + }, + { + "id": "a917a0ab-ceb6-482d-9a4e-f2f6b8bdfdcd", + "name": "policy_2", + "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f", + "context": "config", + "active": true, + "created_at": "2022-05-31T14:15:23.582383Z", + "modified_at": "2022-05-31T14:15:46.72321Z" + } + ]` + + var expectedResponseValue interface{} + assert.NilError(t, json.Unmarshal([]byte(expectedResponse), &expectedResponseValue)) + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + policies, err := client.ListPolicies("ownerId", nil) + assert.DeepEqual(t, policies, expectedResponseValue) + assert.NilError(t, err) + }) +} + +func TestClientGetPolicy(t *testing.T) { + t.Run("expected request", func(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + assert.Equal(t, r.Header.Get("accept"), "application/json") + assert.Equal(t, r.Header.Get("content-type"), "application/json") + assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/policy/policyID") + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("[]")) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + _, err := client.GetPolicy("ownerId", "policyID") + assert.NilError(t, err) + }) + + t.Run("Get Policy - Bad Request", func(t *testing.T) { + expectedResponse := `{"error": "PolicyID: must be a valid UUID."}` + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + policy, err := client.GetPolicy("ownerId", "policyID") + assert.Equal(t, policy, nil) + assert.Error(t, err, "unexpected status-code: 400 - PolicyID: must be a valid UUID.") + }) + + t.Run("Get Policy - Forbidden", func(t *testing.T) { + expectedResponse := `{"error": "Forbidden"}` + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusForbidden) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + policy, err := client.GetPolicy("ownerId", "policyID") + assert.Equal(t, policy, nil) + assert.Error(t, err, "unexpected status-code: 403 - Forbidden") + }) + + t.Run("Get Policy - Not Found", func(t *testing.T) { + expectedResponse := `{"error": "policy not found"}` + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + policy, err := client.GetPolicy("ownerId", "a917a0ab-ceb6-482d-9a4e-f2f6b8bdfdca") + assert.Equal(t, policy, nil) + assert.Error(t, err, "unexpected status-code: 404 - policy not found") + }) + + t.Run("Get Policy - successfully gets a policy", func(t *testing.T) { + expectedResponse := `{ + "document_version": 1, + "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", + "name": "policy_1", + "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f", + "context": "config", + "content": "package test", + "active": false, + "created_at": "2022-05-31T14:15:10.86097Z", + "modified_at": null + }` + + var expectedResponseValue interface{} + assert.NilError(t, json.Unmarshal([]byte(expectedResponse), &expectedResponseValue)) + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + policy, err := client.GetPolicy("462d67f8-b232-4da4-a7de-0c86dd667d3f", "60b7e1a5-c1d7-4422-b813-7a12d353d7c6") + assert.DeepEqual(t, policy, expectedResponseValue) + assert.NilError(t, err) + }) +} + +func TestClientCreatePolicy(t *testing.T) { + t.Run("expected request", func(t *testing.T) { + req := CreationRequest{ + Name: "test-name", + Context: "config", + Content: "test-content", + } + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + assert.Equal(t, r.Header.Get("accept"), "application/json") + assert.Equal(t, r.Header.Get("content-type"), "application/json") + assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + + assert.Equal(t, r.Method, "POST") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/policy") + + var actual CreationRequest + assert.NilError(t, json.NewDecoder(r.Body).Decode(&actual)) + assert.DeepEqual(t, actual, req) + + w.WriteHeader(http.StatusCreated) + _, err := w.Write([]byte("{}")) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} + client := NewClient(svr.URL, config) + + _, err := client.CreatePolicy("ownerId", req) + assert.NilError(t, err) + }) + + t.Run("unexpected status code", func(t *testing.T) { + expectedResponse := `{"error": "Forbidden"}` + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusForbidden) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + _, err := client.CreatePolicy("ownerId", CreationRequest{}) + assert.Error(t, err, "unexpected status-code: 403 - Forbidden") + }) +} + +func TestClientDeletePolicy(t *testing.T) { + t.Run("expected request", func(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + assert.Equal(t, r.Header.Get("accept"), "application/json") + assert.Equal(t, r.Header.Get("content-type"), "application/json") + assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + + assert.Equal(t, r.Method, "DELETE") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/policy/policyID") + + w.WriteHeader(http.StatusNoContent) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + err := client.DeletePolicy("ownerId", "policyID") + assert.NilError(t, err) + }) + + t.Run("Delete Policy - Bad Request", func(t *testing.T) { + expectedResponse := `{"error": "PolicyID: must be a valid UUID."}` + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + err := client.DeletePolicy("ownerId", "policyID") + assert.Error(t, err, "unexpected status-code: 400 - PolicyID: must be a valid UUID.") + }) + + t.Run("Delete Policy - Forbidden", func(t *testing.T) { + expectedResponse := `{"error": "Forbidden"}` + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusForbidden) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + err := client.DeletePolicy("ownerId", "policyID") + assert.Error(t, err, "unexpected status-code: 403 - Forbidden") + }) + + t.Run("Delete Policy - Not Found", func(t *testing.T) { + expectedResponse := `{"error": "policy not found"}` + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + err := client.DeletePolicy("ownerId", "a917a0ab-ceb6-482d-9a4e-f2f6b8bdfdca") + assert.Error(t, err, "unexpected status-code: 404 - policy not found") + }) + + t.Run("Delete Policy - successfully deletes a policy", func(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNoContent) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + err := client.DeletePolicy("462d67f8-b232-4da4-a7de-0c86dd667d3f", "60b7e1a5-c1d7-4422-b813-7a12d353d7c6") + assert.NilError(t, err) + }) +} + +func TestClientUpdatePolicy(t *testing.T) { + t.Run("expected request", func(t *testing.T) { + isActive := true + name := "test-name" + context := "config" + content := "test-content" + req := UpdateRequest{ + Name: &name, + Context: &context, + Content: &content, + Active: &isActive, + } + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + assert.Equal(t, r.Header.Get("accept"), "application/json") + assert.Equal(t, r.Header.Get("content-type"), "application/json") + assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + + assert.Equal(t, r.Method, "PATCH") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerID/policy/policyID") + + var actual UpdateRequest + assert.NilError(t, json.NewDecoder(r.Body).Decode(&actual)) + assert.DeepEqual(t, actual, req) + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("{}")) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} + client := NewClient(svr.URL, config) + + _, err := client.UpdatePolicy("ownerID", "policyID", req) + assert.NilError(t, err) + }) + + t.Run("nil active", func(t *testing.T) { + + name := "test-name" + context := "config" + content := "test-content" + req := UpdateRequest{ + Name: &name, + Context: &context, + Content: &content, + Active: nil, + } + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + assert.Equal(t, r.Header.Get("accept"), "application/json") + assert.Equal(t, r.Header.Get("content-type"), "application/json") + assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + + assert.Equal(t, r.Method, "PATCH") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerID/policy/policyID") + + var actual UpdateRequest + assert.NilError(t, json.NewDecoder(r.Body).Decode(&actual)) + assert.DeepEqual(t, actual, req) + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("{}")) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} + client := NewClient(svr.URL, config) + + _, err := client.UpdatePolicy("ownerID", "policyID", req) + assert.NilError(t, err) + }) + + t.Run("unexpected status code", func(t *testing.T) { + expectedResponse := `{"error": "Forbidden"}` + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusForbidden) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + _, err := client.UpdatePolicy("ownerId", "policyId", UpdateRequest{}) + assert.Error(t, err, "unexpected status-code: 403 - Forbidden") + }) + + t.Run("no changes", func(t *testing.T) { + req := UpdateRequest{} + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + assert.Equal(t, r.Header.Get("accept"), "application/json") + assert.Equal(t, r.Header.Get("content-type"), "application/json") + assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + + assert.Equal(t, r.Method, "PATCH") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerID/policy/policyID") + + var actual UpdateRequest + assert.NilError(t, json.NewDecoder(r.Body).Decode(&actual)) + assert.DeepEqual(t, actual, req) + + expectedResponse := `{"error": "at least one of name, context, content, or active cannot be blank"}` + w.WriteHeader(http.StatusBadRequest) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} + client := NewClient(svr.URL, config) + + _, err := client.UpdatePolicy("ownerID", "policyID", req) + assert.Error(t, err, "unexpected status-code: 400 - at least one of name, context, content, or active cannot be blank") + }) + + t.Run("one change", func(t *testing.T) { + name := "test-name" + req := UpdateRequest{ + Name: &name, + } + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + assert.Equal(t, r.Header.Get("accept"), "application/json") + assert.Equal(t, r.Header.Get("content-type"), "application/json") + assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + + assert.Equal(t, r.Method, "PATCH") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerID/policy/policyID") + + var actual UpdateRequest + assert.NilError(t, json.NewDecoder(r.Body).Decode(&actual)) + assert.DeepEqual(t, actual, req) + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("{}")) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} + client := NewClient(svr.URL, config) + + _, err := client.UpdatePolicy("ownerID", "policyID", req) + assert.NilError(t, err) + }) +} diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go new file mode 100644 index 000000000..7ae108472 --- /dev/null +++ b/cmd/policy/policy.go @@ -0,0 +1,242 @@ +package policy + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + + "github.com/spf13/cobra" + + "github.com/CircleCI-Public/circleci-cli/api/policy" + + "github.com/CircleCI-Public/circleci-cli/settings" +) + +// validator is a cobra command and args validator to be run as persisten PreRun for every policy command. +type validator func(cmd *cobra.Command, args []string) error + +// NewCommand creates the root policy command with all policy subcommands attached. +func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { + cmd := &cobra.Command{ + Use: "policy", + PersistentPreRunE: preRunE, + Short: "Policies ensures security of build configs via security policy management framework. " + + "This group of commands allows the management of polices to be verified against build configs.", + } + + policyBaseURL := cmd.PersistentFlags().String("policy-base-url", "https://internal.circleci.com", "base url for policy api") + ownerID := cmd.PersistentFlags().String("owner-id", "", "the id of the owner of a policy") + if err := cmd.MarkPersistentFlagRequired("owner-id"); err != nil { + panic(err) + } + + list := func() *cobra.Command { + var active bool + + cmd := &cobra.Command{ + Short: "List all policies", + Use: "list", + RunE: func(cmd *cobra.Command, args []string) error { + var flags struct { + Active *bool + } + + if cmd.Flag("active").Changed { + flags.Active = &active + } + + policies, err := policy.NewClient(*policyBaseURL, config).ListPolicies(*ownerID, flags.Active) + if err != nil { + return fmt.Errorf("failed to list policies: %v", err) + } + + enc := json.NewEncoder(cmd.OutOrStdout()) + enc.SetIndent("", " ") + + if err := enc.Encode(policies); err != nil { + return fmt.Errorf("failed to output policies in json format: %v", err) + } + + return nil + }, + Args: cobra.ExactArgs(0), + Example: `policy list --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 --active=true`, + } + + cmd.Flags().BoolVar(&active, "active", false, "(OPTIONAL) filter policies based on active status (true or false)") + + return cmd + }() + + create := func() *cobra.Command { + var policyPath string + var creationRequest policy.CreationRequest + + cmd := &cobra.Command{ + Short: "create policy", + Use: "create", + RunE: func(cmd *cobra.Command, args []string) error { + policyData, err := ioutil.ReadFile(policyPath) + if err != nil { + return fmt.Errorf("failed to read policy file: %w", err) + } + + creationRequest.Content = string(policyData) + + client := policy.NewClient(*policyBaseURL, config) + + result, err := client.CreatePolicy(*ownerID, creationRequest) + if err != nil { + return fmt.Errorf("failed to create policy: %w", err) + } + + enc := json.NewEncoder(cmd.OutOrStdout()) + enc.SetIndent("", " ") + + if err := enc.Encode(result); err != nil { + return fmt.Errorf("failed to encode result to stdout: %w", err) + } + + return nil + }, + Args: cobra.ExactArgs(0), + Example: `policy create --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --name policy_name --policy ./policy.rego`, + } + + cmd.Flags().StringVar(&creationRequest.Name, "name", "", "name of policy to create") + cmd.Flags().StringVar(&creationRequest.Context, "context", "config", "policy context") + cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego policy file") + + if err := cmd.MarkFlagRequired("name"); err != nil { + panic(err) + } + if err := cmd.MarkFlagRequired("policy"); err != nil { + panic(err) + } + + return cmd + }() + + get := func() *cobra.Command { + cmd := &cobra.Command{ + Short: "Get a policy", + Use: "get ", + RunE: func(cmd *cobra.Command, args []string) error { + p, err := policy.NewClient(*policyBaseURL, config).GetPolicy(*ownerID, args[0]) + if err != nil { + return fmt.Errorf("failed to get policy: %v", err) + } + + enc := json.NewEncoder(cmd.OutOrStdout()) + enc.SetIndent("", " ") + + if err := enc.Encode(p); err != nil { + return fmt.Errorf("failed to output policy in json format: %v", err) + } + + return nil + }, + Args: cobra.ExactArgs(1), + Example: `policy get 60b7e1a5-c1d7-4422-b813-7a12d353d7c6 --owner-id 516425b2-e369-421b-838d-920e1f51b0f5`, + } + return cmd + }() + + delete := func() *cobra.Command { + cmd := &cobra.Command{ + Short: "Delete a policy", + Use: "delete ", + RunE: func(cmd *cobra.Command, args []string) error { + err := policy.NewClient(*policyBaseURL, config).DeletePolicy(*ownerID, args[0]) + if err != nil { + return fmt.Errorf("failed to delete policy: %v", err) + } + _, _ = io.WriteString(cmd.OutOrStdout(), "Deleted Successfully\n") + return nil + }, + Args: cobra.ExactArgs(1), + Example: `policy delete 60b7e1a5-c1d7-4422-b813-7a12d353d7c6 --owner-id 516425b2-e369-421b-838d-920e1f51b0f5`, + } + + return cmd + }() + + update := func() *cobra.Command { + var policyPath string + var active bool + var context string + var name string + var updateRequest policy.UpdateRequest + + cmd := &cobra.Command{ + Short: "Update a policy", + Use: "update ", + RunE: func(cmd *cobra.Command, args []string) error { + + if !(cmd.Flag("policy").Changed || + cmd.Flag("active").Changed || + cmd.Flag("context").Changed || + cmd.Flag("name").Changed) { + return fmt.Errorf("one of policy, active, context, or name must be set") + } + + if cmd.Flag("policy").Changed { + policyData, err := ioutil.ReadFile(policyPath) + if err != nil { + return fmt.Errorf("failed to read policy file: %w", err) + } + + content := string(policyData) + + updateRequest.Content = &content + } + + client := policy.NewClient(*policyBaseURL, config) + + if cmd.Flag("active").Changed { + updateRequest.Active = &active + } + + if cmd.Flag("context").Changed { + updateRequest.Context = &context + } + + if cmd.Flag("name").Changed { + updateRequest.Name = &name + } + + result, err := client.UpdatePolicy(*ownerID, args[0], updateRequest) + if err != nil { + return fmt.Errorf("failed to update policy: %w", err) + } + + enc := json.NewEncoder(cmd.OutOrStdout()) + enc.SetIndent("", " ") + + if err := enc.Encode(result); err != nil { + return fmt.Errorf("failed to encode result to stdout: %w", err) + } + + return nil + }, + Args: cobra.ExactArgs(1), + Example: `policy update e9e300d1-5bab-4704-b610-addbd6e03b0b --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --name policy_name --active --policy ./policy.rego`, + } + + cmd.Flags().StringVar(&name, "name", "", "set name of the given policy-id") + cmd.Flags().StringVar(&context, "context", "", "policy context (if set, must be config)") + cmd.Flags().BoolVar(&active, "active", false, "set policy active state (to deactivate, use --active=false)") + cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego file containing the updated policy") + + return cmd + }() + + cmd.AddCommand(list) + cmd.AddCommand(create) + cmd.AddCommand(get) + cmd.AddCommand(delete) + cmd.AddCommand(update) + + return cmd +} diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go new file mode 100644 index 000000000..453e4872d --- /dev/null +++ b/cmd/policy/policy_test.go @@ -0,0 +1,522 @@ +package policy + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/spf13/cobra" + "gotest.tools/v3/assert" + + "github.com/CircleCI-Public/circleci-cli/settings" +) + +func TestListPolicies(t *testing.T) { + testcases := []struct { + Name string + Args []string + ServerHandler http.HandlerFunc + ExpectedOutput string + ExpectedErr string + }{ + { + Name: "requires owner-id", + Args: []string{"list"}, + ExpectedErr: "required flag(s) \"owner-id\" not set", + }, + { + Name: "invalid active filter value", + Args: []string{"list", "--owner-id", "ownerID", "--active=badValue"}, + ExpectedErr: `invalid argument "badValue" for "--active" flag: strconv.ParseBool: parsing "badValue": invalid syntax`, + }, + { + Name: "should set active to true", + Args: []string{"list", "--owner-id", "ownerID", "--active"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy?active=true") + _, err := w.Write([]byte("[]")) + assert.NilError(t, err) + }, + ExpectedOutput: "[]\n", + }, + { + Name: "should set active to false", + Args: []string{"list", "--owner-id", "ownerID", "--active=false"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy?active=false") + _, err := w.Write([]byte("[]")) + assert.NilError(t, err) + }, + ExpectedOutput: "[]\n", + }, + { + Name: "no active is set", + Args: []string{"list", "--owner-id", "ownerID"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy") + _, err := w.Write([]byte("[]")) + assert.NilError(t, err) + }, + ExpectedOutput: "[]\n", + }, + { + Name: "gets error response", + Args: []string{"list", "--owner-id", "ownerID"}, + ExpectedErr: "failed to list policies: unexpected status-code: 403 - Forbidden", + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy") + w.WriteHeader(http.StatusForbidden) + _, err := w.Write([]byte(`{"error": "Forbidden"}`)) + assert.NilError(t, err) + }, + }, + { + Name: "successfully gets a policy", + Args: []string{"list", "--owner-id", "ownerID"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy") + _, err := w.Write([]byte(`[ + { + "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", + "name": "policy_1", + "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f", + "context": "config", + "active": false, + "created_at": "2022-05-31T14:15:10.86097Z", + "modified_at": null + } + ]`)) + assert.NilError(t, err) + }, + ExpectedOutput: `[ + { + "active": false, + "context": "config", + "created_at": "2022-05-31T14:15:10.86097Z", + "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", + "modified_at": null, + "name": "policy_1", + "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f" + } +] +`, + }, + } + + for _, tc := range testcases { + t.Run(tc.Name, func(t *testing.T) { + if tc.ServerHandler == nil { + tc.ServerHandler = func(w http.ResponseWriter, r *http.Request) {} + } + + svr := httptest.NewServer(tc.ServerHandler) + defer svr.Close() + + cmd, stdout, _ := makeCMD() + + cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) + + err := cmd.Execute() + if tc.ExpectedErr == "" { + assert.NilError(t, err) + } else { + assert.Error(t, err, tc.ExpectedErr) + return + } + assert.Equal(t, stdout.String(), tc.ExpectedOutput) + }) + } +} + +func TestCreatePolicy(t *testing.T) { + testcases := []struct { + Name string + Args []string + ServerHandler http.HandlerFunc + ExpectedOutput string + ExpectedErr string + }{ + { + Name: "requires owner-id and name and policy", + Args: []string{"create"}, + ExpectedErr: "required flag(s) \"name\", \"owner-id\", \"policy\" not set", + }, + { + Name: "sends appropriate desired request", + Args: []string{"create", "--owner-id", "test-org", "--name", "test-policy", "--policy", "./testdata/test.rego"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + var body map[string]interface{} + assert.Equal(t, r.Method, "POST") + assert.Equal(t, r.URL.String(), "/api/v1/owner/test-org/policy") + assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.DeepEqual(t, body, map[string]interface{}{ + "content": "package test", + "context": "config", + "name": "test-policy", + }) + + w.WriteHeader(http.StatusCreated) + _, err := w.Write([]byte("{}")) + assert.NilError(t, err) + }, + ExpectedOutput: "{}\n", + }, + } + + for _, tc := range testcases { + t.Run(tc.Name, func(t *testing.T) { + if tc.ServerHandler == nil { + tc.ServerHandler = func(w http.ResponseWriter, r *http.Request) {} + } + + svr := httptest.NewServer(tc.ServerHandler) + defer svr.Close() + + cmd, stdout, _ := makeCMD() + + cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) + + err := cmd.Execute() + if tc.ExpectedErr == "" { + assert.NilError(t, err) + } else { + assert.Error(t, err, tc.ExpectedErr) + return + } + + assert.Equal(t, stdout.String(), tc.ExpectedOutput) + }) + } +} + +func TestGetPolicy(t *testing.T) { + testcases := []struct { + Name string + Args []string + ServerHandler http.HandlerFunc + ExpectedOutput string + ExpectedErr string + }{ + { + Name: "requires policy-id", + Args: []string{"get", "--owner-id", "ownerID"}, + ExpectedErr: "accepts 1 arg(s), received 0", + }, + { + Name: "requires owner-id", + Args: []string{"get", "policyID"}, + ExpectedErr: "required flag(s) \"owner-id\" not set", + }, + { + Name: "gets error response", + Args: []string{"get", "policyID", "--owner-id", "ownerID"}, + ExpectedErr: "failed to get policy: unexpected status-code: 403 - Forbidden", + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy/policyID") + w.WriteHeader(http.StatusForbidden) + _, err := w.Write([]byte(`{"error": "Forbidden"}`)) + assert.NilError(t, err) + }, + }, + { + Name: "successfully gets a policy", + Args: []string{"get", "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", "--owner-id", "462d67f8-b232-4da4-a7de-0c86dd667d3f"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/policy/60b7e1a5-c1d7-4422-b813-7a12d353d7c6") + _, err := w.Write([]byte(`{ + "document_version": 1, + "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", + "name": "policy_1", + "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f", + "context": "config", + "content": "package test", + "active": false, + "created_at": "2022-05-31T14:15:10.86097Z", + "modified_at": null + }`)) + assert.NilError(t, err) + }, + ExpectedOutput: `{ + "active": false, + "content": "package test", + "context": "config", + "created_at": "2022-05-31T14:15:10.86097Z", + "document_version": 1, + "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", + "modified_at": null, + "name": "policy_1", + "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f" +} +`, + }, + } + + for _, tc := range testcases { + t.Run(tc.Name, func(t *testing.T) { + if tc.ServerHandler == nil { + tc.ServerHandler = func(w http.ResponseWriter, r *http.Request) {} + } + + svr := httptest.NewServer(tc.ServerHandler) + defer svr.Close() + + cmd, stdout, _ := makeCMD() + + cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) + + err := cmd.Execute() + if tc.ExpectedErr == "" { + assert.NilError(t, err) + } else { + assert.Error(t, err, tc.ExpectedErr) + return + } + + assert.Equal(t, stdout.String(), tc.ExpectedOutput) + }) + } +} + +func TestDeletePolicy(t *testing.T) { + testcases := []struct { + Name string + Args []string + ServerHandler http.HandlerFunc + ExpectedOutput string + ExpectedErr string + }{ + { + Name: "requires policy-id", + Args: []string{"delete", "--owner-id", "ownerID"}, + ExpectedErr: "accepts 1 arg(s), received 0", + }, + { + Name: "requires owner-id", + Args: []string{"delete", "policyID"}, + ExpectedErr: "required flag(s) \"owner-id\" not set", + }, + { + Name: "gets error response", + Args: []string{"delete", "policyID", "--owner-id", "ownerID"}, + ExpectedErr: "failed to delete policy: unexpected status-code: 403 - Forbidden", + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "DELETE") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy/policyID") + w.WriteHeader(http.StatusForbidden) + _, err := w.Write([]byte(`{"error": "Forbidden"}`)) + assert.NilError(t, err) + }, + }, + { + Name: "successfully deletes a policy", + Args: []string{"delete", "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", "--owner-id", "462d67f8-b232-4da4-a7de-0c86dd667d3f"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "DELETE") + assert.Equal(t, r.URL.String(), "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/policy/60b7e1a5-c1d7-4422-b813-7a12d353d7c6") + w.WriteHeader(http.StatusNoContent) + }, + ExpectedOutput: "Deleted Successfully\n", + }, + } + + for _, tc := range testcases { + t.Run(tc.Name, func(t *testing.T) { + if tc.ServerHandler == nil { + tc.ServerHandler = func(w http.ResponseWriter, r *http.Request) {} + } + + svr := httptest.NewServer(tc.ServerHandler) + defer svr.Close() + + cmd, stdout, _ := makeCMD() + + cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) + + err := cmd.Execute() + if tc.ExpectedErr == "" { + assert.NilError(t, err) + } else { + assert.Error(t, err, tc.ExpectedErr) + return + } + + assert.Equal(t, stdout.String(), tc.ExpectedOutput) + }) + } +} + +func TestUpdatePolicy(t *testing.T) { + makeCMD := func() (*cobra.Command, *bytes.Buffer, *bytes.Buffer) { + config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} + cmd := NewCommand(config, nil) + + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + cmd.SetOut(stdout) + cmd.SetErr(stderr) + + return cmd, stdout, stderr + } + + testcases := []struct { + Name string + Args []string + ServerHandler http.HandlerFunc + ExpectedOutput string + ExpectedErr string + }{ + { + Name: "requires owner-id flag", + Args: []string{"update", "testID"}, + ExpectedErr: "required flag(s) \"owner-id\" not set", + }, + { + Name: "requires policy id", + Args: []string{"update", "--owner-id", "test-org"}, + ExpectedErr: "accepts 1 arg(s), received 0", + }, + { + Name: "sends appropriate desired request", + Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--active", "--name", "test-policy", "--policy", "./testdata/test.rego"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + var body map[string]interface{} + assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-org/policy/test-policy-id") + assert.DeepEqual(t, body, map[string]interface{}{ + "content": "package test", + "name": "test-policy", + "active": true, + }) + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("{}")) + assert.NilError(t, err) + }, + ExpectedOutput: "{}\n", + }, + { + Name: "explicitly set config", + Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--active", "--name", "test-policy", "--policy", "./testdata/test.rego", "--context", "config"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + var body map[string]interface{} + assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-org/policy/test-policy-id") + assert.DeepEqual(t, body, map[string]interface{}{ + "content": "package test", + "name": "test-policy", + "active": true, + "context": "config", + }) + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("{}")) + assert.NilError(t, err) + }, + ExpectedOutput: "{}\n", + }, + { + Name: "sends appropriate desired request with only name", + Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--name", "test-policy"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + var body map[string]interface{} + assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-org/policy/test-policy-id") + assert.DeepEqual(t, body, map[string]interface{}{ + "name": "test-policy", + }) + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("{}")) + assert.NilError(t, err) + }, + ExpectedOutput: "{}\n", + }, + { + Name: "sends appropriate desired request with only policy path", + Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--policy", "./testdata/test.rego"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + var body map[string]interface{} + assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-org/policy/test-policy-id") + assert.DeepEqual(t, body, map[string]interface{}{ + "content": "package test", + }) + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("{}")) + assert.NilError(t, err) + }, + ExpectedOutput: "{}\n", + }, + { + Name: "sends appropriate desired request - deactivate policy", + Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--active=false", "--name", "test-policy", "--policy", "./testdata/test.rego"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + var body map[string]interface{} + assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-org/policy/test-policy-id") + assert.DeepEqual(t, body, map[string]interface{}{ + "content": "package test", + "name": "test-policy", + "active": false, + }) + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("{}")) + assert.NilError(t, err) + }, + ExpectedOutput: "{}\n", + }, + { + Name: "check at least one field is changed", + Args: []string{"update", "test-policy-id", "--owner-id", "test-org"}, + ExpectedErr: "one of policy, active, context, or name must be set", + }, + } + + for _, tc := range testcases { + t.Run(tc.Name, func(t *testing.T) { + if tc.ServerHandler == nil { + tc.ServerHandler = func(w http.ResponseWriter, r *http.Request) {} + } + + svr := httptest.NewServer(tc.ServerHandler) + defer svr.Close() + + cmd, stdout, _ := makeCMD() + + cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) + + err := cmd.Execute() + if tc.ExpectedErr == "" { + assert.NilError(t, err) + } else { + assert.Error(t, err, tc.ExpectedErr) + return + } + + assert.Equal(t, stdout.String(), tc.ExpectedOutput) + }) + } +} + +func makeCMD() (*cobra.Command, *bytes.Buffer, *bytes.Buffer) { + config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} + cmd := NewCommand(config, nil) + + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + cmd.SetOut(stdout) + cmd.SetErr(stderr) + + return cmd, stdout, stderr +} diff --git a/cmd/policy/testdata/policy-expected-usage.txt b/cmd/policy/testdata/policy-expected-usage.txt new file mode 100644 index 000000000..6b42e7d02 --- /dev/null +++ b/cmd/policy/testdata/policy-expected-usage.txt @@ -0,0 +1,15 @@ +Usage: + policy [command] + +Available Commands: + create create policy + delete Delete a policy + get Get a policy + list List all policies + update Update a policy + +Flags: + --owner-id string the id of the owner of a policy + --policy-base-url string base url for policy api (default "https://internal.circleci.com") + +Use "policy [command] --help" for more information about a command. diff --git a/cmd/policy/testdata/policy/create-expected-usage.txt b/cmd/policy/testdata/policy/create-expected-usage.txt new file mode 100644 index 000000000..bea3a7a41 --- /dev/null +++ b/cmd/policy/testdata/policy/create-expected-usage.txt @@ -0,0 +1,14 @@ +Usage: + policy create [flags] + +Examples: +policy create --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --name policy_name --policy ./policy.rego + +Flags: + --context string policy context (default "config") + --name string name of policy to create + --policy string path to rego policy file + +Global Flags: + --owner-id string the id of the owner of a policy + --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/delete-expected-usage.txt b/cmd/policy/testdata/policy/delete-expected-usage.txt new file mode 100644 index 000000000..1d97a99df --- /dev/null +++ b/cmd/policy/testdata/policy/delete-expected-usage.txt @@ -0,0 +1,9 @@ +Usage: + policy delete [flags] + +Examples: +policy delete 60b7e1a5-c1d7-4422-b813-7a12d353d7c6 --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 + +Global Flags: + --owner-id string the id of the owner of a policy + --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/get-expected-usage.txt b/cmd/policy/testdata/policy/get-expected-usage.txt new file mode 100644 index 000000000..61ad84252 --- /dev/null +++ b/cmd/policy/testdata/policy/get-expected-usage.txt @@ -0,0 +1,9 @@ +Usage: + policy get [flags] + +Examples: +policy get 60b7e1a5-c1d7-4422-b813-7a12d353d7c6 --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 + +Global Flags: + --owner-id string the id of the owner of a policy + --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/list-expected-usage.txt b/cmd/policy/testdata/policy/list-expected-usage.txt new file mode 100644 index 000000000..4027ce08f --- /dev/null +++ b/cmd/policy/testdata/policy/list-expected-usage.txt @@ -0,0 +1,12 @@ +Usage: + policy list [flags] + +Examples: +policy list --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 --active=true + +Flags: + --active (OPTIONAL) filter policies based on active status (true or false) + +Global Flags: + --owner-id string the id of the owner of a policy + --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/update-expected-usage.txt b/cmd/policy/testdata/policy/update-expected-usage.txt new file mode 100644 index 000000000..61caa34f0 --- /dev/null +++ b/cmd/policy/testdata/policy/update-expected-usage.txt @@ -0,0 +1,15 @@ +Usage: + policy update [flags] + +Examples: +policy update e9e300d1-5bab-4704-b610-addbd6e03b0b --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --name policy_name --active --policy ./policy.rego + +Flags: + --active set policy active state (to deactivate, use --active=false) + --context string policy context (if set, must be config) + --name string set name of the given policy-id + --policy string path to rego file containing the updated policy + +Global Flags: + --owner-id string the id of the owner of a policy + --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/test.rego b/cmd/policy/testdata/test.rego new file mode 100644 index 000000000..761254b6c --- /dev/null +++ b/cmd/policy/testdata/test.rego @@ -0,0 +1 @@ +package test \ No newline at end of file diff --git a/cmd/policy/usage_test.go b/cmd/policy/usage_test.go new file mode 100644 index 000000000..37b4a250e --- /dev/null +++ b/cmd/policy/usage_test.go @@ -0,0 +1,28 @@ +package policy + +import ( + "fmt" + "testing" + + "gotest.tools/v3/golden" + + "github.com/spf13/cobra" + + "github.com/CircleCI-Public/circleci-cli/settings" +) + +func TestUsage(t *testing.T) { + preRunE := func(cmd *cobra.Command, args []string) error { return nil } + cmd := NewCommand(&settings.Config{}, preRunE) + testSubCommandUsage(t, cmd.Name(), cmd) +} + +func testSubCommandUsage(t *testing.T, prefix string, parent *cobra.Command) { + t.Helper() + t.Run(parent.Name(), func(t *testing.T) { + golden.Assert(t, parent.UsageString(), fmt.Sprintf("%s-expected-usage.txt", prefix)) + for _, cmd := range parent.Commands() { + testSubCommandUsage(t, fmt.Sprintf("%s/%s", prefix, cmd.Name()), cmd) + } + }) +} diff --git a/cmd/root.go b/cmd/root.go index 756ea2265..6c57cbaa6 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/cobra" "github.com/CircleCI-Public/circleci-cli/api/header" + "github.com/CircleCI-Public/circleci-cli/cmd/policy" "github.com/CircleCI-Public/circleci-cli/cmd/runner" "github.com/CircleCI-Public/circleci-cli/data" "github.com/CircleCI-Public/circleci-cli/md_docs" @@ -138,6 +139,7 @@ func MakeCommands() *cobra.Command { rootCmd.AddCommand(newSetupCommand(rootOptions)) rootCmd.AddCommand(followProjectCommand(rootOptions)) + rootCmd.AddCommand(policy.NewCommand(rootOptions, validator)) if isUpdateIncluded(version.PackageManager()) { rootCmd.AddCommand(newUpdateCommand(rootOptions)) diff --git a/cmd/root_test.go b/cmd/root_test.go index 9a9ff0cf4..e91a397bd 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -3,19 +3,20 @@ package cmd_test import ( "os/exec" - "github.com/CircleCI-Public/circleci-cli/clitest" - "github.com/CircleCI-Public/circleci-cli/cmd" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gexec" + + "github.com/CircleCI-Public/circleci-cli/clitest" + "github.com/CircleCI-Public/circleci-cli/cmd" ) var _ = Describe("Root", func() { Describe("subcommands", func() { It("can create commands", func() { commands := cmd.MakeCommands() - Expect(len(commands.Commands())).To(Equal(20)) + Expect(len(commands.Commands())).To(Equal(21)) }) }) From a65cbb5d5d6df7d78628d241ffeb9c9e0c1d23d5 Mon Sep 17 00:00:00 2001 From: Sagar Gupta Date: Mon, 13 Jun 2022 11:57:27 -0400 Subject: [PATCH 12/48] [SECENG-601] Added `policy logs` command to fetch decision audit logs (#734) --- api/policy/policy.go | 69 +++++- api/policy/policy_test.go | 196 +++++++++++++++++- cmd/policy/policy.go | 97 ++++++++- cmd/policy/policy_test.go | 140 +++++++++++++ cmd/policy/testdata/policy-expected-usage.txt | 1 + .../testdata/policy/logs-expected-usage.txt | 16 ++ go.mod | 6 +- go.sum | 17 +- 8 files changed, 532 insertions(+), 10 deletions(-) create mode 100644 cmd/policy/testdata/policy/logs-expected-usage.txt diff --git a/api/policy/policy.go b/api/policy/policy.go index e2ff28cf5..5f14da223 100644 --- a/api/policy/policy.go +++ b/api/policy/policy.go @@ -8,6 +8,7 @@ import ( "net/url" "strconv" "strings" + "time" "github.com/CircleCI-Public/circleci-cli/api/header" "github.com/CircleCI-Public/circleci-cli/settings" @@ -76,7 +77,7 @@ type CreationRequest struct { } // CreatePolicy call the Create Policy API in the Policy-Service. It creates a policy for the specified owner and returns the created -// policy resonse as an interface{}. +// policy response as an interface{}. func (c Client) CreatePolicy(ownerID string, policy CreationRequest) (interface{}, error) { data, err := json.Marshal(policy) if err != nil { @@ -215,6 +216,63 @@ func (c Client) DeletePolicy(ownerID string, policyID string) error { return nil } +type DecisionQueryRequest struct { + After *time.Time + Before *time.Time + Branch string + ProjectID string + Offset int +} + +// GetDecisionLogs calls the GET decision query API of policy-service. The endpoint accepts multiple filter values as +// path query parameters (start-time, end-time, branch-name, project-id and offset). +func (c Client) GetDecisionLogs(ownerID string, request DecisionQueryRequest) ([]interface{}, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/owner/%s/decision", c.serverUrl, ownerID), nil) + if err != nil { + return nil, fmt.Errorf("failed to construct request: %v", err) + } + + query := make(url.Values) + if request.After != nil { + query.Set("after", request.After.Format(time.RFC3339)) + } + if request.Before != nil { + query.Set("before", request.Before.Format(time.RFC3339)) + } + if request.Branch != "" { + query.Set("branch", fmt.Sprint(request.Branch)) + } + if request.ProjectID != "" { + query.Set("project_id", fmt.Sprint(request.ProjectID)) + } + if request.Offset > 0 { + query.Set("offset", fmt.Sprint(request.Offset)) + } + + req.URL.RawQuery = query.Encode() + + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + var payload httpError + if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { + return nil, fmt.Errorf("unexected status-code: %d", resp.StatusCode) + } + return nil, fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, payload.Error) + } + + var body []interface{} + if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { + return nil, fmt.Errorf("failed to decode response body: %v", err) + } + + return body, nil +} + // NewClient returns a new policy client that will use the provided settings.Config to automatically inject appropriate // Circle-Token authentication and other relevant CLI headers. func NewClient(baseURL string, config *settings.Config) *Client { @@ -223,7 +281,16 @@ func NewClient(baseURL string, config *settings.Config) *Client { transport = http.DefaultTransport } + // Throttling the client so that it cannot make more than 10 concurrent requests at time + sem := make(chan struct{}, 10) + config.HTTPClient.Transport = transportFunc(func(r *http.Request) (*http.Response, error) { + // Acquiring semaphore to respect throttling + sem <- struct{}{} + + // releasing the semaphore after a second ensuring client doesn't make more than cap(sem)/second + time.AfterFunc(time.Second, func() { <-sem }) + r.Header.Add("circle-token", config.Token) r.Header.Add("Accept", "application/json") r.Header.Add("Content-Type", "application/json") diff --git a/api/policy/policy_test.go b/api/policy/policy_test.go index 61a6ea324..9df1559f7 100644 --- a/api/policy/policy_test.go +++ b/api/policy/policy_test.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httptest" "testing" + "time" "gotest.tools/v3/assert" @@ -419,7 +420,6 @@ func TestClientUpdatePolicy(t *testing.T) { }) t.Run("nil active", func(t *testing.T) { - name := "test-name" context := "config" content := "test-content" @@ -537,3 +537,197 @@ func TestClientUpdatePolicy(t *testing.T) { assert.NilError(t, err) }) } + +func TestClientGetDecisionLogs(t *testing.T) { + t.Run("expected request without any filters", func(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + assert.Equal(t, r.Header.Get("accept"), "application/json") + assert.Equal(t, r.Header.Get("content-type"), "application/json") + assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/decision") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerId/decision") + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("[]")) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + _, err := client.GetDecisionLogs("ownerId", DecisionQueryRequest{}) + assert.NilError(t, err) + }) + + t.Run("expected request without only one filter", func(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + assert.Equal(t, r.Header.Get("accept"), "application/json") + assert.Equal(t, r.Header.Get("content-type"), "application/json") + assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/decision") + assert.Equal(t, r.URL.RawQuery, "project_id=projectIDValue") + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("[]")) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + _, err := client.GetDecisionLogs("ownerId", DecisionQueryRequest{ProjectID: "projectIDValue"}) + assert.NilError(t, err) + }) + + t.Run("expected request with all filters", func(t *testing.T) { + testTime := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + assert.Equal(t, r.Header.Get("accept"), "application/json") + assert.Equal(t, r.Header.Get("content-type"), "application/json") + assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/decision") + assert.Equal( + t, + r.URL.RawQuery, + "after=2000-01-01T00%3A00%3A00Z&before=2000-01-01T00%3A00%3A00Z&branch=branchValue&offset=42&project_id=projectIDValue", + ) + + assert.Equal(t, r.URL.Query().Get("before"), testTime.Format(time.RFC3339)) + assert.Equal(t, r.URL.Query().Get("after"), testTime.Format(time.RFC3339)) + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("[]")) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + _, err := client.GetDecisionLogs("ownerId", DecisionQueryRequest{ + After: &testTime, + Before: &testTime, + Branch: "branchValue", + ProjectID: "projectIDValue", + Offset: 42, + }) + assert.NilError(t, err) + }) + + t.Run("Get Decision Logs - Bad Request", func(t *testing.T) { + expectedResponse := `{"error": "Offset: must be an integer number."}` + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + logs, err := client.GetDecisionLogs("ownerId", DecisionQueryRequest{}) + assert.Error(t, err, "unexpected status-code: 400 - Offset: must be an integer number.") + assert.Equal(t, len(logs), 0) + }) + + t.Run("Get Decision Logs - Forbidden", func(t *testing.T) { + expectedResponse := `{"error": "Forbidden"}` + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusForbidden) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + logs, err := client.GetDecisionLogs("ownerId", DecisionQueryRequest{}) + assert.Error(t, err, "unexpected status-code: 403 - Forbidden") + assert.Equal(t, len(logs), 0) + }) + + t.Run("Get Decision Logs - no decision logs", func(t *testing.T) { + expectedResponse := "[]" + + var expectedResponseValue []interface{} + assert.NilError(t, json.Unmarshal([]byte(expectedResponse), &expectedResponseValue)) + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + logs, err := client.GetDecisionLogs("ownerId", DecisionQueryRequest{}) + assert.DeepEqual(t, logs, expectedResponseValue) + assert.NilError(t, err) + }) + + t.Run("Get Decision Logs - some logs", func(t *testing.T) { + expectedResponse := `[ + { + "metadata": {}, + "created_at": "2022-06-08T16:56:22.179906Z", + "policies": [ + { + "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", + "version": 2 + } + ], + "decision": { + "status": "PASS" + } + }, + { + "metadata": {}, + "created_at": "2022-06-08T17:06:14.591951Z", + "policies": [ + { + "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", + "version": 2 + } + ], + "decision": { + "status": "PASS" + } + } +]` + + var expectedResponseValue []interface{} + assert.NilError(t, json.Unmarshal([]byte(expectedResponse), &expectedResponseValue)) + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + logs, err := client.GetDecisionLogs("ownerId", DecisionQueryRequest{}) + assert.DeepEqual(t, logs, expectedResponseValue) + assert.NilError(t, err) + }) +} diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index 7ae108472..f659c8d4c 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -5,15 +5,20 @@ import ( "fmt" "io" "io/ioutil" + "os" + "time" + "github.com/briandowns/spinner" "github.com/spf13/cobra" + "github.com/araddon/dateparse" + "github.com/CircleCI-Public/circleci-cli/api/policy" "github.com/CircleCI-Public/circleci-cli/settings" ) -// validator is a cobra command and args validator to be run as persisten PreRun for every policy command. +// validator is a cobra command and args validator to be run as persistent PreRun for every policy command. type validator func(cmd *cobra.Command, args []string) error // NewCommand creates the root policy command with all policy subcommands attached. @@ -173,7 +178,6 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { Short: "Update a policy", Use: "update ", RunE: func(cmd *cobra.Command, args []string) error { - if !(cmd.Flag("policy").Changed || cmd.Flag("active").Changed || cmd.Flag("context").Changed || @@ -232,11 +236,100 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { return cmd }() + logs := func() *cobra.Command { + var after, before, outputFile string + var request policy.DecisionQueryRequest + + cmd := &cobra.Command{ + Short: "Get policy (decision) logs", + Use: "logs", + RunE: func(cmd *cobra.Command, args []string) (err error) { + if cmd.Flag("after").Changed { + request.After = new(time.Time) + *request.After, err = dateparse.ParseStrict(after) + if err != nil { + return fmt.Errorf("error in parsing --after value: %v", err) + } + } + + if cmd.Flag("before").Changed { + request.Before = new(time.Time) + *request.Before, err = dateparse.ParseStrict(before) + if err != nil { + return fmt.Errorf("error in parsing --before value: %v", err) + } + } + + dst := cmd.OutOrStdout() + if outputFile != "" { + file, err := os.Create(outputFile) + if err != nil { + return fmt.Errorf("failed to create output file: %v", err) + } + dst = file + defer func() { + if closeErr := file.Close(); err == nil && closeErr != nil { + err = closeErr + } + }() + } + + allLogs := make([]interface{}, 0) + + spr := spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(cmd.ErrOrStderr())) + spr.Suffix = " Fetching Policy Decision Logs..." + + spr.PostUpdate = func(s *spinner.Spinner) { + s.Suffix = fmt.Sprintf(" Fetching Policy Decision Logs... downloaded %d logs...", len(allLogs)) + } + + spr.Start() + defer spr.Stop() + + client := policy.NewClient(*policyBaseURL, config) + + for { + logsBatch, err := client.GetDecisionLogs(*ownerID, request) + if err != nil { + return fmt.Errorf("failed to get policy decision logs: %v", err) + } + + if len(logsBatch) == 0 { + break + } + + allLogs = append(allLogs, logsBatch...) + request.Offset = len(allLogs) + } + + enc := json.NewEncoder(dst) + enc.SetIndent("", " ") + + if err := enc.Encode(allLogs); err != nil { + return fmt.Errorf("failed to output policy decision logs in json format: %v", err) + } + + return nil + }, + Args: cobra.ExactArgs(0), + Example: `policy logs --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --after 2022/03/14 --out output.json`, + } + + cmd.Flags().StringVar(&after, "after", "", "filter decision logs triggered AFTER this datetime") + cmd.Flags().StringVar(&before, "before", "", "filter decision logs triggered BEFORE this datetime") + cmd.Flags().StringVar(&request.Branch, "branch", "", "filter decision logs based on branch name") + cmd.Flags().StringVar(&request.ProjectID, "project-id", "", "filter decision logs based on project-id") + cmd.Flags().StringVar(&outputFile, "out", "", "specify output file name ") + + return cmd + }() + cmd.AddCommand(list) cmd.AddCommand(create) cmd.AddCommand(get) cmd.AddCommand(delete) cmd.AddCommand(update) + cmd.AddCommand(logs) return cmd } diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index 453e4872d..567c47514 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -3,6 +3,7 @@ package policy import ( "bytes" "encoding/json" + "fmt" "net/http" "net/http/httptest" "testing" @@ -509,6 +510,145 @@ func TestUpdatePolicy(t *testing.T) { } } +func TestGetDecisionLogs(t *testing.T) { + testcases := []struct { + Name string + Args []string + ServerHandler http.HandlerFunc + ExpectedOutput string + ExpectedErr string + }{ + { + Name: "requires owner-id", + Args: []string{"logs"}, + ExpectedErr: "required flag(s) \"owner-id\" not set", + }, + { + Name: "invalid --after filter value", + Args: []string{"logs", "--owner-id", "ownerID", "--after", "1/2/2022"}, + ExpectedErr: `error in parsing --after value: This date has ambiguous mm/dd vs dd/mm type format`, + }, + { + Name: "no filter is set", + Args: []string{"logs", "--owner-id", "ownerID"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/decision") + _, err := w.Write([]byte("[]")) + assert.NilError(t, err) + }, + ExpectedOutput: "[]\n", + }, + { + Name: "all filters are set", + Args: []string{ + "logs", "--owner-id", "ownerID", "--after", "2022/03/14", "--before", "2022/03/15", + "--branch", "branchValue", "--project-id", "projectIDValue", + }, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/decision?after=2022-03-14T00%3A00%3A00Z&before=2022-03-15T00%3A00%3A00Z&branch=branchValue&project_id=projectIDValue") + _, err := w.Write([]byte("[]")) + assert.NilError(t, err) + }, + ExpectedOutput: "[]\n", + }, + { + Name: "gets error response", + Args: []string{"logs", "--owner-id", "ownerID"}, + ExpectedErr: "failed to get policy decision logs: unexpected status-code: 403 - Forbidden", + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/decision") + w.WriteHeader(http.StatusForbidden) + _, err := w.Write([]byte(`{"error": "Forbidden"}`)) + assert.NilError(t, err) + }, + }, + { + Name: "successfully gets decision logs", + Args: []string{"logs", "--owner-id", "ownerID"}, + ServerHandler: func() http.HandlerFunc { + var count int + return func(w http.ResponseWriter, r *http.Request) { + defer func() { count++ }() + + assert.Equal(t, r.Method, "GET") + + if count == 0 { + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/decision") + _, err := w.Write([]byte(` + [ + { + "metadata": {}, + "created_at": "2022-06-08T16:56:22.179906Z", + "policies": [ + { + "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", + "version": 2 + } + ], + "decision": { + "status": "PASS" + } + } + ]`), + ) + assert.NilError(t, err) + } else if count == 1 { + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/decision?offset=1") + _, err := w.Write([]byte("[]")) + assert.NilError(t, err) + } else { + t.Fatal("did not expect more than two requests but received a third") + } + } + }(), + ExpectedOutput: `[ + { + "created_at": "2022-06-08T16:56:22.179906Z", + "decision": { + "status": "PASS" + }, + "metadata": {}, + "policies": [ + { + "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", + "version": 2 + } + ] + } +] +`, + }, + } + + for _, tc := range testcases { + t.Run(tc.Name, func(t *testing.T) { + if tc.ServerHandler == nil { + tc.ServerHandler = func(w http.ResponseWriter, r *http.Request) {} + } + + svr := httptest.NewServer(tc.ServerHandler) + defer svr.Close() + + cmd, stdout, _ := makeCMD() + + cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) + + err := cmd.Execute() + if tc.ExpectedErr == "" { + assert.NilError(t, err) + } else { + assert.Error(t, err, tc.ExpectedErr) + return + } + fmt.Println(stdout.String()) + assert.Equal(t, stdout.String(), tc.ExpectedOutput) + }) + } +} + func makeCMD() (*cobra.Command, *bytes.Buffer, *bytes.Buffer) { config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} cmd := NewCommand(config, nil) diff --git a/cmd/policy/testdata/policy-expected-usage.txt b/cmd/policy/testdata/policy-expected-usage.txt index 6b42e7d02..fb2070dad 100644 --- a/cmd/policy/testdata/policy-expected-usage.txt +++ b/cmd/policy/testdata/policy-expected-usage.txt @@ -6,6 +6,7 @@ Available Commands: delete Delete a policy get Get a policy list List all policies + logs Get policy (decision) logs update Update a policy Flags: diff --git a/cmd/policy/testdata/policy/logs-expected-usage.txt b/cmd/policy/testdata/policy/logs-expected-usage.txt new file mode 100644 index 000000000..21cfffa71 --- /dev/null +++ b/cmd/policy/testdata/policy/logs-expected-usage.txt @@ -0,0 +1,16 @@ +Usage: + policy logs [flags] + +Examples: +policy logs --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --after 2022/03/14 --out output.json + +Flags: + --after string filter decision logs triggered AFTER this datetime + --before string filter decision logs triggered BEFORE this datetime + --branch string filter decision logs based on branch name + --out string specify output file name + --project-id string filter decision logs based on project-id + +Global Flags: + --owner-id string the id of the owner of a policy + --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/go.mod b/go.mod index ff2b46be5..4105a3785 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,9 @@ module github.com/CircleCI-Public/circleci-cli require ( github.com/AlecAivazis/survey/v2 v2.1.1 github.com/Masterminds/semver v1.4.2 + github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/blang/semver v3.5.1+incompatible - github.com/briandowns/spinner v0.0.0-20181018151057-dd69c579ff20 + github.com/briandowns/spinner v1.18.1 github.com/fatih/color v1.9.0 // indirect github.com/go-git/go-git/v5 v5.1.0 github.com/google/go-github v15.0.0+incompatible // indirect @@ -42,10 +43,11 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect github.com/mattn/go-colorable v0.1.4 // indirect - github.com/mattn/go-runewidth v0.0.7 // indirect + github.com/mattn/go-runewidth v0.0.10 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/nxadm/tail v1.4.4 // indirect + github.com/rivo/uniseg v0.1.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect github.com/xanzy/ssh-agent v0.2.1 // indirect golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect diff --git a/go.sum b/go.sum index 8bdb71f34..7bfc99641 100644 --- a/go.sum +++ b/go.sum @@ -9,13 +9,15 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBb github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= +github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/briandowns/spinner v0.0.0-20181018151057-dd69c579ff20 h1:kWWOFAhyzkpi4/+L3++mYiZbuxh1TqYkDMHfFjk6ZfE= -github.com/briandowns/spinner v0.0.0-20181018151057-dd69c579ff20/go.mod h1:hw/JEQBIE+c/BLI4aKM8UU8v+ZqrD3h7HC27kKt8JQU= +github.com/briandowns/spinner v1.18.1 h1:yhQmQtM1zsqFsouh09Bk/jCjd50pC3EOGsh28gLVvwY= +github.com/briandowns/spinner v1.18.1/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU= github.com/coreos/etcd v3.3.24+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -26,6 +28,7 @@ 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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -92,8 +95,9 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 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.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -121,7 +125,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rhysd/go-github-selfupdate v0.0.0-20180520142321-41c1bbb0804a h1:YNh/SV+Z0p7kQDUE9Ux+46ruTucvQP43XB06DfZa8Es= github.com/rhysd/go-github-selfupdate v0.0.0-20180520142321-41c1bbb0804a/go.mod h1:mOFQaTkPA4plTgFW6Gnejb/RsEIqAoIqOACC2XaZX04= +github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -136,8 +143,9 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw= github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -209,6 +217,7 @@ 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 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E= From 214763bb5e05824580489b69daeb42766013a5c2 Mon Sep 17 00:00:00 2001 From: davidmdm Date: Tue, 14 Jun 2022 14:27:06 -0400 Subject: [PATCH 13/48] added make decision policy command --- api/policy/policy.go | 42 +++++++++ api/policy/policy_test.go | 86 ++++++++++++++++++ cmd/policy/policy.go | 70 ++++++++++----- cmd/policy/policy_test.go | 89 +++++++++++++++++++ cmd/policy/testdata/policy-expected-usage.txt | 1 + .../testdata/policy/decide-expected-usage.txt | 10 +++ cmd/policy/testdata/test.yaml | 1 + 7 files changed, 279 insertions(+), 20 deletions(-) create mode 100644 cmd/policy/testdata/policy/decide-expected-usage.txt create mode 100644 cmd/policy/testdata/test.yaml diff --git a/api/policy/policy.go b/api/policy/policy.go index 5f14da223..275743998 100644 --- a/api/policy/policy.go +++ b/api/policy/policy.go @@ -273,6 +273,48 @@ func (c Client) GetDecisionLogs(ownerID string, request DecisionQueryRequest) ([ return body, nil } +type DecisionRequest struct { + Input string `json:"input"` + Context string `json:"context"` +} + +func (c Client) MakeDecision(ownerID string, req DecisionRequest) (interface{}, error) { + payload, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("failed to marshal request: %w", err) + } + + endpoint := fmt.Sprintf("%s/api/v1/owner/%s/decision", c.serverUrl, ownerID) + + request, err := http.NewRequest("POST", endpoint, bytes.NewReader(payload)) + if err != nil { + return nil, fmt.Errorf("failed to construct request: %w", err) + } + + request.Header.Set("Content-Length", strconv.Itoa(len(payload))) + + resp, err := c.client.Do(request) + if err != nil { + return nil, fmt.Errorf("failed to get response: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + var payload httpError + if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { + return nil, fmt.Errorf("unexpected status-code: %d", resp.StatusCode) + } + return nil, fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, payload.Error) + } + + var body interface{} + if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { + return nil, fmt.Errorf("failed to decode response body: %w", err) + } + + return body, nil +} + // NewClient returns a new policy client that will use the provided settings.Config to automatically inject appropriate // Circle-Token authentication and other relevant CLI headers. func NewClient(baseURL string, config *settings.Config) *Client { diff --git a/api/policy/policy_test.go b/api/policy/policy_test.go index 9df1559f7..6dbb566a3 100644 --- a/api/policy/policy_test.go +++ b/api/policy/policy_test.go @@ -2,6 +2,8 @@ package policy import ( "encoding/json" + "errors" + "io" "net/http" "net/http/httptest" "testing" @@ -731,3 +733,87 @@ func TestClientGetDecisionLogs(t *testing.T) { assert.NilError(t, err) }) } + +func TestMakeDecision(t *testing.T) { + testcases := []struct { + Name string + OwnerID string + Request DecisionRequest + Handler http.HandlerFunc + ExpectedError error + ExpectedDecision interface{} + }{ + { + Name: "sends expexted request", + OwnerID: "test-owner", + Request: DecisionRequest{ + Input: "test-input", + Context: "test-context", + }, + Handler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/decision") + assert.Equal(t, r.Method, "POST") + assert.Equal(t, r.Header.Get("Circle-Token"), "test-token") + + var payload map[string]interface{} + assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) + + assert.DeepEqual(t, payload, map[string]interface{}{ + "context": "test-context", + "input": "test-input", + }) + + json.NewEncoder(w).Encode(map[string]string{"status": "PASS"}) + }, + ExpectedDecision: map[string]interface{}{"status": "PASS"}, + }, + { + Name: "unexpected statuscode", + OwnerID: "test-owner", + Request: DecisionRequest{}, + Handler: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(400) + io.WriteString(w, `{"error":"that was a bad request!"}`) + }, + ExpectedError: errors.New("unexpected status-code: 400 - that was a bad request!"), + }, + + { + Name: "unexpected statuscode no body", + OwnerID: "test-owner", + Request: DecisionRequest{}, + Handler: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(204) + }, + ExpectedError: errors.New("unexpected status-code: 204"), + }, + { + Name: "bad decoding", + OwnerID: "test-owner", + Request: DecisionRequest{}, + Handler: func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, "not a json response") + }, + ExpectedError: errors.New("failed to decode response body: invalid character 'o' in literal null (expecting 'u')"), + }, + } + + for _, tc := range testcases { + t.Run(tc.Name, func(t *testing.T) { + svr := httptest.NewServer(tc.Handler) + defer svr.Close() + + client := NewClient(svr.URL, &settings.Config{Token: "test-token", HTTPClient: http.DefaultClient}) + + decision, err := client.MakeDecision(tc.OwnerID, tc.Request) + if tc.ExpectedError == nil { + assert.NilError(t, err) + } else { + assert.Error(t, err, tc.ExpectedError.Error()) + return + } + + assert.DeepEqual(t, decision, tc.ExpectedDecision) + }) + } +} diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index f659c8d4c..cae1e975e 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -32,6 +32,7 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { policyBaseURL := cmd.PersistentFlags().String("policy-base-url", "https://internal.circleci.com", "base url for policy api") ownerID := cmd.PersistentFlags().String("owner-id", "", "the id of the owner of a policy") + if err := cmd.MarkPersistentFlagRequired("owner-id"); err != nil { panic(err) } @@ -56,10 +57,7 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { return fmt.Errorf("failed to list policies: %v", err) } - enc := json.NewEncoder(cmd.OutOrStdout()) - enc.SetIndent("", " ") - - if err := enc.Encode(policies); err != nil { + if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(policies); err != nil { return fmt.Errorf("failed to output policies in json format: %v", err) } @@ -96,10 +94,7 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { return fmt.Errorf("failed to create policy: %w", err) } - enc := json.NewEncoder(cmd.OutOrStdout()) - enc.SetIndent("", " ") - - if err := enc.Encode(result); err != nil { + if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(result); err != nil { return fmt.Errorf("failed to encode result to stdout: %w", err) } @@ -133,10 +128,7 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { return fmt.Errorf("failed to get policy: %v", err) } - enc := json.NewEncoder(cmd.OutOrStdout()) - enc.SetIndent("", " ") - - if err := enc.Encode(p); err != nil { + if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(p); err != nil { return fmt.Errorf("failed to output policy in json format: %v", err) } @@ -215,10 +207,7 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { return fmt.Errorf("failed to update policy: %w", err) } - enc := json.NewEncoder(cmd.OutOrStdout()) - enc.SetIndent("", " ") - - if err := enc.Encode(result); err != nil { + if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(result); err != nil { return fmt.Errorf("failed to encode result to stdout: %w", err) } @@ -302,10 +291,7 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { request.Offset = len(allLogs) } - enc := json.NewEncoder(dst) - enc.SetIndent("", " ") - - if err := enc.Encode(allLogs); err != nil { + if err := prettyJSONEncoder(dst).Encode(allLogs); err != nil { return fmt.Errorf("failed to output policy decision logs in json format: %v", err) } @@ -324,12 +310,56 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { return cmd }() + decide := func() *cobra.Command { + var inputPath string + var request policy.DecisionRequest + + cmd := &cobra.Command{ + Short: "make a decision", + Use: "decide", + RunE: func(cmd *cobra.Command, args []string) error { + input, err := os.ReadFile(inputPath) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + + request.Input = string(input) + + decision, err := policy.NewClient(*policyBaseURL, config).MakeDecision(*ownerID, request) + if err != nil { + return fmt.Errorf("failed to make decision: %w", err) + } + + if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(decision); err != nil { + return fmt.Errorf("failed to encode decision: %w", err) + } + + return nil + }, + Args: cobra.ExactArgs(0), + } + + cmd.Flags().StringVar(&request.Context, "context", "config", "policy context for decision") + cmd.Flags().StringVar(&inputPath, "input", "", "path to input file") + + cmd.MarkFlagRequired("input") + + return cmd + }() + cmd.AddCommand(list) cmd.AddCommand(create) cmd.AddCommand(get) cmd.AddCommand(delete) cmd.AddCommand(update) cmd.AddCommand(logs) + cmd.AddCommand(decide) return cmd } + +func prettyJSONEncoder(dst io.Writer) *json.Encoder { + enc := json.NewEncoder(dst) + enc.SetIndent("", " ") + return enc +} diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index 567c47514..c709f3b98 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "io" "net/http" "net/http/httptest" "testing" @@ -649,6 +650,94 @@ func TestGetDecisionLogs(t *testing.T) { } } +func TestMakeDecisionCommand(t *testing.T) { + testcases := []struct { + Name string + Args []string + ServerHandler http.HandlerFunc + ExpectedOutput string + ExpectedErr string + }{ + { + Name: "requires flags", + Args: []string{"decide"}, + ExpectedErr: `required flag(s) "input", "owner-id" not set`, + }, + { + Name: "sends expected request", + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test.yaml"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "POST") + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/decision") + + var payload map[string]interface{} + assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) + + assert.DeepEqual(t, payload, map[string]interface{}{ + "context": "config", + "input": "test: config\n", + }) + + io.WriteString(w, `{"status":"PASS"}`) + }, + ExpectedOutput: "{\n \"status\": \"PASS\"\n}\n", + }, + { + Name: "sends expected request with context", + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test.yaml", "--context", "custom"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "POST") + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/decision") + + var payload map[string]interface{} + assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) + + assert.DeepEqual(t, payload, map[string]interface{}{ + "context": "custom", + "input": "test: config\n", + }) + + io.WriteString(w, `{"status":"PASS"}`) + }, + ExpectedOutput: "{\n \"status\": \"PASS\"\n}\n", + }, + { + Name: "fails on unexpected status code", + Args: []string{"decide", "--input", "./testdata/test.yaml", "--owner-id", "test-owner"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(500) + io.WriteString(w, `{"error":"oopsie!"}`) + }, + + ExpectedErr: "failed to make decision: unexpected status-code: 500 - oopsie!", + }, + } + + for _, tc := range testcases { + t.Run(tc.Name, func(t *testing.T) { + if tc.ServerHandler == nil { + tc.ServerHandler = func(w http.ResponseWriter, r *http.Request) {} + } + + svr := httptest.NewServer(tc.ServerHandler) + defer svr.Close() + + cmd, stdout, _ := makeCMD() + + cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) + + err := cmd.Execute() + if tc.ExpectedErr == "" { + assert.NilError(t, err) + } else { + assert.Error(t, err, tc.ExpectedErr) + return + } + assert.Equal(t, stdout.String(), tc.ExpectedOutput) + }) + } +} + func makeCMD() (*cobra.Command, *bytes.Buffer, *bytes.Buffer) { config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} cmd := NewCommand(config, nil) diff --git a/cmd/policy/testdata/policy-expected-usage.txt b/cmd/policy/testdata/policy-expected-usage.txt index fb2070dad..4c1066ae2 100644 --- a/cmd/policy/testdata/policy-expected-usage.txt +++ b/cmd/policy/testdata/policy-expected-usage.txt @@ -3,6 +3,7 @@ Usage: Available Commands: create create policy + decide make a decision delete Delete a policy get Get a policy list List all policies diff --git a/cmd/policy/testdata/policy/decide-expected-usage.txt b/cmd/policy/testdata/policy/decide-expected-usage.txt new file mode 100644 index 000000000..a15dea2ac --- /dev/null +++ b/cmd/policy/testdata/policy/decide-expected-usage.txt @@ -0,0 +1,10 @@ +Usage: + policy decide [flags] + +Flags: + --context string policy context for decision (default "config") + --input string path to input file + +Global Flags: + --owner-id string the id of the owner of a policy + --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/test.yaml b/cmd/policy/testdata/test.yaml new file mode 100644 index 000000000..f09fc49f6 --- /dev/null +++ b/cmd/policy/testdata/test.yaml @@ -0,0 +1 @@ +test: config From 111aaa7b902829a56bf25205711248ab537d1922 Mon Sep 17 00:00:00 2001 From: davidmdm Date: Tue, 14 Jun 2022 14:34:42 -0400 Subject: [PATCH 14/48] fix linting --- api/policy/policy_test.go | 6 +++--- cmd/policy/policy.go | 4 ++-- cmd/policy/policy_test.go | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/api/policy/policy_test.go b/api/policy/policy_test.go index 6dbb566a3..b612a225c 100644 --- a/api/policy/policy_test.go +++ b/api/policy/policy_test.go @@ -763,7 +763,7 @@ func TestMakeDecision(t *testing.T) { "input": "test-input", }) - json.NewEncoder(w).Encode(map[string]string{"status": "PASS"}) + _ = json.NewEncoder(w).Encode(map[string]string{"status": "PASS"}) }, ExpectedDecision: map[string]interface{}{"status": "PASS"}, }, @@ -773,7 +773,7 @@ func TestMakeDecision(t *testing.T) { Request: DecisionRequest{}, Handler: func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(400) - io.WriteString(w, `{"error":"that was a bad request!"}`) + _, _ = io.WriteString(w, `{"error":"that was a bad request!"}`) }, ExpectedError: errors.New("unexpected status-code: 400 - that was a bad request!"), }, @@ -792,7 +792,7 @@ func TestMakeDecision(t *testing.T) { OwnerID: "test-owner", Request: DecisionRequest{}, Handler: func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "not a json response") + _, _ = io.WriteString(w, "not a json response") }, ExpectedError: errors.New("failed to decode response body: invalid character 'o' in literal null (expecting 'u')"), }, diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index cae1e975e..a7638c8b1 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -318,7 +318,7 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { Short: "make a decision", Use: "decide", RunE: func(cmd *cobra.Command, args []string) error { - input, err := os.ReadFile(inputPath) + input, err := ioutil.ReadFile(inputPath) if err != nil { return fmt.Errorf("failed to read file: %w", err) } @@ -342,7 +342,7 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { cmd.Flags().StringVar(&request.Context, "context", "config", "policy context for decision") cmd.Flags().StringVar(&inputPath, "input", "", "path to input file") - cmd.MarkFlagRequired("input") + _ = cmd.MarkFlagRequired("input") return cmd }() diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index c709f3b98..ed9c7df74 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -678,7 +678,7 @@ func TestMakeDecisionCommand(t *testing.T) { "input": "test: config\n", }) - io.WriteString(w, `{"status":"PASS"}`) + _, _ = io.WriteString(w, `{"status":"PASS"}`) }, ExpectedOutput: "{\n \"status\": \"PASS\"\n}\n", }, @@ -697,7 +697,7 @@ func TestMakeDecisionCommand(t *testing.T) { "input": "test: config\n", }) - io.WriteString(w, `{"status":"PASS"}`) + _, _ = io.WriteString(w, `{"status":"PASS"}`) }, ExpectedOutput: "{\n \"status\": \"PASS\"\n}\n", }, @@ -706,7 +706,7 @@ func TestMakeDecisionCommand(t *testing.T) { Args: []string{"decide", "--input", "./testdata/test.yaml", "--owner-id", "test-owner"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) - io.WriteString(w, `{"error":"oopsie!"}`) + _, _ = io.WriteString(w, `{"error":"oopsie!"}`) }, ExpectedErr: "failed to make decision: unexpected status-code: 500 - oopsie!", From f41422c5a405843d8b7cb96d0125d2e1798b838a Mon Sep 17 00:00:00 2001 From: davidmdm Date: Thu, 16 Jun 2022 13:01:24 -0400 Subject: [PATCH 15/48] added comments --- api/policy/policy.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/policy/policy.go b/api/policy/policy.go index 275743998..9ce6e98f8 100644 --- a/api/policy/policy.go +++ b/api/policy/policy.go @@ -273,11 +273,14 @@ func (c Client) GetDecisionLogs(ownerID string, request DecisionQueryRequest) ([ return body, nil } +// DecisionRequest represents a request to Policy-Service to evaluate a given input against an organization's policies. +// The context determines which policies to apply. type DecisionRequest struct { Input string `json:"input"` Context string `json:"context"` } +// MakeDecision sends a requests to Policy-Service public decision endpoint and returns the decision response func (c Client) MakeDecision(ownerID string, req DecisionRequest) (interface{}, error) { payload, err := json.Marshal(req) if err != nil { From fe60bd262cbfed9f960342708eb0baec8879cc0c Mon Sep 17 00:00:00 2001 From: davidmdm Date: Fri, 17 Jun 2022 10:18:34 -0400 Subject: [PATCH 16/48] commented prettyJSONEncoder function --- cmd/policy/policy.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index a7638c8b1..22fa9b103 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -358,6 +358,7 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { return cmd } +// prettyJSONEncoder takes a writer and returns a new json encoder with indent set to two space characters func prettyJSONEncoder(dst io.Writer) *json.Encoder { enc := json.NewEncoder(dst) enc.SetIndent("", " ") From 897ebaf265fcd11d42a08a2e48f6f60505a93af5 Mon Sep 17 00:00:00 2001 From: David Desmarais-Michaud Date: Wed, 29 Jun 2022 09:44:50 -0400 Subject: [PATCH 17/48] update go.mod to declare go1.18 and updated CI to use Go 1.18 for tests (#741) * update go.mod to declare go1.18 and updated CI to use Go 1.18 for tests * use cimg/go:1.18 as go executor * update golangci-lint for compat with go1.18 --- .circleci/config.yml | 17 +++++++++-------- .circleci/install-lint.sh | 2 +- cmd/orb_import_test.go | 2 +- go.mod | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 36a2d13bc..c58cefc93 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ orbs: executors: go: docker: - - image: circleci/golang:1.17 + - image: cimg/go:1.18 environment: CGO_ENABLED: 0 mac: @@ -27,7 +27,7 @@ commands: # https://app.circleci.com/jobs/github/CircleCI-Public/circleci-cli/6480 # curl: (92) HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1) # The issue seems to be on the server-side, so force HTTP 1.1 - name: "cURL: Force HTTP 1.1" + name: 'cURL: Force HTTP 1.1' command: echo '--http1.1' >> ~/.curlrc build-docker-image: steps: @@ -51,7 +51,7 @@ commands: - persist_to_workspace: root: . paths: - - "dist" + - 'dist' - store_artifacts: path: ./dist destination: dist @@ -72,7 +72,7 @@ commands: gomod: steps: - restore_cache: - keys: ["v2-gomod-{{ arch }}-"] + keys: ['v2-gomod-{{ arch }}-'] - run: name: Download go module dependencies command: go mod download @@ -104,8 +104,9 @@ jobs: steps: - checkout - run: | - brew install go@1.13 - echo 'export PATH="/usr/local/opt/go@1.13/bin:$PATH"' >> ~/.bash_profile + brew update + brew install go@1.18 + echo 'export PATH="/usr/local/opt/go@1.18/bin:$PATH"' >> ~/.bash_profile - gomod - run: make test build: @@ -117,7 +118,7 @@ jobs: - persist_to_workspace: root: . paths: - - "build" + - 'build' cucumber: docker: - image: cimg/ruby:2.7 @@ -126,7 +127,7 @@ jobs: - attach_workspace: at: . - run: - name: "Install CLI tool from workspace" + name: 'Install CLI tool from workspace' command: sudo cp ~/project/build/linux/amd64/circleci /usr/local/bin/ - run: command: bundle install diff --git a/.circleci/install-lint.sh b/.circleci/install-lint.sh index 920695e39..964ba8605 100755 --- a/.circleci/install-lint.sh +++ b/.circleci/install-lint.sh @@ -10,6 +10,6 @@ function error() { trap error SIGINT -curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.35.2 +curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.46.2 command -v ./bin/golangci-lint diff --git a/cmd/orb_import_test.go b/cmd/orb_import_test.go index 86ce6c10e..d5c66d20d 100644 --- a/cmd/orb_import_test.go +++ b/cmd/orb_import_test.go @@ -680,7 +680,7 @@ The following orb versions already exist: ` actual, err := ioutil.ReadAll(&b) Expect(err).ShouldNot(HaveOccurred()) - Expect(fmt.Sprintf("%s", actual)).To(Equal(expOutput)) + Expect(string(actual)).To(Equal(expOutput)) }) }) diff --git a/go.mod b/go.mod index 4105a3785..e52db37d7 100644 --- a/go.mod +++ b/go.mod @@ -64,4 +64,4 @@ require ( // fix vulnerability: CVE-2020-15114 in etcd v3.3.10+incompatible replace github.com/coreos/etcd => github.com/coreos/etcd v3.3.24+incompatible -go 1.17 +go 1.18 From 0510d319fba05178b0c5b902fba0085b2e5a3146 Mon Sep 17 00:00:00 2001 From: Sagar Gupta Date: Wed, 29 Jun 2022 15:03:44 -0400 Subject: [PATCH 18/48] [SECENG-582] Make policy decisions locally (#740) --- cmd/policy/policy.go | 56 +- cmd/policy/policy_test.go | 37 +- cmd/policy/testdata/policy-expected-usage.txt | 2 +- .../testdata/policy/create-expected-usage.txt | 2 +- .../testdata/policy/decide-expected-usage.txt | 3 +- .../testdata/policy/delete-expected-usage.txt | 2 +- .../testdata/policy/get-expected-usage.txt | 2 +- .../testdata/policy/list-expected-usage.txt | 2 +- .../testdata/policy/logs-expected-usage.txt | 2 +- .../testdata/policy/update-expected-usage.txt | 2 +- cmd/policy/testdata/{test.yaml => test.yml} | 0 cmd/policy/testdata/test0/config.yml | 1 + cmd/policy/testdata/test0/policy.rego | 3 + go.mod | 53 +- go.sum | 1559 ++++++++++++++++- 15 files changed, 1656 insertions(+), 70 deletions(-) rename cmd/policy/testdata/{test.yaml => test.yml} (100%) create mode 100644 cmd/policy/testdata/test0/config.yml create mode 100644 cmd/policy/testdata/test0/policy.rego diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index 22fa9b103..6da2046cc 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -1,6 +1,7 @@ package policy import ( + "context" "encoding/json" "fmt" "io" @@ -8,8 +9,10 @@ import ( "os" "time" + "github.com/CircleCI-Public/circle-policy-agent/cpa" "github.com/briandowns/spinner" "github.com/spf13/cobra" + "gopkg.in/yaml.v3" "github.com/araddon/dateparse" @@ -31,7 +34,7 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { } policyBaseURL := cmd.PersistentFlags().String("policy-base-url", "https://internal.circleci.com", "base url for policy api") - ownerID := cmd.PersistentFlags().String("owner-id", "", "the id of the owner of a policy") + ownerID := cmd.PersistentFlags().String("owner-id", "", "the id of the policy's owner") if err := cmd.MarkPersistentFlagRequired("owner-id"); err != nil { panic(err) @@ -311,21 +314,31 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { }() decide := func() *cobra.Command { - var inputPath string + var inputPath, policyPath string var request policy.DecisionRequest cmd := &cobra.Command{ Short: "make a decision", Use: "decide", RunE: func(cmd *cobra.Command, args []string) error { + if policyPath == "" && *ownerID == "" { + return fmt.Errorf("--owner-id or --policy is required") + } + + var decision interface{} input, err := ioutil.ReadFile(inputPath) if err != nil { return fmt.Errorf("failed to read file: %w", err) } - request.Input = string(input) - decision, err := policy.NewClient(*policyBaseURL, config).MakeDecision(*ownerID, request) + if policyPath != "" { + // make decision from local policy + decision, err = getPolicyDecisionLocally(policyPath, request.Input) + } else { + // make decision from policy obtained from policy-service + decision, err = policy.NewClient(*policyBaseURL, config).MakeDecision(*ownerID, request) + } if err != nil { return fmt.Errorf("failed to make decision: %w", err) } @@ -333,7 +346,6 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(decision); err != nil { return fmt.Errorf("failed to encode decision: %w", err) } - return nil }, Args: cobra.ExactArgs(0), @@ -341,6 +353,8 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { cmd.Flags().StringVar(&request.Context, "context", "config", "policy context for decision") cmd.Flags().StringVar(&inputPath, "input", "", "path to input file") + cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego policy file or directory containing policy files") + cmd.Flags().StringVar(ownerID, "owner-id", "", "the id of the policy's owner") //Redeclared flag to make optional _ = cmd.MarkFlagRequired("input") @@ -364,3 +378,35 @@ func prettyJSONEncoder(dst io.Writer) *json.Encoder { enc.SetIndent("", " ") return enc } + +//getPolicyDecisionLocally takes path of policy path/directory and input (eg build config) as string, and performs policy evaluation locally +func getPolicyDecisionLocally(policyPath, inputString string) (*cpa.Decision, error) { + var input interface{} + if err := yaml.Unmarshal([]byte(inputString), &input); err != nil { + return nil, fmt.Errorf("invalid input: %w", err) + } + + var parsedPolicy *cpa.Policy + pathInfo, err := os.Stat(policyPath) + if err != nil { + return nil, fmt.Errorf("failed to get path info: %w", err) + } + + //if policyPath is directory get content of all files in this directory (non-recursively) and add to document bundle + if pathInfo.IsDir() { + parsedPolicy, err = cpa.LoadPolicyDirectory(policyPath) + } else { + parsedPolicy, err = cpa.LoadPolicyFile(policyPath) + } + if err != nil { + return nil, fmt.Errorf("failed to load policy files: %w", err) + } + + ctx := context.Background() + decision, err := parsedPolicy.Decide(ctx, input) + if err != nil { + return nil, fmt.Errorf("failed to make decision: %w", err) + } + + return decision, nil +} diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index ed9c7df74..6a45a6e33 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -661,11 +661,11 @@ func TestMakeDecisionCommand(t *testing.T) { { Name: "requires flags", Args: []string{"decide"}, - ExpectedErr: `required flag(s) "input", "owner-id" not set`, + ExpectedErr: `required flag(s) "input" not set`, }, { Name: "sends expected request", - Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test.yaml"}, + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test.yml"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Method, "POST") assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/decision") @@ -684,7 +684,7 @@ func TestMakeDecisionCommand(t *testing.T) { }, { Name: "sends expected request with context", - Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test.yaml", "--context", "custom"}, + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test.yml", "--context", "custom"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Method, "POST") assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/decision") @@ -703,7 +703,7 @@ func TestMakeDecisionCommand(t *testing.T) { }, { Name: "fails on unexpected status code", - Args: []string{"decide", "--input", "./testdata/test.yaml", "--owner-id", "test-owner"}, + Args: []string{"decide", "--input", "./testdata/test.yml", "--owner-id", "test-owner"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) _, _ = io.WriteString(w, `{"error":"oopsie!"}`) @@ -711,6 +711,33 @@ func TestMakeDecisionCommand(t *testing.T) { ExpectedErr: "failed to make decision: unexpected status-code: 500 - oopsie!", }, + { + Name: "fails if neither local-policy nor owner-id is provided", + Args: []string{"decide", "--input", "./testdata/test.yml"}, + ExpectedErr: "--owner-id or --policy is required", + }, + { + Name: "fails for input file not found", + Args: []string{"decide", "--policy", "./testdata/policy.rego", "--input", "./testdata/no_such_file.yml"}, + ExpectedErr: "failed to read file: open ./testdata/no_such_file.yml: ", + }, + { + Name: "fails for policy FILE/DIRECTORY not found", + Args: []string{"decide", "--policy", "./testdata/no_such_file.rego", "--input", "./testdata/test.yml"}, + ExpectedErr: "failed to make decision: failed to get path info: ", + }, + { + Name: "successfully performs decision for policy FILE provided locally", + Args: []string{"decide", "--policy", "./testdata/test0/policy.rego", "--input", + "./testdata/test0/config.yml"}, + ExpectedOutput: `{ + "status": "PASS", + "enabled_rules": [ + "branch_is_main" + ] +} +`, + }, } for _, tc := range testcases { @@ -730,7 +757,7 @@ func TestMakeDecisionCommand(t *testing.T) { if tc.ExpectedErr == "" { assert.NilError(t, err) } else { - assert.Error(t, err, tc.ExpectedErr) + assert.ErrorContains(t, err, tc.ExpectedErr) return } assert.Equal(t, stdout.String(), tc.ExpectedOutput) diff --git a/cmd/policy/testdata/policy-expected-usage.txt b/cmd/policy/testdata/policy-expected-usage.txt index 4c1066ae2..8b3339623 100644 --- a/cmd/policy/testdata/policy-expected-usage.txt +++ b/cmd/policy/testdata/policy-expected-usage.txt @@ -11,7 +11,7 @@ Available Commands: update Update a policy Flags: - --owner-id string the id of the owner of a policy + --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") Use "policy [command] --help" for more information about a command. diff --git a/cmd/policy/testdata/policy/create-expected-usage.txt b/cmd/policy/testdata/policy/create-expected-usage.txt index bea3a7a41..8cd309aaa 100644 --- a/cmd/policy/testdata/policy/create-expected-usage.txt +++ b/cmd/policy/testdata/policy/create-expected-usage.txt @@ -10,5 +10,5 @@ Flags: --policy string path to rego policy file Global Flags: - --owner-id string the id of the owner of a policy + --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/decide-expected-usage.txt b/cmd/policy/testdata/policy/decide-expected-usage.txt index a15dea2ac..76231ff97 100644 --- a/cmd/policy/testdata/policy/decide-expected-usage.txt +++ b/cmd/policy/testdata/policy/decide-expected-usage.txt @@ -4,7 +4,8 @@ Usage: Flags: --context string policy context for decision (default "config") --input string path to input file + --policy string path to rego policy file or directory containing policy files Global Flags: - --owner-id string the id of the owner of a policy + --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/delete-expected-usage.txt b/cmd/policy/testdata/policy/delete-expected-usage.txt index 1d97a99df..fa01cb4a1 100644 --- a/cmd/policy/testdata/policy/delete-expected-usage.txt +++ b/cmd/policy/testdata/policy/delete-expected-usage.txt @@ -5,5 +5,5 @@ Examples: policy delete 60b7e1a5-c1d7-4422-b813-7a12d353d7c6 --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 Global Flags: - --owner-id string the id of the owner of a policy + --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/get-expected-usage.txt b/cmd/policy/testdata/policy/get-expected-usage.txt index 61ad84252..4b5f76a43 100644 --- a/cmd/policy/testdata/policy/get-expected-usage.txt +++ b/cmd/policy/testdata/policy/get-expected-usage.txt @@ -5,5 +5,5 @@ Examples: policy get 60b7e1a5-c1d7-4422-b813-7a12d353d7c6 --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 Global Flags: - --owner-id string the id of the owner of a policy + --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/list-expected-usage.txt b/cmd/policy/testdata/policy/list-expected-usage.txt index 4027ce08f..eea21b829 100644 --- a/cmd/policy/testdata/policy/list-expected-usage.txt +++ b/cmd/policy/testdata/policy/list-expected-usage.txt @@ -8,5 +8,5 @@ Flags: --active (OPTIONAL) filter policies based on active status (true or false) Global Flags: - --owner-id string the id of the owner of a policy + --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/logs-expected-usage.txt b/cmd/policy/testdata/policy/logs-expected-usage.txt index 21cfffa71..81cc8168e 100644 --- a/cmd/policy/testdata/policy/logs-expected-usage.txt +++ b/cmd/policy/testdata/policy/logs-expected-usage.txt @@ -12,5 +12,5 @@ Flags: --project-id string filter decision logs based on project-id Global Flags: - --owner-id string the id of the owner of a policy + --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/update-expected-usage.txt b/cmd/policy/testdata/policy/update-expected-usage.txt index 61caa34f0..45180fb07 100644 --- a/cmd/policy/testdata/policy/update-expected-usage.txt +++ b/cmd/policy/testdata/policy/update-expected-usage.txt @@ -11,5 +11,5 @@ Flags: --policy string path to rego file containing the updated policy Global Flags: - --owner-id string the id of the owner of a policy + --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/test.yaml b/cmd/policy/testdata/test.yml similarity index 100% rename from cmd/policy/testdata/test.yaml rename to cmd/policy/testdata/test.yml diff --git a/cmd/policy/testdata/test0/config.yml b/cmd/policy/testdata/test0/config.yml new file mode 100644 index 000000000..a43817612 --- /dev/null +++ b/cmd/policy/testdata/test0/config.yml @@ -0,0 +1 @@ +branch: main \ No newline at end of file diff --git a/cmd/policy/testdata/test0/policy.rego b/cmd/policy/testdata/test0/policy.rego new file mode 100644 index 000000000..9e2890022 --- /dev/null +++ b/cmd/policy/testdata/test0/policy.rego @@ -0,0 +1,3 @@ +package org +enable_rule["branch_is_main"] +branch_is_main = "branch must be main!" { input.branch != "main" } \ No newline at end of file diff --git a/go.mod b/go.mod index e52db37d7..4aa647954 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,7 @@ module github.com/CircleCI-Public/circleci-cli require ( github.com/AlecAivazis/survey/v2 v2.1.1 + github.com/CircleCI-Public/circle-policy-agent v0.0.54 github.com/Masterminds/semver v1.4.2 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/blang/semver v3.5.1+incompatible @@ -13,31 +14,35 @@ require ( github.com/google/uuid v1.3.0 github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect github.com/mattn/go-isatty v0.0.12 // indirect - github.com/mitchellh/mapstructure v1.1.2 - github.com/olekukonko/tablewriter v0.0.4 - github.com/onsi/ginkgo v1.12.1 - github.com/onsi/gomega v1.10.4 + github.com/mitchellh/mapstructure v1.4.1 + github.com/olekukonko/tablewriter v0.0.5 + github.com/onsi/ginkgo v1.16.4 + github.com/onsi/gomega v1.17.0 github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 - github.com/pkg/errors v0.8.1 + github.com/pkg/errors v0.9.1 github.com/rhysd/go-github-selfupdate v0.0.0-20180520142321-41c1bbb0804a - github.com/spf13/cobra v0.0.5 + github.com/spf13/cobra v1.4.0 github.com/spf13/pflag v1.0.5 github.com/tcnksm/go-gitconfig v0.1.2 // indirect github.com/ulikunitz/xz v0.5.9 // indirect - golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc // indirect + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect google.golang.org/appengine v1.6.7 // indirect - gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c - gotest.tools/v3 v3.0.2 + gopkg.in/yaml.v3 v3.0.1 + gotest.tools/v3 v3.0.3 ) require ( + github.com/OneOfOne/xxhash v1.2.8 // indirect + github.com/agnivade/levenshtein v1.1.1 // indirect github.com/emirpasic/gods v1.12.0 // indirect - github.com/fsnotify/fsnotify v1.4.7 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/ghodss/yaml v1.0.0 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.0.0 // indirect - github.com/golang/protobuf v1.4.2 // indirect - github.com/google/go-cmp v0.4.0 // indirect - github.com/imdario/mergo v0.3.9 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.7 // indirect + github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect @@ -46,19 +51,25 @@ require ( github.com/mattn/go-runewidth v0.0.10 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/nxadm/tail v1.4.4 // indirect + github.com/nxadm/tail v1.4.8 // indirect + github.com/open-policy-agent/opa v0.41.0 // indirect + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.1.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect + github.com/vektah/gqlparser/v2 v2.4.5 // indirect github.com/xanzy/ssh-agent v0.2.1 // indirect - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect - golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb // indirect - golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba // indirect - golang.org/x/text v0.3.3 // indirect - golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect - google.golang.org/protobuf v1.23.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/yashtewari/glob-intersection v0.1.0 // indirect + golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect + golang.org/x/net v0.0.0-20220621193019-9d032be2e588 // indirect + golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/text v0.3.7 // indirect + google.golang.org/protobuf v1.28.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) // fix vulnerability: CVE-2020-15114 in etcd v3.3.10+incompatible diff --git a/go.sum b/go.sum index 7bfc99641..5ee85ddb4 100644 --- a/go.sum +++ b/go.sum @@ -1,39 +1,425 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= +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= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= github.com/AlecAivazis/survey/v2 v2.1.1 h1:LEMbHE0pLj75faaVEKClEX1TM4AJmmnOh9eimREzLWI= github.com/AlecAivazis/survey/v2 v2.1.1/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v56.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/CircleCI-Public/circle-policy-agent v0.0.54 h1:Z1eHtqm/l8Az28i22s3+O7nK/APRtSmrspUDEG3UWmU= +github.com/CircleCI-Public/circle-policy-agent v0.0.54/go.mod h1:9sif3N0AlIuqJ9A4PJ90wrvUZyAuNBmywYxScPHOY6E= github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= +github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= +github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= +github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= +github.com/Microsoft/hcsshim v0.9.3/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= +github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= +github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= +github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= +github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +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-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= +github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +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/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.44.40/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +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/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/briandowns/spinner v1.18.1 h1:yhQmQtM1zsqFsouh09Bk/jCjd50pC3EOGsh28gLVvwY= github.com/briandowns/spinner v1.18.1/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU= +github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/bytecodealliance/wasmtime-go v0.36.0 h1:B6thr7RMM9xQmouBtUqm1RpkJjuLS37m6nxX+iwsQSc= +github.com/bytecodealliance/wasmtime-go v0.36.0/go.mod h1:q320gUxqyI8yB+ZqRuaJOEnGkAnHh6WtJjMaT2CW4wI= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= +github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= +github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= +github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= +github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= +github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= +github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= +github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= +github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= +github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= +github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= +github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= +github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= +github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= +github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= +github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= +github.com/containerd/containerd v1.6.4/go.mod h1:oWOqbuJUZmOVafhA0lj2NAXbiO1u7F0K5l1bUgdyo94= +github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= +github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= +github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= +github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= +github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= +github.com/containerd/go-cni v1.1.5/go.mod h1:Rf2ZrMycr1El589IyuRzn7RkfdRZVKaFGaxSDHVAjj0= +github.com/containerd/go-cni v1.1.6/go.mod h1:BWtoWl5ghVymxu6MBjg79W9NZrCRyHIdUtk4cauMe34= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= +github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= +github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= +github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= +github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4= +github.com/containerd/imgcrypt v1.1.4/go.mod h1:LorQnPtzL/T0IyCeftcsMEO7AqxUDbdO8j/tSUpgxvo= +github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= +github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= +github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= +github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= +github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= +github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= +github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= +github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= +github.com/containernetworking/cni v1.1.0/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= +github.com/containernetworking/cni v1.1.1/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= +github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= +github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= +github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19sZPp3ry5uHSkI4LPxV8= +github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= +github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= +github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/containers/ocicrypt v1.1.3/go.mod h1:xpdkbVAuaH3WzbEabUd5yDsl9SwJA5pABH85425Es2g= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.24+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +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/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +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/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= +github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= +github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= +github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= +github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= 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/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/dgraph-io/badger/v3 v3.2103.2 h1:dpyM5eCJAtQCBcMCZcT4UBZchuTJgCywerHHgmxfxM8= +github.com/dgraph-io/badger/v3 v3.2103.2/go.mod h1:RHo4/GmYcKKh5Lxu63wLEMHJ70Pac2JqZRYGhlyAo2M= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684/go.mod h1:UfCu3YXJJCI+IdnqGgYP82dk2+Joxmv+mUTVBES6wac= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.11+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897 h1:E52jfcE64UG42SwLmrW0QByONfGynWuzBvm86BoB9z8= +github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= 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/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= @@ -44,181 +430,1292 @@ github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk= github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-ini/ini v1.66.6/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +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/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +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-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= +github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 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/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +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/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= +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 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/go-github v15.0.0+incompatible h1:jlPg2Cpsxb/FyEV/MFiIE9tW/2RAevQNZDPeHbf5a94= github.com/google/go-github v15.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.7.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/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.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 v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +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.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/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +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/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8= github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= +github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +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.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +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/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY= +github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +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/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ= github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +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.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 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.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +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-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= +github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= +github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= +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/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/networkplumbing/go-nft v0.2.0/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= -github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/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.4 h1:NiTx7EEvBzu9sFOD1zORteLSt3o8gnlvZZwSE9TnY9U= -github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/open-policy-agent/opa v0.41.0 h1:XDTkP8bcUVuY8WOVbRY4e/KZW31+f+/cxisPc0TPe5E= +github.com/open-policy-agent/opa v0.41.0/go.mod h1:+kB8/8/4meTlq6ZmYRnvrL5nNrykd2eckDx4O6rk/dA= +github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= +github.com/opencontainers/runc v1.1.1/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= +github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= +github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= +github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/opencontainers/selinux v1.10.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 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/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/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.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rhysd/go-github-selfupdate v0.0.0-20180520142321-41c1bbb0804a h1:YNh/SV+Z0p7kQDUE9Ux+46ruTucvQP43XB06DfZa8Es= github.com/rhysd/go-github-selfupdate v0.0.0-20180520142321-41c1bbb0804a/go.mod h1:mOFQaTkPA4plTgFW6Gnejb/RsEIqAoIqOACC2XaZX04= github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +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/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +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/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM= +github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw= github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I= github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +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/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vektah/gqlparser/v2 v2.4.4/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0= +github.com/vektah/gqlparser/v2 v2.4.5 h1:C02NsyEsL4TXJB7ndonqTfuQOL4XPIu0aAWugdmTgmc= +github.com/vektah/gqlparser/v2 v2.4.5/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0= +github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yashtewari/glob-intersection v0.1.0 h1:6gJvMYQlTDOL3dMsPF6J0+26vwX9MB8/1q3uAdhmTrg= +github.com/yashtewari/glob-intersection v0.1.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= +go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= +go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.32.0/go.mod h1:5eCOqeGphOyz6TsY3ZDNjE33SM/TFAK3RGuCL2naTgY= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= +go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= +go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0/go.mod h1:M1hVZHNxcbkAlcvrOMlpQ4YOO3Awf+4N2dxkZL3xm04= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0/go.mod h1:ceUgdyfNv4h4gLxHR0WNfDiiVmZFodZhZSbOLhpxqXE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0/go.mod h1:E+/KKhwOSw8yoPxSSuUHG6vKppkvhN+S1Jc7Nib3k3o= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/metric v0.30.0/go.mod h1:/ShZ7+TS4dHzDFmfi1kSXMhMVubNoP0oIaBp70J6UXU= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= +go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU= +go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= +go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= +go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= +go.opentelemetry.io/proto/otlp v0.16.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +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.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/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-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +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-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +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-20181011144130-49bb7cea24b1/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-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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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-20190619014844-b5b0513f8c1b/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-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/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-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc h1:3ElrZeO6IBP+M8kgu5YFwRo92Gqr+zBg3aooYQ6ziqU= -golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220621193019-9d032be2e588 h1:9ubFuySsnAJYGyJrZ3koiEv8FyqofCBdz3G9Mbf2YFc= +golang.org/x/net v0.0.0-20220621193019-9d032be2e588/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +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/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/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-20181205085412-a5c9d58dba9a/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-20190221075227-b4e8571b14e0/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-20190312061237-fead79001313/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-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba h1:AyHWHCBVlIYI5rgEM3o+1PLd0sLPcIAoaUckGQMaWtw= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190312151545-0bb0c0a6e846/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-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/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-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +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.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= +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-20190418145605-e7d98fc518a7/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-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220622131801-db39fadba55f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +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.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +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.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= 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 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +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-20141024133853-64131543e789/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +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/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 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.3/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 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/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-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= -gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/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= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= +k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= +k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= +k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= +k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg= +k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= +k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= +k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= +k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= +k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= +k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= +k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= +k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= +k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= +k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= +k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y= +k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= +k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= +k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= +k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= +k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= +k8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI= +k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= +k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= +k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= +k8s.io/kube-openapi v0.0.0-20220621154418-c39d0f63fac8/go.mod h1:PNbiP2hKArDh8cgJZTDL6Ss/z3wsbga8yjj/7VMB+I4= +k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +oras.land/oras-go v1.1.1/go.mod h1:n2TE1ummt9MUyprGhT+Q7kGZUF4kVUpYysPFxeV2IpQ= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= From a498b267871118dae13fd37260ee8fb0aa5673cc Mon Sep 17 00:00:00 2001 From: Kyle T <33272306+KyleTryon@users.noreply.github.com> Date: Thu, 31 Mar 2022 10:05:16 -0400 Subject: [PATCH 19/48] feat: Upgrade Golang version in Dockerfile The original image was based on a deprecated version of Ubuntu. Testing on 1.17 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1414f61c4..184f6871b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM circleci/golang:1.10.3 +FROM cimg/go:1.17 ENV CIRCLECI_CLI_SKIP_UPDATE_CHECK true From e08d27192246fa64af49eb2f2d7b82fbf4f18196 Mon Sep 17 00:00:00 2001 From: Liene Verzemnieks Date: Thu, 30 Jun 2022 14:34:16 -0700 Subject: [PATCH 20/48] update image version The existing pull request is several months old, and as such there is a newer version of this (newer) image available now; let's use that instead. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 184f6871b..c94a4659f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM cimg/go:1.17 +FROM cimg/go:1.18.3 ENV CIRCLECI_CLI_SKIP_UPDATE_CHECK true From f5cf3f29c0ca9255c356cd406efd9baa48030102 Mon Sep 17 00:00:00 2001 From: Liene Verzemnieks Date: Thu, 30 Jun 2022 15:24:12 -0700 Subject: [PATCH 21/48] update cache keys The "restore cache" step is taking far, far too long compared to previous runs (total run 40-60 minutes or more, versus under 4 minutes previously). Consistent with our [documentation](https://circleci.com/docs/2.0/caching#managing-caches), since we've updated the Go version significantly, we should also update our cache keys so we can generate a new cache. --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c58cefc93..2a8240421 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -62,7 +62,7 @@ commands: default: https://github.com/goreleaser/goreleaser/releases/download/v0.127.0/goreleaser_amd64.deb steps: - restore_cache: - keys: [v4-goreleaser-] + keys: [v5-goreleaser-] - run: name: Install GoReleaser command: | @@ -72,7 +72,7 @@ commands: gomod: steps: - restore_cache: - keys: ['v2-gomod-{{ arch }}-'] + keys: ['v3-gomod-{{ arch }}-'] - run: name: Download go module dependencies command: go mod download From 8fdf6699e87bc329c7cb4cd1bccb4bbac9f88b5c Mon Sep 17 00:00:00 2001 From: William Yardley Date: Sat, 21 May 2022 09:56:50 -0700 Subject: [PATCH 22/48] ci: use golangci-lint docker image directly - Directly use golangci-lint in Docker in CI - Remove unused lint scripts and Makefile targets - Update Makefile to call golangci-lint directly for lint target (for local use) - Update golangci-lint version from 1.35 to 1.46 - Remove golangmetalinter config, which was seemingly ignored, and add a config for golangci-lint - Resolve one new lint warning from a test case - Update formatting on files that were not properly formatted --- .circleci/config.yml | 8 ++++---- .circleci/install-lint.sh | 15 --------------- .circleci/lint.sh | 14 -------------- .golangci.yml | 36 ++++++++++++++++++++++++++++++++++++ .gometalinter.json | 38 -------------------------------------- Makefile | 7 +------ api/api_test.go | 2 +- api/context_graphql.go | 4 ++-- api/context_test.go | 2 +- api/rest/client.go | 4 ++-- api/rest/client_test.go | 8 ++++---- api/runner/runner_test.go | 8 ++++---- api/schedule_test.go | 6 +++--- cmd/check_test.go | 2 +- cmd/config.go | 2 +- cmd/namespace.go | 14 +++++++------- cmd/orb.go | 7 +++---- cmd/update.go | 2 +- cmd/update_test.go | 8 ++++---- 19 files changed, 75 insertions(+), 112 deletions(-) delete mode 100755 .circleci/install-lint.sh delete mode 100644 .circleci/lint.sh create mode 100644 .golangci.yml delete mode 100644 .gometalinter.json diff --git a/.circleci/config.yml b/.circleci/config.yml index 2a8240421..e23c022c3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -172,12 +172,12 @@ jobs: - run: ./.circleci/deploy-gh-pages.sh lint: - executor: go + docker: + - image: golangci/golangci-lint:v1.46-alpine + resource_class: large steps: - checkout - - run: make install-lint - - run: make build - - run: make lint + - run: golangci-lint run deploy-test: executor: go diff --git a/.circleci/install-lint.sh b/.circleci/install-lint.sh deleted file mode 100755 index 964ba8605..000000000 --- a/.circleci/install-lint.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o pipefail -set -o nounset - -function error() { - echo "An error occured installing golangci-lint." -} - -trap error SIGINT - -curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.46.2 - -command -v ./bin/golangci-lint diff --git a/.circleci/lint.sh b/.circleci/lint.sh deleted file mode 100644 index f9c5aedac..000000000 --- a/.circleci/lint.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o pipefail -set -o nounset - -function error() { - echo "An error occured running golangci-lint." - echo "Have you run \"make install-lint\"?" -} - -trap error SIGINT - -./bin/golangci-lint run diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 000000000..0e2823522 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,36 @@ +--- +service: + golangci-lint-version: 1.46.x + +linters: + enable: + - deadcode + - errcheck + - goconst + - gofmt + - goimports + # - gosec + - gosimple + - govet + - ineffassign + - megacheck + - misspell + - nakedret + # - revive + - staticcheck + - structcheck + - typecheck + - unconvert + # - unparam + - unused + - varcheck + - vet + - vetshadow + +# Instead of disabling tests entirely, just ignore goconst, which is the only +# one with issues there currently. +issues: + exclude-rules: + - path: (.+)_test.go + linters: + - goconst diff --git a/.gometalinter.json b/.gometalinter.json deleted file mode 100644 index 77b13bda8..000000000 --- a/.gometalinter.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "Vendor": true, - "Deadline": "5m", - "Concurrency": 2, - "Linters": { - "gofmt": {"Command": "gofmt -l -s -w"}, - "goimports": {"Command": "goimports -l -w"} - }, - - "Exclude": [ - "md_docs/*" - ], - - "Enable": [ - "deadcode", - "errcheck", - "goconst", - "gocyclo", - "gofmt", - "goimports", - "golint", - "gosec", - "gosimple", - "gotype", - "gotypex", - "ineffassign", - "interfacer", - "megacheck", - "misspell", - "nakedret", - "structcheck", - "unconvert", - "unparam", - "varcheck", - "vet", - "vetshadow" - ] -} diff --git a/Makefile b/Makefile index 831dad5df..cd4d130de 100644 --- a/Makefile +++ b/Makefile @@ -26,16 +26,11 @@ cover: .PHONY: lint lint: - bash .circleci/lint.sh + golangci-lint run .PHONY: doc doc: godoc -http=:6060 -.PHONY: install-lint -install-lint: - bash .circleci/install-lint.sh - - .PHONY: always always: diff --git a/api/api_test.go b/api/api_test.go index ddd32bac8..1d32f7d39 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -61,7 +61,7 @@ func TestFollowProject(t *testing.T) { expErr: "invalid character '/'", }, { - label: "returns a followed project succesfully", + label: "returns a followed project successfully", transportFn: func(r *http.Request) (*http.Response, error) { if r.URL.String() != "https://circleci.com/api/v1.1/project/github/test-user/test-project/follow" { panic(fmt.Sprintf("unexpected url: %s", r.URL.String())) diff --git a/api/context_graphql.go b/api/context_graphql.go index 48d13c94b..b1d2a2c02 100644 --- a/api/context_graphql.go +++ b/api/context_graphql.go @@ -285,7 +285,7 @@ func (c *GraphQLContextClient) DeleteEnvironmentVariable(contextId, variableName } err := cl.Run(request, &response) - return errors.Wrap(improveVcsTypeError(err), "failed to delete environment varaible") + return errors.Wrap(improveVcsTypeError(err), "failed to delete environment variable") } // CreateEnvironmentVariable creates a new environment variable in the given @@ -343,7 +343,7 @@ func (c *GraphQLContextClient) CreateEnvironmentVariable(contextId, variableName } if err := cl.Run(request, &response); err != nil { - return errors.Wrap(improveVcsTypeError(err), "failed to store environment varaible in context") + return errors.Wrap(improveVcsTypeError(err), "failed to store environment variable in context") } if response.StoreEnvironmentVariable.Error.Type != "" { diff --git a/api/context_test.go b/api/context_test.go index 980a18b8e..fb6d188dd 100644 --- a/api/context_test.go +++ b/api/context_test.go @@ -188,7 +188,7 @@ var _ = ginkgo.Describe("API", func() { list.Organization.Id = "C3D79A95-6BD5-40B4-9958-AB6BDC4CAD50" list.Organization.Contexts.Edges = []struct{ Node circleCIContext }{ - struct{ Node circleCIContext }{ + { Node: ctx, }, } diff --git a/api/rest/client.go b/api/rest/client.go index 3474d523a..5ce649f66 100644 --- a/api/rest/client.go +++ b/api/rest/client.go @@ -99,8 +99,8 @@ func (c *Client) DoRequest(req *http.Request, resp interface{}) (statusCode int, } type HTTPError struct { - Code int - Message string + Code int + Message string } func (e *HTTPError) Error() string { diff --git a/api/rest/client_test.go b/api/rest/client_test.go index 335a97322..619dc87ea 100644 --- a/api/rest/client_test.go +++ b/api/rest/client_test.go @@ -61,7 +61,7 @@ func TestClient_DoRequest(t *testing.T) { defer cleanup() t.Run("Check result", func(t *testing.T) { - r, err := c.NewRequest("GET", &url.URL{Path: "my/error/endpoint"}, nil) + r, err := c.NewRequest(http.MethodGet, &url.URL{Path: "my/error/endpoint"}, nil) assert.NilError(t, err) resp := make(map[string]interface{}) @@ -73,7 +73,7 @@ func TestClient_DoRequest(t *testing.T) { t.Run("Check request", func(t *testing.T) { assert.Check(t, cmp.Equal(fix.URL(), url.URL{Path: "/api/v2/my/error/endpoint"})) - assert.Check(t, cmp.Equal(fix.Method(), "GET")) + assert.Check(t, cmp.Equal(fix.Method(), http.MethodGet)) assert.Check(t, cmp.DeepEqual(fix.Header(), http.Header{ "Accept-Encoding": {"gzip"}, "Accept-Type": {"application/json"}, @@ -90,7 +90,7 @@ func TestClient_DoRequest(t *testing.T) { defer cleanup() t.Run("Check result", func(t *testing.T) { - r, err := c.NewRequest("GET", &url.URL{Path: "path"}, nil) + r, err := c.NewRequest(http.MethodGet, &url.URL{Path: "path"}, nil) assert.NilError(t, err) resp := make(map[string]interface{}) @@ -105,7 +105,7 @@ func TestClient_DoRequest(t *testing.T) { t.Run("Check request", func(t *testing.T) { assert.Check(t, cmp.Equal(fix.URL(), url.URL{Path: "/api/v2/path"})) - assert.Check(t, cmp.Equal(fix.Method(), "GET")) + assert.Check(t, cmp.Equal(fix.Method(), http.MethodGet)) assert.Check(t, cmp.DeepEqual(fix.Header(), http.Header{ "Accept-Encoding": {"gzip"}, "Accept-Type": {"application/json"}, diff --git a/api/runner/runner_test.go b/api/runner/runner_test.go index 811a70a14..cf9755b4f 100644 --- a/api/runner/runner_test.go +++ b/api/runner/runner_test.go @@ -82,7 +82,7 @@ func TestRunner_GetResourceClassByName(t *testing.T) { t.Run("Check request", func(t *testing.T) { assert.Check(t, cmp.Equal(fix.URL(), url.URL{Path: "/api/v2/runner/resource", RawQuery: "namespace=the-namespace"})) - assert.Check(t, cmp.Equal(fix.method, "GET")) + assert.Check(t, cmp.Equal(fix.method, http.MethodGet)) assert.Check(t, cmp.DeepEqual(fix.Header(), http.Header{ "Accept-Encoding": {"gzip"}, "Accept-Type": {"application/json"}, @@ -133,7 +133,7 @@ func TestRunner_GetResourceClassesByNamespace(t *testing.T) { t.Run("Check request", func(t *testing.T) { assert.Check(t, cmp.Equal(fix.URL(), url.URL{Path: "/api/v2/runner/resource", RawQuery: "namespace=the-namespace"})) - assert.Check(t, cmp.Equal(fix.method, "GET")) + assert.Check(t, cmp.Equal(fix.method, http.MethodGet)) assert.Check(t, cmp.DeepEqual(fix.Header(), http.Header{ "Accept-Encoding": {"gzip"}, "Accept-Type": {"application/json"}, @@ -279,7 +279,7 @@ func TestRunner_GetRunnerTokensByResourceClass(t *testing.T) { t.Run("Check request", func(t *testing.T) { assert.Check(t, cmp.Equal(fix.URL(), url.URL{Path: "/api/v2/runner/token", RawQuery: "resource-class=the-namespace%2Fthe-resource-class"})) - assert.Check(t, cmp.Equal(fix.method, "GET")) + assert.Check(t, cmp.Equal(fix.method, http.MethodGet)) assert.Check(t, cmp.DeepEqual(fix.Header(), http.Header{ "Accept-Encoding": {"gzip"}, "Accept-Type": {"application/json"}, @@ -390,7 +390,7 @@ func TestRunner_GetRunnerInstances_ByNamespace(t *testing.T) { t.Run("Check request", func(t *testing.T) { assert.Check(t, cmp.Equal(fix.URL(), url.URL{Path: "/api/v2/runner", RawQuery: "namespace=the-namespace"})) - assert.Check(t, cmp.Equal(fix.method, "GET")) + assert.Check(t, cmp.Equal(fix.method, http.MethodGet)) assert.Check(t, cmp.DeepEqual(fix.Header(), http.Header{ "Accept-Encoding": {"gzip"}, "Accept-Type": {"application/json"}, diff --git a/api/schedule_test.go b/api/schedule_test.go index 432595e85..c046b4321 100644 --- a/api/schedule_test.go +++ b/api/schedule_test.go @@ -67,7 +67,7 @@ func TestSchedules(t *testing.T) { if r.URL.String() != "https://circleci.com/api/v2/project/github/test-org/test-project/schedule" { panic(fmt.Sprintf("unexpected url: %s", r.URL.String())) } - if r.Method != "GET" { + if r.Method != http.MethodGet { panic(fmt.Sprintf("unexpected method: %s", r.Method)) } return mock.NewHTTPResponse(200, formatListResponse([]Schedule{mockSchedule()})), nil @@ -90,7 +90,7 @@ func TestScheduleByID(t *testing.T) { if r.URL.String() != "https://circleci.com/api/v2/schedule/07f08dea-de06-48d4-9b47-9639229b7d24" { panic(fmt.Sprintf("unexpected url: %s", r.URL.String())) } - if r.Method != "GET" { + if r.Method != http.MethodGet { panic(fmt.Sprintf("unexpected method: %s", r.Method)) } return mock.NewHTTPResponse(200, mockScheduleString()), nil @@ -114,7 +114,7 @@ func TestScheduleByName(t *testing.T) { if r.URL.String() != "https://circleci.com/api/v2/project/github/test-org/test-project/schedule" { panic(fmt.Sprintf("unexpected url: %s", r.URL.String())) } - if r.Method != "GET" { + if r.Method != http.MethodGet { panic(fmt.Sprintf("unexpected method: %s", r.Method)) } return mock.NewHTTPResponse(200, formatListResponse([]Schedule{mockSchedule()})), nil diff --git a/cmd/check_test.go b/cmd/check_test.go index 1c4ad928b..5b9a419a4 100644 --- a/cmd/check_test.go +++ b/cmd/check_test.go @@ -96,7 +96,7 @@ var _ = Describe("Check", func() { tempSettings.TestServer.AppendHandlers( ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", "/repos/CircleCI-Public/circleci-cli/releases"), + ghttp.VerifyRequest(http.MethodGet, "/repos/CircleCI-Public/circleci-cli/releases"), ghttp.RespondWith(http.StatusOK, response), ), ) diff --git a/cmd/config.go b/cmd/config.go index be7523c84..40344a079 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -178,7 +178,7 @@ func processConfig(opts configOptions, flags *pflag.FlagSet) error { var err error if len(paramsYaml) > 0 { - // The 'src' value can be a filepath, or a yaml string. If the file cannot be read sucessfully, + // The 'src' value can be a filepath, or a yaml string. If the file cannot be read successfully, // proceed with the assumption that the value is already valid yaml. raw, err := ioutil.ReadFile(paramsYaml) if err != nil { diff --git a/cmd/namespace.go b/cmd/namespace.go index 682f832fb..0e5fb8cc5 100644 --- a/cmd/namespace.go +++ b/cmd/namespace.go @@ -19,7 +19,7 @@ type namespaceOptions struct { // Allows user to skip y/n confirm when creating a namespace noPrompt bool - orgID *string + orgID *string // This lets us pass in our own interface for testing tty createNamespaceUserInterface // Linked with --integration-testing flag for stubbing UI in gexec tests @@ -57,7 +57,7 @@ func newNamespaceCommand(config *settings.Config) *cobra.Command { } createCmd := &cobra.Command{ - Use: "create [] []", + Use: "create [] []", Short: "Create a namespace", Long: `Create a namespace. Please note that at this time all namespaces created in the registry are world-readable.`, @@ -76,7 +76,7 @@ Please note that at this time all namespaces created in the registry are world-r return createNamespace(cmd, opts) }, - Args: cobra.RangeArgs(1, 3), + Args: cobra.RangeArgs(1, 3), Annotations: make(map[string]string), Example: ` circleci namespace create NamespaceName github OrgName circleci namespace create NamespaceName --org-id "your-org-id-here"`, @@ -157,17 +157,17 @@ To change the namespace, you will have to contact CircleCI customer support. return nil } -func createNamespace(cmd *cobra.Command,opts namespaceOptions) error { +func createNamespace(cmd *cobra.Command, opts namespaceOptions) error { namespaceName := opts.args[0] //skip if no orgid provided - if opts.orgID != nil && strings.TrimSpace(*opts.orgID) != ""{ + if opts.orgID != nil && strings.TrimSpace(*opts.orgID) != "" { _, err := uuid.Parse(*opts.orgID) if err == nil { return createNamespaceWithOrgId(opts, namespaceName, *opts.orgID) } - //skip if no vcs type and org name provided - } else if len(opts.args) == 3{ + //skip if no vcs type and org name provided + } else if len(opts.args) == 3 { return createNamespaceWithVcsTypeAndOrgName(opts, namespaceName, opts.args[1], opts.args[2]) } return cmd.Help() diff --git a/cmd/orb.go b/cmd/orb.go index cfa9fd647..231abfe5f 100644 --- a/cmd/orb.go +++ b/cmd/orb.go @@ -76,7 +76,7 @@ type createOrbTestUI struct { type orbProtectTemplateRelease struct { ZipUrl string `json:"zipball_url"` - Name string `json:"name"` + Name string `json:"name"` } func (ui createOrbTestUI) askUserToConfirm(message string) bool { @@ -1086,7 +1086,6 @@ func initOrb(opts orbOptions) error { } } - latestRelease := releaseTags[0] resp, err := http.Get(latestRelease.ZipUrl) if err != nil { @@ -1342,7 +1341,7 @@ func initOrb(opts orbOptions) error { return err } - orbRoot, err := ioutil.ReadFile(path.Join(orbPath, "src", "@orb.yml")) + orbRoot, err := ioutil.ReadFile(path.Join(orbPath, "src", "@orb.yml")) if err != nil { return err } @@ -1521,7 +1520,7 @@ func unzipToOrbPath(src, dest string) error { } }() - // This is neccesary because the zip downloaded from GitHub will have a + // This is necessary because the zip downloaded from GitHub will have a // directory with the actual template, rather than the template being // top-level. pathParts := strings.Split(f.Name, "/") diff --git a/cmd/update.go b/cmd/update.go index 1251d6fb3..c43bf33d4 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -73,7 +73,7 @@ func newUpdateCommand(config *settings.Config) *cobra.Command { update.AddCommand(&cobra.Command{ Use: "build-agent", Hidden: true, - Short: "This command has no effect, and is kept for backwards compatiblity", + Short: "This command has no effect, and is kept for backwards compatibility", PersistentPreRun: func(_ *cobra.Command, _ []string) { opts.cfg.SkipUpdateCheck = true }, diff --git a/cmd/update_test.go b/cmd/update_test.go index d0dba1aef..431987119 100644 --- a/cmd/update_test.go +++ b/cmd/update_test.go @@ -60,7 +60,7 @@ var _ = Describe("Update", func() { tempSettings.TestServer.AppendHandlers( ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", "/repos/CircleCI-Public/circleci-cli/releases"), + ghttp.VerifyRequest(http.MethodGet, "/repos/CircleCI-Public/circleci-cli/releases"), ghttp.RespondWith(http.StatusOK, response), ), ) @@ -132,11 +132,11 @@ var _ = Describe("Update", func() { tempSettings.TestServer.AppendHandlers( ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", "/repos/CircleCI-Public/circleci-cli/releases"), + ghttp.VerifyRequest(http.MethodGet, "/repos/CircleCI-Public/circleci-cli/releases"), ghttp.RespondWith(http.StatusOK, response), ), ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", "/repos/CircleCI-Public/circleci-cli/releases/assets/1"), + ghttp.VerifyRequest(http.MethodGet, "/repos/CircleCI-Public/circleci-cli/releases/assets/1"), ghttp.RespondWith(http.StatusOK, assetResponse), ), ) @@ -166,7 +166,7 @@ var _ = Describe("Update", func() { tempSettings.TestServer.Reset() tempSettings.TestServer.AppendHandlers( ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", "/repos/CircleCI-Public/circleci-cli/releases"), + ghttp.VerifyRequest(http.MethodGet, "/repos/CircleCI-Public/circleci-cli/releases"), ghttp.RespondWith(http.StatusForbidden, []byte("Forbidden")), ), ) From f3c17207b51e455c0c001ca9e7aca02d9d8a4216 Mon Sep 17 00:00:00 2001 From: corinnesollows Date: Mon, 4 Jul 2022 10:30:33 -0300 Subject: [PATCH 23/48] EXT-280 - EXT-280-open-project-vcs (#744) * formatting file * Warning Error --- cmd/open.go | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/cmd/open.go b/cmd/open.go index 52d134b72..8300aec65 100644 --- a/cmd/open.go +++ b/cmd/open.go @@ -11,6 +11,12 @@ import ( "github.com/spf13/cobra" ) +// errorMessage string containing the error message displayed in both the open command and the follow command +var errorMessage = ` +This command is intended to be run from a git repository with a remote named 'origin' that is hosted on Github or Bitbucket only. +We are not currently supporting any other hosts.` + +//projectUrl uses the provided values to create the url to open func projectUrl(remote *git.Remote) string { return fmt.Sprintf("https://app.circleci.com/pipelines/%s/%s/%s", url.PathEscape(strings.ToLower(string(remote.VcsType))), @@ -18,24 +24,22 @@ func projectUrl(remote *git.Remote) string { url.PathEscape(remote.Project)) } -var errorMessage = ` -Unable detect which URL should be opened. This command is intended to be run from -a git repository with a remote named 'origin' that is hosted on Github or Bitbucket -Error` - +//openProjectInBrowser takes the created url and opens a browser to it func openProjectInBrowser() error { - remote, err := git.InferProjectFromGitRemotes() - if err != nil { return errors.Wrap(err, errorMessage) } - - return browser.OpenURL(projectUrl(remote)) + //check that project url contains github or bitbucket; our legacy vcs + if remote.VcsType == git.GitHub || remote.VcsType == git.Bitbucket { + return browser.OpenURL(projectUrl(remote)) + } + //if not warn user their vcs is not supported + return errors.New(errorMessage) } +// newOpenCommand creates the cli command open func newOpenCommand() *cobra.Command { - openCommand := &cobra.Command{ Use: "open", Short: "Open the current project in the browser.", From 25ac45cb73814b0789fdb757cddffd706c8e655b Mon Sep 17 00:00:00 2001 From: corinnesollows Date: Mon, 4 Jul 2022 11:47:06 -0300 Subject: [PATCH 24/48] EXT-282-follow-project (#745) * If user is attempting to follow a project that is not in bitbucket or git then warn them we do not support it * Syntax fixes * Error correcti0n --- cmd/follow.go | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/cmd/follow.go b/cmd/follow.go index 609376f89..3471c0cd7 100644 --- a/cmd/follow.go +++ b/cmd/follow.go @@ -14,31 +14,38 @@ type options struct { cfg *settings.Config } +//followProject gets the remote data and attempts to follow its git project func followProject(opts options) error { remote, err := git.InferProjectFromGitRemotes() - if err != nil { return errors.Wrap(err, errorMessage) } - vcsShort := "gh" - if remote.VcsType == "BITBUCKET" { - vcsShort = "bb" - } - res, err := api.FollowProject(*opts.cfg, vcsShort, remote.Organization, remote.Project) - if err != nil { - return err + //check that project url contains github or bitbucket; our legacy vcs + if remote.VcsType == git.GitHub || remote.VcsType == git.Bitbucket { + vcsShort := "gh" + if remote.VcsType == git.Bitbucket { + vcsShort = "bb" + } + res, err := api.FollowProject(*opts.cfg, vcsShort, remote.Organization, remote.Project) + if err != nil { + return err + } + if res.Followed { + fmt.Println("Project successfully followed!") + } else if res.Message == "Project not found" { + fmt.Println("Unable to determine project slug for CircleCI (slug is case sensitive).") + } + + } else { + //if not warn user their vcs is not supported + return errors.New(errorMessage) } - if res.Followed { - fmt.Println("Project successfully followed!") - } else if res.Message == "Project not found" { - fmt.Println("Unable to determine project slug for CircleCI (slug is case sensitive).") - } - return nil } +//followProjectCommand follow cobra command creation func followProjectCommand(config *settings.Config) *cobra.Command { opts := options{ cfg: config, From ee1f05455ec36f2e2fa761f0e3ffadd3684f5273 Mon Sep 17 00:00:00 2001 From: Alberto Franco Date: Mon, 4 Jul 2022 13:23:49 -0400 Subject: [PATCH 25/48] [RT-603] Migrate CLI to use runner.circleci.com (#742) * [RT-603] Migrate CLI to use runner.circleci.com Jira: [RT-603](https://circleci.atlassian.net/browse/RT-603) As part of the incident remediation for [Runner UI & CLI making requests to the wrong domain](https://circleci.atlassian.net/l/c/ou0ABPYK), migrate the CLI to use `runner.circleci.com` for runner related operations. This is needed as the infra team starts to depreacate `www-proxy`. --- cmd/runner/runner.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cmd/runner/runner.go b/cmd/runner/runner.go index 5163238e3..9dc016c95 100644 --- a/cmd/runner/runner.go +++ b/cmd/runner/runner.go @@ -1,6 +1,8 @@ package runner import ( + "strings" + "github.com/spf13/cobra" "github.com/CircleCI-Public/circleci-cli/api/rest" @@ -18,12 +20,20 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { Use: "runner", Short: "Operate on runners", PersistentPreRun: func(cmd *cobra.Command, args []string) { - opts.r = runner.New(rest.New(config.Host, config.RestEndpoint, config.Token)) + var host string + if strings.Contains(config.Host, "https://circleci.com") { + host = "https://runner.circleci.com" + } else { + host = config.Host + } + opts.r = runner.New(rest.New(host, config.RestEndpoint, config.Token)) }, } + cmd.AddCommand(newResourceClassCommand(&opts, preRunE)) cmd.AddCommand(newTokenCommand(&opts, preRunE)) cmd.AddCommand(newRunnerInstanceCommand(&opts, preRunE)) + return cmd } From 655d06503f783e26968068b9cf165cc92a31ec68 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Tue, 5 Jul 2022 15:52:29 -0400 Subject: [PATCH 26/48] Fix its possessive typo in orb process command Signed-off-by: Adam Harvey --- cmd/orb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/orb.go b/cmd/orb.go index 231abfe5f..aa832e0f0 100644 --- a/cmd/orb.go +++ b/cmd/orb.go @@ -125,7 +125,7 @@ func newOrbCommand(config *settings.Config) *cobra.Command { Use: "process ", Short: "Validate an orb and print its form after all pre-registration processing", Long: strings.Join([]string{ - "Use `$ circleci orb process` to resolve an orb, and it's dependencies to see how it would be expanded when you publish it to the registry.", + "Use `$ circleci orb process` to resolve an orb and its dependencies, to see how it would be expanded when you publish it to the registry.", "", // purposeful new-line "This can be helpful for validating an orb and debugging the processed form before publishing.", }, "\n"), From d17ebe054d121fd2d00737618e99d608d5a626ff Mon Sep 17 00:00:00 2001 From: Chris Stephen Date: Thu, 7 Jul 2022 11:31:45 -0300 Subject: [PATCH 27/48] Add Runner team to CODEOWNERS of runner packages --- .github/CODEOWNERS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7509d0655..138eb5b32 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,6 @@ * @CircleCI-Public/Extensibility *orb*.go @CircleCI-Public/CPEng @CircleCI-Public/Extensibility + +/api/runner @CircleCI-Public/runner +/cmd/runner @CircleCI-Public/runner + From e15f9169b140c964f218a2011031dbc585d5117e Mon Sep 17 00:00:00 2001 From: Sagar Gupta Date: Wed, 13 Jul 2022 18:36:41 -0400 Subject: [PATCH 28/48] [SECENG-669] Remove 'active' attribute for policies (#750) --- api/policy/policy.go | 21 ++-- api/policy/policy_test.go | 72 +++----------- cmd/policy/policy.go | 28 +----- cmd/policy/policy_test.go | 97 +++++++++---------- .../testdata/policy/list-expected-usage.txt | 5 +- .../testdata/policy/update-expected-usage.txt | 3 +- 6 files changed, 74 insertions(+), 152 deletions(-) diff --git a/api/policy/policy.go b/api/policy/policy.go index 9ce6e98f8..42f08faf2 100644 --- a/api/policy/policy.go +++ b/api/policy/policy.go @@ -30,21 +30,13 @@ type httpError struct { Context map[string]interface{} `json:"context,omitempty"` } -// ListPolicies calls the view policy-service list policy API. If the active filter is nil, all policies are returned. If -// activeFilter is not nil it will only return active or inactive policies based on the value of *activeFilter. -func (c Client) ListPolicies(ownerID string, activeFilter *bool) (interface{}, error) { +// ListPolicies calls the view policy-service list policy API +func (c Client) ListPolicies(ownerID string) (interface{}, error) { req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/owner/%s/policy", c.serverUrl, ownerID), nil) if err != nil { return nil, fmt.Errorf("failed to construct request: %v", err) } - query := make(url.Values) - if activeFilter != nil { - query.Set("active", fmt.Sprint(*activeFilter)) - } - - req.URL.RawQuery = query.Encode() - resp, err := c.client.Do(req) if err != nil { return nil, err @@ -54,7 +46,7 @@ func (c Client) ListPolicies(ownerID string, activeFilter *bool) (interface{}, e if resp.StatusCode != http.StatusOK { var payload httpError if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { - return nil, fmt.Errorf("unexected status-code: %d", resp.StatusCode) + return nil, fmt.Errorf("unexpected status-code: %d", resp.StatusCode) } return nil, fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, payload.Error) } @@ -117,7 +109,6 @@ type UpdateRequest struct { Name *string `json:"name,omitempty"` Context *string `json:"context,omitempty"` Content *string `json:"content,omitempty"` - Active *bool `json:"active,omitempty"` } // UpdatePolicy calls the UPDATE policy API in the policy-service. It updates a policy in the policy-service matching the given owner-id and policy-id. @@ -177,7 +168,7 @@ func (c Client) GetPolicy(ownerID string, policyID string) (interface{}, error) if resp.StatusCode != http.StatusOK { var payload httpError if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { - return nil, fmt.Errorf("unexected status-code: %d", resp.StatusCode) + return nil, fmt.Errorf("unexpected status-code: %d", resp.StatusCode) } return nil, fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, payload.Error) } @@ -208,7 +199,7 @@ func (c Client) DeletePolicy(ownerID string, policyID string) error { if resp.StatusCode != http.StatusNoContent { var payload httpError if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { - return fmt.Errorf("unexected status-code: %d", resp.StatusCode) + return fmt.Errorf("unexpected status-code: %d", resp.StatusCode) } return fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, payload.Error) } @@ -260,7 +251,7 @@ func (c Client) GetDecisionLogs(ownerID string, request DecisionQueryRequest) ([ if resp.StatusCode != http.StatusOK { var payload httpError if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { - return nil, fmt.Errorf("unexected status-code: %d", resp.StatusCode) + return nil, fmt.Errorf("unexpected status-code: %d", resp.StatusCode) } return nil, fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, payload.Error) } diff --git a/api/policy/policy_test.go b/api/policy/policy_test.go index b612a225c..3f1f1ab59 100644 --- a/api/policy/policy_test.go +++ b/api/policy/policy_test.go @@ -36,15 +36,14 @@ func TestClientListPolicies(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - _, err := client.ListPolicies("ownerId", nil) + _, err := client.ListPolicies("ownerId") assert.NilError(t, err) }) - t.Run("List Policies - Bad Request", func(t *testing.T) { - expectedResponse := `{"error": "active: query string not a boolean."}` - + t.Run("List Policies - Forbidden", func(t *testing.T) { + expectedResponse := `{"error": "Forbidden"}` svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadRequest) + w.WriteHeader(http.StatusForbidden) _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) })) @@ -53,13 +52,13 @@ func TestClientListPolicies(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - policies, err := client.ListPolicies("ownerId", nil) + policies, err := client.ListPolicies("ownerId") assert.Equal(t, policies, nil) - assert.Error(t, err, "unexpected status-code: 400 - active: query string not a boolean.") + assert.Error(t, err, "unexpected status-code: 403 - Forbidden") }) - t.Run("List Policies - Forbidden", func(t *testing.T) { - expectedResponse := `{"error": "Forbidden"}` + t.Run("List Policies - Bad error json", func(t *testing.T) { + expectedResponse := `{"this is bad json": }` svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusForbidden) _, err := w.Write([]byte(expectedResponse)) @@ -70,9 +69,9 @@ func TestClientListPolicies(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - policies, err := client.ListPolicies("ownerId", nil) + policies, err := client.ListPolicies("ownerId") assert.Equal(t, policies, nil) - assert.Error(t, err, "unexpected status-code: 403 - Forbidden") + assert.Error(t, err, "unexpected status-code: 403") }) t.Run("List Policies - no policies", func(t *testing.T) { @@ -90,7 +89,7 @@ func TestClientListPolicies(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - policies, err := client.ListPolicies("ownerId", nil) + policies, err := client.ListPolicies("ownerId") assert.DeepEqual(t, policies, expectedResponseValue) assert.NilError(t, err) }) @@ -102,7 +101,6 @@ func TestClientListPolicies(t *testing.T) { "name": "policy_1", "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f", "context": "config", - "active": false, "created_at": "2022-05-31T14:15:10.86097Z", "modified_at": null }, @@ -111,7 +109,6 @@ func TestClientListPolicies(t *testing.T) { "name": "policy_2", "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f", "context": "config", - "active": true, "created_at": "2022-05-31T14:15:23.582383Z", "modified_at": "2022-05-31T14:15:46.72321Z" } @@ -129,7 +126,7 @@ func TestClientListPolicies(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - policies, err := client.ListPolicies("ownerId", nil) + policies, err := client.ListPolicies("ownerId") assert.DeepEqual(t, policies, expectedResponseValue) assert.NilError(t, err) }) @@ -220,7 +217,6 @@ func TestClientGetPolicy(t *testing.T) { "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f", "context": "config", "content": "package test", - "active": false, "created_at": "2022-05-31T14:15:10.86097Z", "modified_at": null }` @@ -383,45 +379,6 @@ func TestClientDeletePolicy(t *testing.T) { func TestClientUpdatePolicy(t *testing.T) { t.Run("expected request", func(t *testing.T) { - isActive := true - name := "test-name" - context := "config" - content := "test-content" - req := UpdateRequest{ - Name: &name, - Context: &context, - Content: &content, - Active: &isActive, - } - - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Header.Get("circle-token"), "testtoken") - assert.Equal(t, r.Header.Get("accept"), "application/json") - assert.Equal(t, r.Header.Get("content-type"), "application/json") - assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) - assert.Equal(t, r.Header.Get("circle-token"), "testtoken") - - assert.Equal(t, r.Method, "PATCH") - assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerID/policy/policyID") - - var actual UpdateRequest - assert.NilError(t, json.NewDecoder(r.Body).Decode(&actual)) - assert.DeepEqual(t, actual, req) - - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte("{}")) - assert.NilError(t, err) - })) - defer svr.Close() - - config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} - client := NewClient(svr.URL, config) - - _, err := client.UpdatePolicy("ownerID", "policyID", req) - assert.NilError(t, err) - }) - - t.Run("nil active", func(t *testing.T) { name := "test-name" context := "config" content := "test-content" @@ -429,7 +386,6 @@ func TestClientUpdatePolicy(t *testing.T) { Name: &name, Context: &context, Content: &content, - Active: nil, } svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -492,7 +448,7 @@ func TestClientUpdatePolicy(t *testing.T) { assert.NilError(t, json.NewDecoder(r.Body).Decode(&actual)) assert.DeepEqual(t, actual, req) - expectedResponse := `{"error": "at least one of name, context, content, or active cannot be blank"}` + expectedResponse := `{"error": "at least one of name, context, or content, cannot be blank"}` w.WriteHeader(http.StatusBadRequest) _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) @@ -503,7 +459,7 @@ func TestClientUpdatePolicy(t *testing.T) { client := NewClient(svr.URL, config) _, err := client.UpdatePolicy("ownerID", "policyID", req) - assert.Error(t, err, "unexpected status-code: 400 - at least one of name, context, content, or active cannot be blank") + assert.Error(t, err, "unexpected status-code: 400 - at least one of name, context, or content, cannot be blank") }) t.Run("one change", func(t *testing.T) { diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index 6da2046cc..c13841308 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -41,21 +41,11 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { } list := func() *cobra.Command { - var active bool - cmd := &cobra.Command{ Short: "List all policies", Use: "list", RunE: func(cmd *cobra.Command, args []string) error { - var flags struct { - Active *bool - } - - if cmd.Flag("active").Changed { - flags.Active = &active - } - - policies, err := policy.NewClient(*policyBaseURL, config).ListPolicies(*ownerID, flags.Active) + policies, err := policy.NewClient(*policyBaseURL, config).ListPolicies(*ownerID) if err != nil { return fmt.Errorf("failed to list policies: %v", err) } @@ -67,11 +57,8 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { return nil }, Args: cobra.ExactArgs(0), - Example: `policy list --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 --active=true`, + Example: `policy list --owner-id 516425b2-e369-421b-838d-920e1f51b0f5`, } - - cmd.Flags().BoolVar(&active, "active", false, "(OPTIONAL) filter policies based on active status (true or false)") - return cmd }() @@ -164,7 +151,6 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { update := func() *cobra.Command { var policyPath string - var active bool var context string var name string var updateRequest policy.UpdateRequest @@ -174,10 +160,9 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { Use: "update ", RunE: func(cmd *cobra.Command, args []string) error { if !(cmd.Flag("policy").Changed || - cmd.Flag("active").Changed || cmd.Flag("context").Changed || cmd.Flag("name").Changed) { - return fmt.Errorf("one of policy, active, context, or name must be set") + return fmt.Errorf("one of policy, context, or name must be set") } if cmd.Flag("policy").Changed { @@ -193,10 +178,6 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { client := policy.NewClient(*policyBaseURL, config) - if cmd.Flag("active").Changed { - updateRequest.Active = &active - } - if cmd.Flag("context").Changed { updateRequest.Context = &context } @@ -217,12 +198,11 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { return nil }, Args: cobra.ExactArgs(1), - Example: `policy update e9e300d1-5bab-4704-b610-addbd6e03b0b --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --name policy_name --active --policy ./policy.rego`, + Example: `policy update e9e300d1-5bab-4704-b610-addbd6e03b0b --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --name policy_name --policy ./policy.rego`, } cmd.Flags().StringVar(&name, "name", "", "set name of the given policy-id") cmd.Flags().StringVar(&context, "context", "", "policy context (if set, must be config)") - cmd.Flags().BoolVar(&active, "active", false, "set policy active state (to deactivate, use --active=false)") cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego file containing the updated policy") return cmd diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index 6a45a6e33..566a0b626 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -29,52 +29,25 @@ func TestListPolicies(t *testing.T) { ExpectedErr: "required flag(s) \"owner-id\" not set", }, { - Name: "invalid active filter value", - Args: []string{"list", "--owner-id", "ownerID", "--active=badValue"}, - ExpectedErr: `invalid argument "badValue" for "--active" flag: strconv.ParseBool: parsing "badValue": invalid syntax`, - }, - { - Name: "should set active to true", - Args: []string{"list", "--owner-id", "ownerID", "--active"}, - ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy?active=true") - _, err := w.Write([]byte("[]")) - assert.NilError(t, err) - }, - ExpectedOutput: "[]\n", - }, - { - Name: "should set active to false", - Args: []string{"list", "--owner-id", "ownerID", "--active=false"}, - ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy?active=false") - _, err := w.Write([]byte("[]")) - assert.NilError(t, err) - }, - ExpectedOutput: "[]\n", - }, - { - Name: "no active is set", - Args: []string{"list", "--owner-id", "ownerID"}, + Name: "gets error response", + Args: []string{"list", "--owner-id", "ownerID"}, + ExpectedErr: "failed to list policies: unexpected status-code: 403 - Forbidden", ServerHandler: func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Method, "GET") assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy") - _, err := w.Write([]byte("[]")) + w.WriteHeader(http.StatusForbidden) + _, err := w.Write([]byte(`{"error": "Forbidden"}`)) assert.NilError(t, err) }, - ExpectedOutput: "[]\n", }, { - Name: "gets error response", + Name: "gets bad json response", Args: []string{"list", "--owner-id", "ownerID"}, - ExpectedErr: "failed to list policies: unexpected status-code: 403 - Forbidden", + ExpectedErr: "failed to list policies: failed to decode response body: invalid character '}' looking for beginning of value", ServerHandler: func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Method, "GET") assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy") - w.WriteHeader(http.StatusForbidden) - _, err := w.Write([]byte(`{"error": "Forbidden"}`)) + _, err := w.Write([]byte(`{"bad json": }`)) assert.NilError(t, err) }, }, @@ -90,7 +63,6 @@ func TestListPolicies(t *testing.T) { "name": "policy_1", "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f", "context": "config", - "active": false, "created_at": "2022-05-31T14:15:10.86097Z", "modified_at": null } @@ -99,7 +71,6 @@ func TestListPolicies(t *testing.T) { }, ExpectedOutput: `[ { - "active": false, "context": "config", "created_at": "2022-05-31T14:15:10.86097Z", "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", @@ -150,6 +121,11 @@ func TestCreatePolicy(t *testing.T) { Args: []string{"create"}, ExpectedErr: "required flag(s) \"name\", \"owner-id\", \"policy\" not set", }, + { + Name: "fails for policy file not found", + Args: []string{"create", "--owner-id", "test-org", "--name", "test-policy", "--policy", "./testdata/file_not_present.rego"}, + ExpectedErr: "failed to read policy file: open ./testdata/file_not_present.rego: ", + }, { Name: "sends appropriate desired request", Args: []string{"create", "--owner-id", "test-org", "--name", "test-policy", "--policy", "./testdata/test.rego"}, @@ -189,7 +165,7 @@ func TestCreatePolicy(t *testing.T) { if tc.ExpectedErr == "" { assert.NilError(t, err) } else { - assert.Error(t, err, tc.ExpectedErr) + assert.ErrorContains(t, err, tc.ExpectedErr) return } @@ -241,14 +217,12 @@ func TestGetPolicy(t *testing.T) { "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f", "context": "config", "content": "package test", - "active": false, "created_at": "2022-05-31T14:15:10.86097Z", "modified_at": null }`)) assert.NilError(t, err) }, ExpectedOutput: `{ - "active": false, "content": "package test", "context": "config", "created_at": "2022-05-31T14:15:10.86097Z", @@ -386,9 +360,32 @@ func TestUpdatePolicy(t *testing.T) { Args: []string{"update", "--owner-id", "test-org"}, ExpectedErr: "accepts 1 arg(s), received 0", }, + { + Name: "fails if policy file not found", + Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--policy", "./testdata/file_not_present.rego"}, + ExpectedErr: "failed to read policy file: open ./testdata/file_not_present.rego: ", + }, + { + Name: "gets error response", + Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--name", "test-policy", "--policy", "./testdata/test.rego"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + var body map[string]interface{} + assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-org/policy/test-policy-id") + assert.DeepEqual(t, body, map[string]interface{}{ + "content": "package test", + "name": "test-policy", + }) + + w.WriteHeader(http.StatusForbidden) + _, err := w.Write([]byte(`{"error": "Forbidden"}`)) + assert.NilError(t, err) + }, + ExpectedErr: "failed to update policy: unexpected status-code: 403 - Forbidden", + }, { Name: "sends appropriate desired request", - Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--active", "--name", "test-policy", "--policy", "./testdata/test.rego"}, + Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--name", "test-policy", "--policy", "./testdata/test.rego"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) @@ -396,7 +393,6 @@ func TestUpdatePolicy(t *testing.T) { assert.DeepEqual(t, body, map[string]interface{}{ "content": "package test", "name": "test-policy", - "active": true, }) w.WriteHeader(http.StatusOK) @@ -407,7 +403,7 @@ func TestUpdatePolicy(t *testing.T) { }, { Name: "explicitly set config", - Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--active", "--name", "test-policy", "--policy", "./testdata/test.rego", "--context", "config"}, + Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--name", "test-policy", "--policy", "./testdata/test.rego", "--context", "config"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) @@ -415,7 +411,6 @@ func TestUpdatePolicy(t *testing.T) { assert.DeepEqual(t, body, map[string]interface{}{ "content": "package test", "name": "test-policy", - "active": true, "context": "config", }) @@ -460,8 +455,8 @@ func TestUpdatePolicy(t *testing.T) { ExpectedOutput: "{}\n", }, { - Name: "sends appropriate desired request - deactivate policy", - Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--active=false", "--name", "test-policy", "--policy", "./testdata/test.rego"}, + Name: "sends appropriate desired request - policy name and content", + Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--name", "test-policy", "--policy", "./testdata/test.rego"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) @@ -469,7 +464,6 @@ func TestUpdatePolicy(t *testing.T) { assert.DeepEqual(t, body, map[string]interface{}{ "content": "package test", "name": "test-policy", - "active": false, }) w.WriteHeader(http.StatusOK) @@ -481,7 +475,7 @@ func TestUpdatePolicy(t *testing.T) { { Name: "check at least one field is changed", Args: []string{"update", "test-policy-id", "--owner-id", "test-org"}, - ExpectedErr: "one of policy, active, context, or name must be set", + ExpectedErr: "one of policy, context, or name must be set", }, } @@ -502,7 +496,7 @@ func TestUpdatePolicy(t *testing.T) { if tc.ExpectedErr == "" { assert.NilError(t, err) } else { - assert.Error(t, err, tc.ExpectedErr) + assert.ErrorContains(t, err, tc.ExpectedErr) return } @@ -529,6 +523,11 @@ func TestGetDecisionLogs(t *testing.T) { Args: []string{"logs", "--owner-id", "ownerID", "--after", "1/2/2022"}, ExpectedErr: `error in parsing --after value: This date has ambiguous mm/dd vs dd/mm type format`, }, + { + Name: "invalid --before filter value", + Args: []string{"logs", "--owner-id", "ownerID", "--before", "1/2/2022"}, + ExpectedErr: `error in parsing --before value: This date has ambiguous mm/dd vs dd/mm type format`, + }, { Name: "no filter is set", Args: []string{"logs", "--owner-id", "ownerID"}, diff --git a/cmd/policy/testdata/policy/list-expected-usage.txt b/cmd/policy/testdata/policy/list-expected-usage.txt index eea21b829..1406bfab0 100644 --- a/cmd/policy/testdata/policy/list-expected-usage.txt +++ b/cmd/policy/testdata/policy/list-expected-usage.txt @@ -2,10 +2,7 @@ Usage: policy list [flags] Examples: -policy list --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 --active=true - -Flags: - --active (OPTIONAL) filter policies based on active status (true or false) +policy list --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 Global Flags: --owner-id string the id of the policy's owner diff --git a/cmd/policy/testdata/policy/update-expected-usage.txt b/cmd/policy/testdata/policy/update-expected-usage.txt index 45180fb07..a1b3d3a01 100644 --- a/cmd/policy/testdata/policy/update-expected-usage.txt +++ b/cmd/policy/testdata/policy/update-expected-usage.txt @@ -2,10 +2,9 @@ Usage: policy update [flags] Examples: -policy update e9e300d1-5bab-4704-b610-addbd6e03b0b --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --name policy_name --active --policy ./policy.rego +policy update e9e300d1-5bab-4704-b610-addbd6e03b0b --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --name policy_name --policy ./policy.rego Flags: - --active set policy active state (to deactivate, use --active=false) --context string policy context (if set, must be config) --name string set name of the given policy-id --policy string path to rego file containing the updated policy From f848adbb56b79fa62c209238001633f9c65a0ce6 Mon Sep 17 00:00:00 2001 From: Chris Stephen Date: Wed, 13 Jul 2022 07:52:54 -0300 Subject: [PATCH 29/48] Add command to force delete a Runner resource class --- api/runner/runner.go | 9 ++++++-- api/runner/runner_test.go | 23 +++++++++++++++++-- cmd/runner/resource_class.go | 10 +++++--- cmd/runner/resource_class_test.go | 2 +- cmd/runner/runner.go | 2 +- .../resource-class/delete-expected-usage.txt | 5 +++- 6 files changed, 41 insertions(+), 10 deletions(-) diff --git a/api/runner/runner.go b/api/runner/runner.go index aa8ab335d..6cc006655 100644 --- a/api/runner/runner.go +++ b/api/runner/runner.go @@ -82,8 +82,13 @@ func (r *Runner) GetResourceClassesByNamespace(namespace string) ([]ResourceClas return resp.Items, err } -func (r *Runner) DeleteResourceClass(id string) error { - req, err := r.rc.NewRequest("DELETE", &url.URL{Path: "runner/resource/" + url.PathEscape(id)}, nil) +func (r *Runner) DeleteResourceClass(id string, force bool) error { + path := "runner/resource/" + url.PathEscape(id) + if force { + path = fmt.Sprintf("runner/resource/%s/force", url.PathEscape(id)) + } + + req, err := r.rc.NewRequest("DELETE", &url.URL{Path: path}, nil) if err != nil { return err } diff --git a/api/runner/runner_test.go b/api/runner/runner_test.go index cf9755b4f..dd25a0d4b 100644 --- a/api/runner/runner_test.go +++ b/api/runner/runner_test.go @@ -150,7 +150,7 @@ func TestRunner_DeleteResourceClass(t *testing.T) { defer cleanup() t.Run("Check resource-class is deleted", func(t *testing.T) { - err := runner.DeleteResourceClass("51628548-4627-4813-9f9b-8cc9637ac879") + err := runner.DeleteResourceClass("51628548-4627-4813-9f9b-8cc9637ac879", false) assert.NilError(t, err) }) @@ -167,13 +167,32 @@ func TestRunner_DeleteResourceClass(t *testing.T) { }) } +func TestRunner_DeleteResourceClass_Force(t *testing.T) { + fix := fixture{} + runner, cleanup := fix.Run(http.StatusOK, ``) + defer cleanup() + + err := runner.DeleteResourceClass("5a1ef22d-444b-45db-8e98-21d7c42fb80b", true) + assert.NilError(t, err) + + assert.Check(t, cmp.Equal(fix.URL(), url.URL{Path: "/api/v2/runner/resource/5a1ef22d-444b-45db-8e98-21d7c42fb80b/force"})) + assert.Check(t, cmp.Equal(fix.method, "DELETE")) + assert.Check(t, cmp.DeepEqual(fix.Header(), http.Header{ + "Accept-Encoding": {"gzip"}, + "Accept-Type": {"application/json"}, + "Circle-Token": {"fake-token"}, + "User-Agent": {version.UserAgent()}, + })) + assert.Check(t, cmp.Equal(fix.Body(), ``)) +} + func TestRunner_DeleteResourceClass_PathEscaping(t *testing.T) { fix := fixture{} runner, cleanup := fix.Run(http.StatusOK, ``) defer cleanup() t.Run("Check resource-class is deleted", func(t *testing.T) { - err := runner.DeleteResourceClass("escape~,/;?~noescape~$&+:=@") + err := runner.DeleteResourceClass("escape~,/;?~noescape~$&+:=@", false) assert.NilError(t, err) }) diff --git a/cmd/runner/resource_class.go b/cmd/runner/resource_class.go index 9a7a513bd..0761b42e9 100644 --- a/cmd/runner/resource_class.go +++ b/cmd/runner/resource_class.go @@ -47,7 +47,8 @@ func newResourceClassCommand(o *runnerOpts, preRunE validator) *cobra.Command { "Generate a default token") cmd.AddCommand(createCmd) - cmd.AddCommand(&cobra.Command{ + forceDelete := false + deleteCmd := &cobra.Command{ Use: "delete ", Short: "Delete a resource-class", Aliases: []string{"rm"}, @@ -58,9 +59,12 @@ func newResourceClassCommand(o *runnerOpts, preRunE validator) *cobra.Command { if err != nil { return err } - return o.r.DeleteResourceClass(rc.ID) + return o.r.DeleteResourceClass(rc.ID, forceDelete) }, - }) + } + deleteCmd.PersistentFlags().BoolVarP(&forceDelete, "force", "f", false, + "Delete resource-class and any associated tokens") + cmd.AddCommand(deleteCmd) cmd.AddCommand(&cobra.Command{ Use: "list ", diff --git a/cmd/runner/resource_class_test.go b/cmd/runner/resource_class_test.go index 204de1d85..df9cbbf27 100644 --- a/cmd/runner/resource_class_test.go +++ b/cmd/runner/resource_class_test.go @@ -120,7 +120,7 @@ func (r *runnerMock) GetResourceClassesByNamespace(namespace string) ([]runner.R return rcs, nil } -func (r *runnerMock) DeleteResourceClass(id string) error { +func (r *runnerMock) DeleteResourceClass(id string, force bool) error { for i, rc := range r.resourceClasses { if rc.ID == id { r.resourceClasses = append(r.resourceClasses[:i], r.resourceClasses[i+1:]...) diff --git a/cmd/runner/runner.go b/cmd/runner/runner.go index 9dc016c95..4e3b2afe6 100644 --- a/cmd/runner/runner.go +++ b/cmd/runner/runner.go @@ -42,7 +42,7 @@ type running interface { GetResourceClassByName(resourceClass string) (rc *runner.ResourceClass, err error) GetNamespaceByResourceClass(resourceClass string) (ns string, err error) GetResourceClassesByNamespace(namespace string) ([]runner.ResourceClass, error) - DeleteResourceClass(id string) error + DeleteResourceClass(id string, force bool) error CreateToken(resourceClass, nickname string) (token *runner.Token, err error) GetRunnerTokensByResourceClass(resourceClass string) ([]runner.Token, error) DeleteToken(id string) error diff --git a/cmd/runner/testdata/runner/resource-class/delete-expected-usage.txt b/cmd/runner/testdata/runner/resource-class/delete-expected-usage.txt index 276c2cb3c..57cce717a 100644 --- a/cmd/runner/testdata/runner/resource-class/delete-expected-usage.txt +++ b/cmd/runner/testdata/runner/resource-class/delete-expected-usage.txt @@ -1,5 +1,8 @@ Usage: - runner resource-class delete + runner resource-class delete [flags] Aliases: delete, rm + +Flags: + -f, --force Delete resource-class and any associated tokens From e4216bdcb0296b665d1ecd12f65800feaca7b75e Mon Sep 17 00:00:00 2001 From: Sagar Gupta Date: Thu, 14 Jul 2022 10:31:46 -0400 Subject: [PATCH 30/48] added 'status' filter to decision logs fetch command (#753) --- api/policy/policy.go | 4 ++++ api/policy/policy_test.go | 3 ++- cmd/policy/policy.go | 1 + cmd/policy/policy_test.go | 4 ++-- cmd/policy/testdata/policy/logs-expected-usage.txt | 1 + 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/api/policy/policy.go b/api/policy/policy.go index 42f08faf2..144fd7613 100644 --- a/api/policy/policy.go +++ b/api/policy/policy.go @@ -208,6 +208,7 @@ func (c Client) DeletePolicy(ownerID string, policyID string) error { } type DecisionQueryRequest struct { + Status string After *time.Time Before *time.Time Branch string @@ -224,6 +225,9 @@ func (c Client) GetDecisionLogs(ownerID string, request DecisionQueryRequest) ([ } query := make(url.Values) + if request.Status != "" { + query.Set("status", fmt.Sprint(request.Status)) + } if request.After != nil { query.Set("after", request.After.Format(time.RFC3339)) } diff --git a/api/policy/policy_test.go b/api/policy/policy_test.go index 3f1f1ab59..3eb2f7f31 100644 --- a/api/policy/policy_test.go +++ b/api/policy/policy_test.go @@ -562,7 +562,7 @@ func TestClientGetDecisionLogs(t *testing.T) { assert.Equal( t, r.URL.RawQuery, - "after=2000-01-01T00%3A00%3A00Z&before=2000-01-01T00%3A00%3A00Z&branch=branchValue&offset=42&project_id=projectIDValue", + "after=2000-01-01T00%3A00%3A00Z&before=2000-01-01T00%3A00%3A00Z&branch=branchValue&offset=42&project_id=projectIDValue&status=PASS", ) assert.Equal(t, r.URL.Query().Get("before"), testTime.Format(time.RFC3339)) @@ -578,6 +578,7 @@ func TestClientGetDecisionLogs(t *testing.T) { client := NewClient(svr.URL, config) _, err := client.GetDecisionLogs("ownerId", DecisionQueryRequest{ + Status: "PASS", After: &testTime, Before: &testTime, Branch: "branchValue", diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index c13841308..906acc1ab 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -284,6 +284,7 @@ func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { Example: `policy logs --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --after 2022/03/14 --out output.json`, } + cmd.Flags().StringVar(&request.Status, "status", "", "filter decision logs based on their status") cmd.Flags().StringVar(&after, "after", "", "filter decision logs triggered AFTER this datetime") cmd.Flags().StringVar(&before, "before", "", "filter decision logs triggered BEFORE this datetime") cmd.Flags().StringVar(&request.Branch, "branch", "", "filter decision logs based on branch name") diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index 566a0b626..7b4f2ee40 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -542,12 +542,12 @@ func TestGetDecisionLogs(t *testing.T) { { Name: "all filters are set", Args: []string{ - "logs", "--owner-id", "ownerID", "--after", "2022/03/14", "--before", "2022/03/15", + "logs", "--owner-id", "ownerID", "--status", "PASS", "--after", "2022/03/14", "--before", "2022/03/15", "--branch", "branchValue", "--project-id", "projectIDValue", }, ServerHandler: func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/decision?after=2022-03-14T00%3A00%3A00Z&before=2022-03-15T00%3A00%3A00Z&branch=branchValue&project_id=projectIDValue") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/decision?after=2022-03-14T00%3A00%3A00Z&before=2022-03-15T00%3A00%3A00Z&branch=branchValue&project_id=projectIDValue&status=PASS") _, err := w.Write([]byte("[]")) assert.NilError(t, err) }, diff --git a/cmd/policy/testdata/policy/logs-expected-usage.txt b/cmd/policy/testdata/policy/logs-expected-usage.txt index 81cc8168e..9e91bccdd 100644 --- a/cmd/policy/testdata/policy/logs-expected-usage.txt +++ b/cmd/policy/testdata/policy/logs-expected-usage.txt @@ -10,6 +10,7 @@ Flags: --branch string filter decision logs based on branch name --out string specify output file name --project-id string filter decision logs based on project-id + --status string filter decision logs based on their status Global Flags: --owner-id string the id of the policy's owner From 63ff9243a3a27b9368d4cf20b04de09d30befc9f Mon Sep 17 00:00:00 2001 From: corinnesollows Date: Wed, 6 Jul 2022 15:57:16 -0300 Subject: [PATCH 31/48] Info command skeleton created Command fix Keep the header content type Unused var removal testing Testing one more time Test Refactor info into using API info package to match project standards reformat org info table with tablewriter Use the same tablewriter solution from context.go to print out a more readable org-info table. Return nil instead of err in the penultimate line. Refactor API to use client from root instead This is in order to respect TLS rules for custom. Fix tests remove unused flag assert output because my local env is borked I'm just gonna push a bunch of stuff to the CI because my local env is broken for some reason. I don't know why things are broken locally but meh I'll squash all these later. I don't know Revert rest handler in tests I don't know I'm shooting at straws right now. I might just change the entire way we're doing things. If this doesn't work because I'm sick of working with Gomega. Refactor Info and write tests Refactored the info stuff into it's own package because the CMD package was getting to be too big. Wrote standard tests without Gingko as it was unnecessary and causing problems. Info Org error 500 message handling test. Test the expected case where a 500 message returned. Info API client tests Write tests for happy path and bad path for info tests. Add documentation to info api client stuff. Add DS_Store to gitignore and remove the file. Revert client changes. Extract validator and write a test for it. Refactor ServerURL parsing from config into method Refactor the ServerURL implementations from the code into a method found within the config itself since it seems like an appropriate place to put the code. Fix broken import references use validator.Validator type Settings ServerURL test --- .gitignore | 1 + api/context_rest.go | 12 +--- api/info/info.go | 115 +++++++++++++++++++++++++++++++ api/info/info_test.go | 78 +++++++++++++++++++++ api/rest/client.go | 1 - api/schedule_rest.go | 12 +--- cmd/info/info.go | 69 +++++++++++++++++++ cmd/info/info_test.go | 130 +++++++++++++++++++++++++++++++++++ cmd/policy/policy.go | 6 +- cmd/root.go | 2 + cmd/root_test.go | 2 +- cmd/runner/instance.go | 3 +- cmd/runner/resource_class.go | 3 +- cmd/runner/runner.go | 5 +- cmd/runner/token.go | 3 +- cmd/validator/validator.go | 5 ++ go.sum | 51 -------------- settings/settings.go | 24 +++++++ settings/settings_test.go | 13 ++++ 19 files changed, 450 insertions(+), 85 deletions(-) create mode 100644 api/info/info.go create mode 100644 api/info/info_test.go create mode 100644 cmd/info/info.go create mode 100644 cmd/info/info_test.go create mode 100644 cmd/validator/validator.go diff --git a/.gitignore b/.gitignore index 9b900d350..b9c95955f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ out/ vendor/ .vscode .idea +.DS_Store # For supporting generated config of 2.1 syntax for local testing .circleci/processed.yml diff --git a/api/context_rest.go b/api/context_rest.go index 7b942faf6..fcc169a8a 100644 --- a/api/context_rest.go +++ b/api/context_rest.go @@ -8,7 +8,6 @@ import ( "io/ioutil" "net/http" "net/url" - "strings" "github.com/CircleCI-Public/circleci-cli/api/header" "github.com/CircleCI-Public/circleci-cli/settings" @@ -605,16 +604,7 @@ func (c *ContextRestClient) EnsureExists() error { // NewContextRestClient returns a new client satisfying the api.ContextInterface // interface via the REST API. func NewContextRestClient(config settings.Config) (*ContextRestClient, error) { - // Ensure server ends with a slash - if !strings.HasSuffix(config.RestEndpoint, "/") { - config.RestEndpoint += "/" - } - serverURL, err := url.Parse(config.Host) - if err != nil { - return nil, err - } - - serverURL, err = serverURL.Parse(config.RestEndpoint) + serverURL, err := config.ServerURL() if err != nil { return nil, err } diff --git a/api/info/info.go b/api/info/info.go new file mode 100644 index 000000000..3ba9c65ce --- /dev/null +++ b/api/info/info.go @@ -0,0 +1,115 @@ +package info + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + + "github.com/CircleCI-Public/circleci-cli/api/header" + "github.com/CircleCI-Public/circleci-cli/settings" + "github.com/CircleCI-Public/circleci-cli/version" +) + +// InfoClient An interface with all the Info Functions. +type InfoClient interface { + GetInfo() (*[]Organization, error) +} + +// errorResponse used to handle error messages from the API. +type errorResponse struct { + Message *string `json:"message"` +} + +// InfoRESTClient A restful implementation of the InfoClient +type InfoRESTClient struct { + token string + server string + client *http.Client +} + +//organization json org info +type Organization struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// GetInfo +func (c *InfoRESTClient) GetInfo() (*[]Organization, error) { + var err error + queryURL, err := url.Parse(c.server) + if err != nil { + return nil, err + } + queryURL, err = queryURL.Parse("me/collaborations") + if err != nil { + return nil, err + } + req, err := c.newHTTPRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, fmt.Errorf("failed to construct new request: %v", err) + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + + bodyBytes, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return nil, err + } + + if resp.StatusCode != 200 { + var dest errorResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + + } + return nil, errors.New(*dest.Message) + } + + orgs := make([]Organization, 0) + if err := json.Unmarshal(bodyBytes, &orgs); err != nil { + return nil, err + } + + return &orgs, nil +} + +// newHTTPRequest Creates a new standard HTTP request object used to communicate with the API +func (c *InfoRESTClient) newHTTPRequest(method, url string, body io.Reader) (*http.Request, error) { + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, err + } + req.Header.Add("circle-token", c.token) + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + req.Header.Add("User-Agent", version.UserAgent()) + commandStr := header.GetCommandStr() + if commandStr != "" { + req.Header.Add("Circleci-Cli-Command", commandStr) + } + return req, nil +} + +// Creates a new client to talk with the rest info endpoints. +func NewInfoClient(config settings.Config) (InfoClient, error) { + serverURL, err := config.ServerURL() + if err != nil { + return nil, err + } + + client := &InfoRESTClient{ + token: config.Token, + server: serverURL.String(), + client: config.HTTPClient, + } + + return client, nil +} diff --git a/api/info/info_test.go b/api/info/info_test.go new file mode 100644 index 000000000..66c604e1c --- /dev/null +++ b/api/info/info_test.go @@ -0,0 +1,78 @@ +package info + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/CircleCI-Public/circleci-cli/settings" + "gotest.tools/v3/assert" +) + +func TestOkResponse(t *testing.T) { + token := "pluto-is-a-planet" + id := "id" + name := "name" + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/me/collaborations") + assert.Equal(t, r.Header.Get("circle-token"), token) + assert.Equal(t, r.Header.Get("Content-Type"), "application/json") + assert.Equal(t, r.Header.Get("Accept"), "application/json") + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(fmt.Sprintf(`[{"id": "%s", "name": "%s"}]`, id, name))) + assert.NilError(t, err) + })) + + defer server.Close() + + config := settings.Config{ + Host: server.URL, + HTTPClient: http.DefaultClient, + Token: token, + } + + client, _ := NewInfoClient(config) + orgs, err := client.GetInfo() + organizations := *orgs + + assert.NilError(t, err) + assert.Equal(t, len(organizations), 1) + + org := organizations[0] + assert.Equal(t, org.ID, id) + assert.Equal(t, org.Name, name) +} + +func TestServerErrorResponse(t *testing.T) { + token := "pluto-is-a-planet" + message := "i-come-in-peace" + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/me/collaborations") + assert.Equal(t, r.Header.Get("circle-token"), token) + assert.Equal(t, r.Header.Get("Content-Type"), "application/json") + assert.Equal(t, r.Header.Get("Accept"), "application/json") + + w.WriteHeader(http.StatusInternalServerError) + _, err := w.Write([]byte(fmt.Sprintf(`{"message": "%s"}`, message))) + assert.NilError(t, err) + })) + + defer server.Close() + + config := settings.Config{ + Host: server.URL, + HTTPClient: http.DefaultClient, + Token: token, + } + + client, _ := NewInfoClient(config) + _, err := client.GetInfo() + + assert.Error(t, err, message) +} diff --git a/api/rest/client.go b/api/rest/client.go index 5ce649f66..9b06d97a2 100644 --- a/api/rest/client.go +++ b/api/rest/client.go @@ -63,7 +63,6 @@ func (c *Client) NewRequest(method string, u *url.URL, payload interface{}) (req if payload != nil { req.Header.Set("Content-Type", "application/json") } - return req, nil } diff --git a/api/schedule_rest.go b/api/schedule_rest.go index 638f63c1d..6b35d700f 100644 --- a/api/schedule_rest.go +++ b/api/schedule_rest.go @@ -8,7 +8,6 @@ import ( "io/ioutil" "net/http" "net/url" - "strings" "github.com/CircleCI-Public/circleci-cli/api/header" "github.com/CircleCI-Public/circleci-cli/settings" @@ -463,16 +462,7 @@ func (c *ScheduleRestClient) EnsureExists() error { // Returns a new client satisfying the api.ScheduleInterface interface // via the REST API. func NewScheduleRestClient(config settings.Config) (*ScheduleRestClient, error) { - // Ensure server ends with a slash - if !strings.HasSuffix(config.RestEndpoint, "/") { - config.RestEndpoint += "/" - } - serverURL, err := url.Parse(config.Host) - if err != nil { - return nil, err - } - - serverURL, err = serverURL.Parse(config.RestEndpoint) + serverURL, err := config.ServerURL() if err != nil { return nil, err } diff --git a/cmd/info/info.go b/cmd/info/info.go new file mode 100644 index 000000000..d0ba3e0e6 --- /dev/null +++ b/cmd/info/info.go @@ -0,0 +1,69 @@ +package info + +import ( + "github.com/CircleCI-Public/circleci-cli/api/info" + "github.com/CircleCI-Public/circleci-cli/cmd/validator" + "github.com/CircleCI-Public/circleci-cli/settings" + "github.com/olekukonko/tablewriter" + + "github.com/spf13/cobra" +) + +//infoOptions info command options +type infoOptions struct { + cfg *settings.Config + validator validator.Validator +} + +// NewInfoCommand information cobra command creation +func NewInfoCommand(config *settings.Config, preRunE validator.Validator) *cobra.Command { + client, _ := info.NewInfoClient(*config) + + opts := infoOptions{ + cfg: config, + validator: preRunE, + } + infoCommand := &cobra.Command{ + Use: "info", + Short: "Check information associated to your user account.", + } + orgInfoCmd := orgInfoCommand(client, opts) + infoCommand.AddCommand(orgInfoCmd) + + return infoCommand +} + +//orgInfoCommand organization information subcommand cobra command creation +func orgInfoCommand(client info.InfoClient, opts infoOptions) *cobra.Command { + return &cobra.Command{ + Use: "org", + Short: "View your Organizations' information", + Long: `View your Organizations' names and ids.`, + PreRunE: opts.validator, + RunE: func(cmd *cobra.Command, _ []string) error { + return getOrgInformation(cmd, client) + }, + Annotations: make(map[string]string), + Example: `circleci info org`, + } +} + +//getOrgInformation gets all of the users organizations' information +func getOrgInformation(cmd *cobra.Command, client info.InfoClient) error { + resp, err := client.GetInfo() + if err != nil { + return err + } + + table := tablewriter.NewWriter(cmd.OutOrStdout()) + + table.SetHeader([]string{"ID", "Name"}) + + for _, info := range *resp { + table.Append([]string{ + info.ID, info.Name, + }) + } + table.Render() + return nil +} diff --git a/cmd/info/info_test.go b/cmd/info/info_test.go new file mode 100644 index 000000000..19bda1417 --- /dev/null +++ b/cmd/info/info_test.go @@ -0,0 +1,130 @@ +package info + +import ( + "bytes" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/CircleCI-Public/circleci-cli/cmd/validator" + "github.com/CircleCI-Public/circleci-cli/settings" + "github.com/spf13/cobra" + "gotest.tools/v3/assert" +) + +func TestGetOrgSuccess(t *testing.T) { + id := "id" + name := "name" + + // Test server + var serverHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/me/collaborations") + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(fmt.Sprintf(`[{"id": "%s", "name": "%s"}]`, id, name))) + assert.NilError(t, err) + } + server := httptest.NewServer(serverHandler) + defer server.Close() + + // Test command + cmd, stdout, _ := scaffoldCMD(server.URL, defaultValidator) + args := []string{ + "org", + } + cmd.SetArgs(args) + + // Execute + err := cmd.Execute() + + // Asserts + assert.NilError(t, err) + assert.Equal(t, stdout.String(), `+----+------+ +| ID | NAME | ++----+------+ +| id | name | ++----+------+ +`) +} + +func TestGetOrgError(t *testing.T) { + errorMessage := "server error message" + + // Test server + var serverHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/me/collaborations") + w.WriteHeader(http.StatusInternalServerError) + _, err := w.Write([]byte(fmt.Sprintf(`{"message": "%s"}`, errorMessage))) + assert.NilError(t, err) + } + server := httptest.NewServer(serverHandler) + defer server.Close() + + // Test command + cmd, _, _ := scaffoldCMD(server.URL, defaultValidator) + args := []string{ + "org", + } + cmd.SetArgs(args) + + // Execute + err := cmd.Execute() + + // Asserts + assert.Error(t, err, errorMessage) +} + +func TestFailedValidator(t *testing.T) { + errorMessage := "validator error" + + // Test server + var serverHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/me/collaborations") + w.WriteHeader(http.StatusInternalServerError) + _, err := w.Write([]byte(fmt.Sprintf(`{"message": "%s"}`, errorMessage))) + assert.NilError(t, err) + } + server := httptest.NewServer(serverHandler) + defer server.Close() + + // Test command + cmd, _, _ := scaffoldCMD(server.URL, func(_ *cobra.Command, _ []string) error { + return fmt.Errorf(errorMessage) + }) + args := []string{ + "org", + } + cmd.SetArgs(args) + + // Execute + err := cmd.Execute() + + // Asserts + assert.Error(t, err, errorMessage) +} + +func defaultValidator(cmd *cobra.Command, args []string) error { + return nil +} + +func scaffoldCMD( + baseURL string, + validator validator.Validator, +) (*cobra.Command, *bytes.Buffer, *bytes.Buffer) { + config := &settings.Config{ + Token: "testtoken", + HTTPClient: http.DefaultClient, + Host: baseURL, + } + cmd := NewInfoCommand(config, validator) + + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + cmd.SetOut(stdout) + cmd.SetErr(stderr) + + return cmd, stdout, stderr +} diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index 906acc1ab..457af3275 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -17,15 +17,13 @@ import ( "github.com/araddon/dateparse" "github.com/CircleCI-Public/circleci-cli/api/policy" + "github.com/CircleCI-Public/circleci-cli/cmd/validator" "github.com/CircleCI-Public/circleci-cli/settings" ) -// validator is a cobra command and args validator to be run as persistent PreRun for every policy command. -type validator func(cmd *cobra.Command, args []string) error - // NewCommand creates the root policy command with all policy subcommands attached. -func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { +func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Command { cmd := &cobra.Command{ Use: "policy", PersistentPreRunE: preRunE, diff --git a/cmd/root.go b/cmd/root.go index 6c57cbaa6..5261e5829 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/cobra" "github.com/CircleCI-Public/circleci-cli/api/header" + "github.com/CircleCI-Public/circleci-cli/cmd/info" "github.com/CircleCI-Public/circleci-cli/cmd/policy" "github.com/CircleCI-Public/circleci-cli/cmd/runner" "github.com/CircleCI-Public/circleci-cli/data" @@ -148,6 +149,7 @@ func MakeCommands() *cobra.Command { } rootCmd.AddCommand(newNamespaceCommand(rootOptions)) + rootCmd.AddCommand(info.NewInfoCommand(rootOptions, validator)) rootCmd.AddCommand(newUsageCommand(rootOptions)) rootCmd.AddCommand(newStepCommand(rootOptions)) rootCmd.AddCommand(newSwitchCommand(rootOptions)) diff --git a/cmd/root_test.go b/cmd/root_test.go index e91a397bd..01fbd7205 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -16,7 +16,7 @@ var _ = Describe("Root", func() { Describe("subcommands", func() { It("can create commands", func() { commands := cmd.MakeCommands() - Expect(len(commands.Commands())).To(Equal(21)) + Expect(len(commands.Commands())).To(Equal(22)) }) }) diff --git a/cmd/runner/instance.go b/cmd/runner/instance.go index 98fb1c3c4..28f260937 100644 --- a/cmd/runner/instance.go +++ b/cmd/runner/instance.go @@ -8,9 +8,10 @@ import ( "github.com/spf13/cobra" "github.com/CircleCI-Public/circleci-cli/api/runner" + "github.com/CircleCI-Public/circleci-cli/cmd/validator" ) -func newRunnerInstanceCommand(o *runnerOpts, preRunE validator) *cobra.Command { +func newRunnerInstanceCommand(o *runnerOpts, preRunE validator.Validator) *cobra.Command { cmd := &cobra.Command{ Use: "instance", Short: "Operate on runner instances", diff --git a/cmd/runner/resource_class.go b/cmd/runner/resource_class.go index 0761b42e9..1a727737e 100644 --- a/cmd/runner/resource_class.go +++ b/cmd/runner/resource_class.go @@ -7,9 +7,10 @@ import ( "github.com/spf13/cobra" "github.com/CircleCI-Public/circleci-cli/api/runner" + "github.com/CircleCI-Public/circleci-cli/cmd/validator" ) -func newResourceClassCommand(o *runnerOpts, preRunE validator) *cobra.Command { +func newResourceClassCommand(o *runnerOpts, preRunE validator.Validator) *cobra.Command { cmd := &cobra.Command{ Use: "resource-class", Short: "Operate on runner resource-classes", diff --git a/cmd/runner/runner.go b/cmd/runner/runner.go index 4e3b2afe6..436fc2792 100644 --- a/cmd/runner/runner.go +++ b/cmd/runner/runner.go @@ -7,6 +7,7 @@ import ( "github.com/CircleCI-Public/circleci-cli/api/rest" "github.com/CircleCI-Public/circleci-cli/api/runner" + "github.com/CircleCI-Public/circleci-cli/cmd/validator" "github.com/CircleCI-Public/circleci-cli/settings" ) @@ -14,7 +15,7 @@ type runnerOpts struct { r running } -func NewCommand(config *settings.Config, preRunE validator) *cobra.Command { +func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Command { var opts runnerOpts cmd := &cobra.Command{ Use: "runner", @@ -48,5 +49,3 @@ type running interface { DeleteToken(id string) error GetRunnerInstances(query string) ([]runner.RunnerInstance, error) } - -type validator func(cmd *cobra.Command, args []string) error diff --git a/cmd/runner/token.go b/cmd/runner/token.go index f4a85f5fd..4771b37e9 100644 --- a/cmd/runner/token.go +++ b/cmd/runner/token.go @@ -3,11 +3,12 @@ package runner import ( "time" + "github.com/CircleCI-Public/circleci-cli/cmd/validator" "github.com/olekukonko/tablewriter" "github.com/spf13/cobra" ) -func newTokenCommand(o *runnerOpts, preRunE validator) *cobra.Command { +func newTokenCommand(o *runnerOpts, preRunE validator.Validator) *cobra.Command { cmd := &cobra.Command{ Use: "token", Short: "Operate on runner tokens", diff --git a/cmd/validator/validator.go b/cmd/validator/validator.go new file mode 100644 index 000000000..c6661a70a --- /dev/null +++ b/cmd/validator/validator.go @@ -0,0 +1,5 @@ +package validator + +import "github.com/spf13/cobra" + +type Validator func(cmd *cobra.Command, args []string) error diff --git a/go.sum b/go.sum index 5ee85ddb4..4dda53def 100644 --- a/go.sum +++ b/go.sum @@ -78,7 +78,6 @@ github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JP github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= @@ -90,7 +89,6 @@ github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwT github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= -github.com/Microsoft/hcsshim v0.9.3/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -135,7 +133,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go v1.44.40/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -214,7 +211,6 @@ github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4S github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= -github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= @@ -239,7 +235,6 @@ github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0 github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= github.com/containerd/containerd v1.6.4/go.mod h1:oWOqbuJUZmOVafhA0lj2NAXbiO1u7F0K5l1bUgdyo94= -github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -248,7 +243,6 @@ github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= -github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= @@ -260,7 +254,6 @@ github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-cni v1.1.5/go.mod h1:Rf2ZrMycr1El589IyuRzn7RkfdRZVKaFGaxSDHVAjj0= -github.com/containerd/go-cni v1.1.6/go.mod h1:BWtoWl5ghVymxu6MBjg79W9NZrCRyHIdUtk4cauMe34= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= @@ -296,7 +289,6 @@ github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= github.com/containernetworking/cni v1.1.0/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= -github.com/containernetworking/cni v1.1.1/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= @@ -378,8 +370,6 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -394,13 +384,11 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go. github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -415,7 +403,6 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -458,14 +445,12 @@ github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1 github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -480,7 +465,6 @@ github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= -github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -535,8 +519,6 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= -github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= 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= @@ -653,8 +635,6 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= @@ -683,7 +663,6 @@ github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdY github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY= -github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 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/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -709,7 +688,6 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -758,9 +736,7 @@ github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0Gq github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= -github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= -github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= @@ -832,8 +808,6 @@ github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84 github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runc v1.1.1/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= -github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= -github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -930,7 +904,6 @@ github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= -github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -981,7 +954,6 @@ github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+ 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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -992,7 +964,6 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM= -github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -1033,7 +1004,6 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yashtewari/glob-intersection v0.1.0 h1:6gJvMYQlTDOL3dMsPF6J0+26vwX9MB8/1q3uAdhmTrg= @@ -1131,7 +1101,6 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1224,14 +1193,12 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220621193019-9d032be2e588 h1:9ubFuySsnAJYGyJrZ3koiEv8FyqofCBdz3G9Mbf2YFc= golang.org/x/net v0.0.0-20220621193019-9d032be2e588/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1261,7 +1228,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/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= @@ -1368,10 +1334,7 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1398,7 +1361,6 @@ golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1554,7 +1516,6 @@ google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEc google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220622131801-db39fadba55f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1640,7 +1601,6 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= @@ -1656,13 +1616,11 @@ k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= -k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= -k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= @@ -1671,7 +1629,6 @@ k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y= -k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= @@ -1685,25 +1642,19 @@ k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= -k8s.io/kube-openapi v0.0.0-20220621154418-c39d0f63fac8/go.mod h1:PNbiP2hKArDh8cgJZTDL6Ss/z3wsbga8yjj/7VMB+I4= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= oras.land/oras-go v1.1.1/go.mod h1:n2TE1ummt9MUyprGhT+Q7kGZUF4kVUpYysPFxeV2IpQ= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= @@ -1711,11 +1662,9 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/settings/settings.go b/settings/settings.go index 330f15c38..d9f0953b4 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -7,6 +7,7 @@ import ( "fmt" "io/ioutil" "net/http" + "net/url" "os" "path" "path/filepath" @@ -273,3 +274,26 @@ func isWorldWritable(info os.FileInfo) bool { sysPerms := mode[len(mode)-3:] return strings.Contains(sysPerms, "w") } + +// ServerURL retrieves and formats a ServerURL from our restEndpoint and host. +func (cfg *Config) ServerURL() (*url.URL, error) { + var URL string + + if !strings.HasSuffix(cfg.RestEndpoint, "/") { + URL = fmt.Sprintf("%s/", cfg.RestEndpoint) + } else { + URL = cfg.RestEndpoint + } + + serverURL, err := url.Parse(cfg.Host) + if err != nil { + return nil, err + } + + serverURL, err = serverURL.Parse(URL) + if err != nil { + return nil, err + } + + return serverURL, nil +} diff --git a/settings/settings_test.go b/settings/settings_test.go index 0e72224d2..b98f38060 100644 --- a/settings/settings_test.go +++ b/settings/settings_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/CircleCI-Public/circleci-cli/settings" + "gotest.tools/v3/assert" ) func TestWithHTTPClient(t *testing.T) { @@ -80,3 +81,15 @@ func TestWithHTTPClient(t *testing.T) { }) } } + +func TestServerURL(t *testing.T) { + config := settings.Config{ + Host: "/host", + RestEndpoint: "/restendpoint", + } + + serverURL, err := config.ServerURL() + + assert.NilError(t, err) + assert.Equal(t, serverURL.String(), "/restendpoint/") +} From 52fb20927807eeb5ab069544e8154fa092c72644 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Wed, 20 Jul 2022 13:44:17 -0400 Subject: [PATCH 32/48] fix pandoc installation on CI --- .circleci/config.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e23c022c3..820cfd7d3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -162,7 +162,11 @@ jobs: executor: go steps: - checkout - - run: sudo apt-get install pandoc + - run: + name: Install pandoc + command: | + sudo apt-get update + sudo apt-get install pandoc - gomod - run: go run main.go usage - store_artifacts: From 571ae88aea10d2f551932a3a901d0591ccfa2b28 Mon Sep 17 00:00:00 2001 From: David Desmarais-Michaud Date: Fri, 22 Jul 2022 09:35:24 -0400 Subject: [PATCH 33/48] added metafile flag for policy decisions (#757) * stripped name from Policy CLI commands * update policy agent and remove name from policy apis * added metafile flag for policy decisions --- api/policy/policy.go | 7 +- api/policy/policy_test.go | 50 ++++---- cmd/policy/policy.go | 110 +++++++++--------- cmd/policy/policy_test.go | 102 ++++++++-------- cmd/policy/testdata/meta.yml | 2 + .../testdata/policy/create-expected-usage.txt | 1 - .../testdata/policy/decide-expected-usage.txt | 7 +- .../testdata/policy/update-expected-usage.txt | 1 - cmd/policy/testdata/test0/meta-policy.rego | 6 + cmd/policy/testdata/test0/policy.rego | 1 + go.mod | 14 +-- go.sum | 86 +++++++------- 12 files changed, 192 insertions(+), 195 deletions(-) create mode 100644 cmd/policy/testdata/meta.yml create mode 100644 cmd/policy/testdata/test0/meta-policy.rego diff --git a/api/policy/policy.go b/api/policy/policy.go index 144fd7613..0d505dcf2 100644 --- a/api/policy/policy.go +++ b/api/policy/policy.go @@ -63,7 +63,6 @@ func (c Client) ListPolicies(ownerID string) (interface{}, error) { // CreationRequest represents the json payload to create a Policy in the Policy-Service type CreationRequest struct { - Name string `json:"name"` Context string `json:"context"` Content string `json:"content"` } @@ -106,7 +105,6 @@ func (c Client) CreatePolicy(ownerID string, policy CreationRequest) (interface{ } type UpdateRequest struct { - Name *string `json:"name,omitempty"` Context *string `json:"context,omitempty"` Content *string `json:"content,omitempty"` } @@ -271,8 +269,9 @@ func (c Client) GetDecisionLogs(ownerID string, request DecisionQueryRequest) ([ // DecisionRequest represents a request to Policy-Service to evaluate a given input against an organization's policies. // The context determines which policies to apply. type DecisionRequest struct { - Input string `json:"input"` - Context string `json:"context"` + Input string `json:"input"` + Context string `json:"context"` + Metadata map[string]interface{} `json:"metadata,omitempty"` } // MakeDecision sends a requests to Policy-Service public decision endpoint and returns the decision response diff --git a/api/policy/policy_test.go b/api/policy/policy_test.go index 3eb2f7f31..76f637e38 100644 --- a/api/policy/policy_test.go +++ b/api/policy/policy_test.go @@ -42,7 +42,7 @@ func TestClientListPolicies(t *testing.T) { t.Run("List Policies - Forbidden", func(t *testing.T) { expectedResponse := `{"error": "Forbidden"}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusForbidden) _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) @@ -59,7 +59,7 @@ func TestClientListPolicies(t *testing.T) { t.Run("List Policies - Bad error json", func(t *testing.T) { expectedResponse := `{"this is bad json": }` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusForbidden) _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) @@ -80,7 +80,7 @@ func TestClientListPolicies(t *testing.T) { var expectedResponseValue interface{} assert.NilError(t, json.Unmarshal([]byte(expectedResponse), &expectedResponseValue)) - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) })) @@ -117,7 +117,7 @@ func TestClientListPolicies(t *testing.T) { var expectedResponseValue interface{} assert.NilError(t, json.Unmarshal([]byte(expectedResponse), &expectedResponseValue)) - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) })) @@ -160,7 +160,7 @@ func TestClientGetPolicy(t *testing.T) { t.Run("Get Policy - Bad Request", func(t *testing.T) { expectedResponse := `{"error": "PolicyID: must be a valid UUID."}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusBadRequest) _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) @@ -177,7 +177,7 @@ func TestClientGetPolicy(t *testing.T) { t.Run("Get Policy - Forbidden", func(t *testing.T) { expectedResponse := `{"error": "Forbidden"}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusForbidden) _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) @@ -194,7 +194,7 @@ func TestClientGetPolicy(t *testing.T) { t.Run("Get Policy - Not Found", func(t *testing.T) { expectedResponse := `{"error": "policy not found"}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) @@ -224,7 +224,7 @@ func TestClientGetPolicy(t *testing.T) { var expectedResponseValue interface{} assert.NilError(t, json.Unmarshal([]byte(expectedResponse), &expectedResponseValue)) - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) })) @@ -242,7 +242,6 @@ func TestClientGetPolicy(t *testing.T) { func TestClientCreatePolicy(t *testing.T) { t.Run("expected request", func(t *testing.T) { req := CreationRequest{ - Name: "test-name", Context: "config", Content: "test-content", } @@ -276,7 +275,7 @@ func TestClientCreatePolicy(t *testing.T) { t.Run("unexpected status code", func(t *testing.T) { expectedResponse := `{"error": "Forbidden"}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusForbidden) _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) @@ -317,7 +316,7 @@ func TestClientDeletePolicy(t *testing.T) { t.Run("Delete Policy - Bad Request", func(t *testing.T) { expectedResponse := `{"error": "PolicyID: must be a valid UUID."}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusBadRequest) _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) @@ -333,7 +332,7 @@ func TestClientDeletePolicy(t *testing.T) { t.Run("Delete Policy - Forbidden", func(t *testing.T) { expectedResponse := `{"error": "Forbidden"}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusForbidden) _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) @@ -349,7 +348,7 @@ func TestClientDeletePolicy(t *testing.T) { t.Run("Delete Policy - Not Found", func(t *testing.T) { expectedResponse := `{"error": "policy not found"}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) @@ -364,7 +363,7 @@ func TestClientDeletePolicy(t *testing.T) { }) t.Run("Delete Policy - successfully deletes a policy", func(t *testing.T) { - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) })) defer svr.Close() @@ -379,11 +378,9 @@ func TestClientDeletePolicy(t *testing.T) { func TestClientUpdatePolicy(t *testing.T) { t.Run("expected request", func(t *testing.T) { - name := "test-name" context := "config" content := "test-content" req := UpdateRequest{ - Name: &name, Context: &context, Content: &content, } @@ -417,7 +414,7 @@ func TestClientUpdatePolicy(t *testing.T) { t.Run("unexpected status code", func(t *testing.T) { expectedResponse := `{"error": "Forbidden"}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusForbidden) _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) @@ -463,10 +460,7 @@ func TestClientUpdatePolicy(t *testing.T) { }) t.Run("one change", func(t *testing.T) { - name := "test-name" - req := UpdateRequest{ - Name: &name, - } + req := UpdateRequest{} svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Header.Get("circle-token"), "testtoken") @@ -591,7 +585,7 @@ func TestClientGetDecisionLogs(t *testing.T) { t.Run("Get Decision Logs - Bad Request", func(t *testing.T) { expectedResponse := `{"error": "Offset: must be an integer number."}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusBadRequest) _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) @@ -608,7 +602,7 @@ func TestClientGetDecisionLogs(t *testing.T) { t.Run("Get Decision Logs - Forbidden", func(t *testing.T) { expectedResponse := `{"error": "Forbidden"}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusForbidden) _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) @@ -629,7 +623,7 @@ func TestClientGetDecisionLogs(t *testing.T) { var expectedResponseValue []interface{} assert.NilError(t, json.Unmarshal([]byte(expectedResponse), &expectedResponseValue)) - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) })) @@ -676,7 +670,7 @@ func TestClientGetDecisionLogs(t *testing.T) { var expectedResponseValue []interface{} assert.NilError(t, json.Unmarshal([]byte(expectedResponse), &expectedResponseValue)) - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, err := w.Write([]byte(expectedResponse)) assert.NilError(t, err) })) @@ -728,7 +722,7 @@ func TestMakeDecision(t *testing.T) { Name: "unexpected statuscode", OwnerID: "test-owner", Request: DecisionRequest{}, - Handler: func(w http.ResponseWriter, r *http.Request) { + Handler: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(400) _, _ = io.WriteString(w, `{"error":"that was a bad request!"}`) }, @@ -739,7 +733,7 @@ func TestMakeDecision(t *testing.T) { Name: "unexpected statuscode no body", OwnerID: "test-owner", Request: DecisionRequest{}, - Handler: func(w http.ResponseWriter, r *http.Request) { + Handler: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(204) }, ExpectedError: errors.New("unexpected status-code: 204"), @@ -748,7 +742,7 @@ func TestMakeDecision(t *testing.T) { Name: "bad decoding", OwnerID: "test-owner", Request: DecisionRequest{}, - Handler: func(w http.ResponseWriter, r *http.Request) { + Handler: func(w http.ResponseWriter, _ *http.Request) { _, _ = io.WriteString(w, "not a json response") }, ExpectedError: errors.New("failed to decode response body: invalid character 'o' in literal null (expecting 'u')"), diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index 457af3275..6b0650898 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "os" "time" @@ -42,7 +41,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com cmd := &cobra.Command{ Short: "List all policies", Use: "list", - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { policies, err := policy.NewClient(*policyBaseURL, config).ListPolicies(*ownerID) if err != nil { return fmt.Errorf("failed to list policies: %v", err) @@ -67,17 +66,14 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com cmd := &cobra.Command{ Short: "create policy", Use: "create", - RunE: func(cmd *cobra.Command, args []string) error { - policyData, err := ioutil.ReadFile(policyPath) + RunE: func(cmd *cobra.Command, _ []string) error { + policyData, err := os.ReadFile(policyPath) if err != nil { return fmt.Errorf("failed to read policy file: %w", err) } - creationRequest.Content = string(policyData) - client := policy.NewClient(*policyBaseURL, config) - - result, err := client.CreatePolicy(*ownerID, creationRequest) + result, err := policy.NewClient(*policyBaseURL, config).CreatePolicy(*ownerID, creationRequest) if err != nil { return fmt.Errorf("failed to create policy: %w", err) } @@ -92,13 +88,9 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com Example: `policy create --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --name policy_name --policy ./policy.rego`, } - cmd.Flags().StringVar(&creationRequest.Name, "name", "", "name of policy to create") cmd.Flags().StringVar(&creationRequest.Context, "context", "config", "policy context") cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego policy file") - if err := cmd.MarkFlagRequired("name"); err != nil { - panic(err) - } if err := cmd.MarkFlagRequired("policy"); err != nil { panic(err) } @@ -150,41 +142,30 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com update := func() *cobra.Command { var policyPath string var context string - var name string var updateRequest policy.UpdateRequest cmd := &cobra.Command{ Short: "Update a policy", Use: "update ", RunE: func(cmd *cobra.Command, args []string) error { - if !(cmd.Flag("policy").Changed || - cmd.Flag("context").Changed || - cmd.Flag("name").Changed) { - return fmt.Errorf("one of policy, context, or name must be set") + if !(cmd.Flag("policy").Changed || cmd.Flag("context").Changed) { + return fmt.Errorf("one of policy or context must be set") } if cmd.Flag("policy").Changed { - policyData, err := ioutil.ReadFile(policyPath) + policyData, err := os.ReadFile(policyPath) if err != nil { return fmt.Errorf("failed to read policy file: %w", err) } - content := string(policyData) - updateRequest.Content = &content } - client := policy.NewClient(*policyBaseURL, config) - if cmd.Flag("context").Changed { updateRequest.Context = &context } - if cmd.Flag("name").Changed { - updateRequest.Name = &name - } - - result, err := client.UpdatePolicy(*ownerID, args[0], updateRequest) + result, err := policy.NewClient(*policyBaseURL, config).UpdatePolicy(*ownerID, args[0], updateRequest) if err != nil { return fmt.Errorf("failed to update policy: %w", err) } @@ -199,7 +180,6 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com Example: `policy update e9e300d1-5bab-4704-b610-addbd6e03b0b --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --name policy_name --policy ./policy.rego`, } - cmd.Flags().StringVar(&name, "name", "", "set name of the given policy-id") cmd.Flags().StringVar(&context, "context", "", "policy context (if set, must be config)") cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego file containing the updated policy") @@ -213,7 +193,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com cmd := &cobra.Command{ Short: "Get policy (decision) logs", Use: "logs", - RunE: func(cmd *cobra.Command, args []string) (err error) { + RunE: func(cmd *cobra.Command, _ []string) (err error) { if cmd.Flag("after").Changed { request.After = new(time.Time) *request.After, err = dateparse.ParseStrict(after) @@ -293,31 +273,45 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com }() decide := func() *cobra.Command { - var inputPath, policyPath string - var request policy.DecisionRequest + var ( + inputPath string + policyPath string + metaFile string + request policy.DecisionRequest + ) cmd := &cobra.Command{ Short: "make a decision", Use: "decide", - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { if policyPath == "" && *ownerID == "" { return fmt.Errorf("--owner-id or --policy is required") } - var decision interface{} - input, err := ioutil.ReadFile(inputPath) + input, err := os.ReadFile(inputPath) if err != nil { - return fmt.Errorf("failed to read file: %w", err) + return fmt.Errorf("failed to read input file: %w", err) } - request.Input = string(input) - - if policyPath != "" { - // make decision from local policy - decision, err = getPolicyDecisionLocally(policyPath, request.Input) - } else { - // make decision from policy obtained from policy-service - decision, err = policy.NewClient(*policyBaseURL, config).MakeDecision(*ownerID, request) + + var metadata map[string]interface{} + if metaFile != "" { + raw, err := os.ReadFile(metaFile) + if err != nil { + return fmt.Errorf("failed to read meta file: %w", err) + } + if err := yaml.Unmarshal(raw, &metadata); err != nil { + return fmt.Errorf("failed to decode meta content: %w", err) + } } + + decision, err := func() (interface{}, error) { + if policyPath != "" { + return getPolicyDecisionLocally(policyPath, input, metadata) + } + request.Input = string(input) + request.Metadata = metadata + return policy.NewClient(*policyBaseURL, config).MakeDecision(*ownerID, request) + }() if err != nil { return fmt.Errorf("failed to make decision: %w", err) } @@ -325,15 +319,19 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(decision); err != nil { return fmt.Errorf("failed to encode decision: %w", err) } + return nil }, Args: cobra.ExactArgs(0), } + // Redeclared flag to make optional + cmd.Flags().StringVar(ownerID, "owner-id", "", "the id of the policy's owner") + cmd.Flags().StringVar(&request.Context, "context", "config", "policy context for decision") cmd.Flags().StringVar(&inputPath, "input", "", "path to input file") cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego policy file or directory containing policy files") - cmd.Flags().StringVar(ownerID, "owner-id", "", "the id of the policy's owner") //Redeclared flag to make optional + cmd.Flags().StringVar(&metaFile, "metafile", "", "decision metadata file") _ = cmd.MarkFlagRequired("input") @@ -358,31 +356,31 @@ func prettyJSONEncoder(dst io.Writer) *json.Encoder { return enc } -//getPolicyDecisionLocally takes path of policy path/directory and input (eg build config) as string, and performs policy evaluation locally -func getPolicyDecisionLocally(policyPath, inputString string) (*cpa.Decision, error) { +// getPolicyDecisionLocally takes path of policy path/directory and input (eg build config) as string, and performs policy evaluation locally +func getPolicyDecisionLocally(policyPath string, rawInput []byte, meta map[string]interface{}) (*cpa.Decision, error) { var input interface{} - if err := yaml.Unmarshal([]byte(inputString), &input); err != nil { + if err := yaml.Unmarshal(rawInput, &input); err != nil { return nil, fmt.Errorf("invalid input: %w", err) } - var parsedPolicy *cpa.Policy pathInfo, err := os.Stat(policyPath) if err != nil { return nil, fmt.Errorf("failed to get path info: %w", err) } - //if policyPath is directory get content of all files in this directory (non-recursively) and add to document bundle - if pathInfo.IsDir() { - parsedPolicy, err = cpa.LoadPolicyDirectory(policyPath) - } else { - parsedPolicy, err = cpa.LoadPolicyFile(policyPath) - } + loadPolicy := func() func(string) (*cpa.Policy, error) { + if pathInfo.IsDir() { + return cpa.LoadPolicyDirectory + } + return cpa.LoadPolicyFile + }() + + policy, err := loadPolicy(policyPath) if err != nil { return nil, fmt.Errorf("failed to load policy files: %w", err) } - ctx := context.Background() - decision, err := parsedPolicy.Decide(ctx, input) + decision, err := policy.Decide(context.Background(), input, cpa.Meta(meta)) if err != nil { return nil, fmt.Errorf("failed to make decision: %w", err) } diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index 7b4f2ee40..65fde4a31 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -117,18 +117,18 @@ func TestCreatePolicy(t *testing.T) { ExpectedErr string }{ { - Name: "requires owner-id and name and policy", + Name: "requires owner-id and policy", Args: []string{"create"}, - ExpectedErr: "required flag(s) \"name\", \"owner-id\", \"policy\" not set", + ExpectedErr: "required flag(s) \"owner-id\", \"policy\" not set", }, { Name: "fails for policy file not found", - Args: []string{"create", "--owner-id", "test-org", "--name", "test-policy", "--policy", "./testdata/file_not_present.rego"}, + Args: []string{"create", "--owner-id", "test-org", "--policy", "./testdata/file_not_present.rego"}, ExpectedErr: "failed to read policy file: open ./testdata/file_not_present.rego: ", }, { Name: "sends appropriate desired request", - Args: []string{"create", "--owner-id", "test-org", "--name", "test-policy", "--policy", "./testdata/test.rego"}, + Args: []string{"create", "--owner-id", "test-org", "--policy", "./testdata/test.rego"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} assert.Equal(t, r.Method, "POST") @@ -137,7 +137,6 @@ func TestCreatePolicy(t *testing.T) { assert.DeepEqual(t, body, map[string]interface{}{ "content": "package test", "context": "config", - "name": "test-policy", }) w.WriteHeader(http.StatusCreated) @@ -367,14 +366,13 @@ func TestUpdatePolicy(t *testing.T) { }, { Name: "gets error response", - Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--name", "test-policy", "--policy", "./testdata/test.rego"}, + Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--policy", "./testdata/test.rego"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) assert.Equal(t, r.URL.Path, "/api/v1/owner/test-org/policy/test-policy-id") assert.DeepEqual(t, body, map[string]interface{}{ "content": "package test", - "name": "test-policy", }) w.WriteHeader(http.StatusForbidden) @@ -385,14 +383,13 @@ func TestUpdatePolicy(t *testing.T) { }, { Name: "sends appropriate desired request", - Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--name", "test-policy", "--policy", "./testdata/test.rego"}, + Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--policy", "./testdata/test.rego"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) assert.Equal(t, r.URL.Path, "/api/v1/owner/test-org/policy/test-policy-id") assert.DeepEqual(t, body, map[string]interface{}{ "content": "package test", - "name": "test-policy", }) w.WriteHeader(http.StatusOK) @@ -403,14 +400,13 @@ func TestUpdatePolicy(t *testing.T) { }, { Name: "explicitly set config", - Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--name", "test-policy", "--policy", "./testdata/test.rego", "--context", "config"}, + Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--policy", "./testdata/test.rego", "--context", "config"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) assert.Equal(t, r.URL.Path, "/api/v1/owner/test-org/policy/test-policy-id") assert.DeepEqual(t, body, map[string]interface{}{ "content": "package test", - "name": "test-policy", "context": "config", }) @@ -420,23 +416,6 @@ func TestUpdatePolicy(t *testing.T) { }, ExpectedOutput: "{}\n", }, - { - Name: "sends appropriate desired request with only name", - Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--name", "test-policy"}, - ServerHandler: func(w http.ResponseWriter, r *http.Request) { - var body map[string]interface{} - assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-org/policy/test-policy-id") - assert.DeepEqual(t, body, map[string]interface{}{ - "name": "test-policy", - }) - - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte("{}")) - assert.NilError(t, err) - }, - ExpectedOutput: "{}\n", - }, { Name: "sends appropriate desired request with only policy path", Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--policy", "./testdata/test.rego"}, @@ -454,28 +433,10 @@ func TestUpdatePolicy(t *testing.T) { }, ExpectedOutput: "{}\n", }, - { - Name: "sends appropriate desired request - policy name and content", - Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--name", "test-policy", "--policy", "./testdata/test.rego"}, - ServerHandler: func(w http.ResponseWriter, r *http.Request) { - var body map[string]interface{} - assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-org/policy/test-policy-id") - assert.DeepEqual(t, body, map[string]interface{}{ - "content": "package test", - "name": "test-policy", - }) - - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte("{}")) - assert.NilError(t, err) - }, - ExpectedOutput: "{}\n", - }, { Name: "check at least one field is changed", Args: []string{"update", "test-policy-id", "--owner-id", "test-org"}, - ExpectedErr: "one of policy, context, or name must be set", + ExpectedErr: "one of policy or context must be set", }, } @@ -700,10 +661,33 @@ func TestMakeDecisionCommand(t *testing.T) { }, ExpectedOutput: "{\n \"status\": \"PASS\"\n}\n", }, + { + Name: "sends expected request with metadata", + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test.yml", "--context", "custom", "--metafile", "./testdata/meta.yml"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "POST") + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/decision") + + var payload map[string]interface{} + assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) + + assert.DeepEqual(t, payload, map[string]interface{}{ + "context": "custom", + "input": "test: config\n", + "metadata": map[string]interface{}{ + "project_id": "test-project-id", + "branch": "main", + }, + }) + + _, _ = io.WriteString(w, `{"status":"PASS"}`) + }, + ExpectedOutput: "{\n \"status\": \"PASS\"\n}\n", + }, { Name: "fails on unexpected status code", Args: []string{"decide", "--input", "./testdata/test.yml", "--owner-id", "test-owner"}, - ServerHandler: func(w http.ResponseWriter, r *http.Request) { + ServerHandler: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(500) _, _ = io.WriteString(w, `{"error":"oopsie!"}`) }, @@ -718,7 +702,7 @@ func TestMakeDecisionCommand(t *testing.T) { { Name: "fails for input file not found", Args: []string{"decide", "--policy", "./testdata/policy.rego", "--input", "./testdata/no_such_file.yml"}, - ExpectedErr: "failed to read file: open ./testdata/no_such_file.yml: ", + ExpectedErr: "failed to read input file: open ./testdata/no_such_file.yml: ", }, { Name: "fails for policy FILE/DIRECTORY not found", @@ -727,14 +711,30 @@ func TestMakeDecisionCommand(t *testing.T) { }, { Name: "successfully performs decision for policy FILE provided locally", - Args: []string{"decide", "--policy", "./testdata/test0/policy.rego", "--input", - "./testdata/test0/config.yml"}, + Args: []string{ + "decide", "--policy", "./testdata/test0/policy.rego", "--input", + "./testdata/test0/config.yml", + }, ExpectedOutput: `{ "status": "PASS", "enabled_rules": [ "branch_is_main" ] } +`, + }, + { + Name: "successfully performs decision with metadata for policy FILE provided locally", + Args: []string{ + "decide", "--metafile", "./testdata/meta.yml", "--policy", "./testdata/test0/meta-policy.rego", "--input", + "./testdata/test0/config.yml", + }, + ExpectedOutput: `{ + "status": "PASS", + "enabled_rules": [ + "enabled" + ] +} `, }, } diff --git a/cmd/policy/testdata/meta.yml b/cmd/policy/testdata/meta.yml new file mode 100644 index 000000000..ed8d9bde4 --- /dev/null +++ b/cmd/policy/testdata/meta.yml @@ -0,0 +1,2 @@ +project_id: test-project-id +branch: main diff --git a/cmd/policy/testdata/policy/create-expected-usage.txt b/cmd/policy/testdata/policy/create-expected-usage.txt index 8cd309aaa..d483057e6 100644 --- a/cmd/policy/testdata/policy/create-expected-usage.txt +++ b/cmd/policy/testdata/policy/create-expected-usage.txt @@ -6,7 +6,6 @@ policy create --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --name policy_name Flags: --context string policy context (default "config") - --name string name of policy to create --policy string path to rego policy file Global Flags: diff --git a/cmd/policy/testdata/policy/decide-expected-usage.txt b/cmd/policy/testdata/policy/decide-expected-usage.txt index 76231ff97..ede4b417c 100644 --- a/cmd/policy/testdata/policy/decide-expected-usage.txt +++ b/cmd/policy/testdata/policy/decide-expected-usage.txt @@ -2,9 +2,10 @@ Usage: policy decide [flags] Flags: - --context string policy context for decision (default "config") - --input string path to input file - --policy string path to rego policy file or directory containing policy files + --context string policy context for decision (default "config") + --input string path to input file + --metafile string decision metadata file + --policy string path to rego policy file or directory containing policy files Global Flags: --owner-id string the id of the policy's owner diff --git a/cmd/policy/testdata/policy/update-expected-usage.txt b/cmd/policy/testdata/policy/update-expected-usage.txt index a1b3d3a01..24c70058d 100644 --- a/cmd/policy/testdata/policy/update-expected-usage.txt +++ b/cmd/policy/testdata/policy/update-expected-usage.txt @@ -6,7 +6,6 @@ policy update e9e300d1-5bab-4704-b610-addbd6e03b0b --owner-id 462d67f8-b232-4da4 Flags: --context string policy context (if set, must be config) - --name string set name of the given policy-id --policy string path to rego file containing the updated policy Global Flags: diff --git a/cmd/policy/testdata/test0/meta-policy.rego b/cmd/policy/testdata/test0/meta-policy.rego new file mode 100644 index 000000000..a083a8472 --- /dev/null +++ b/cmd/policy/testdata/test0/meta-policy.rego @@ -0,0 +1,6 @@ +package org + +policy_name["meta_policy_test"] + +enable_rule["enabled"] { data.meta.branch == "main" } +enable_rule["disabled"] { data.meta.project_id != "test-project-id" } diff --git a/cmd/policy/testdata/test0/policy.rego b/cmd/policy/testdata/test0/policy.rego index 9e2890022..c6056bba0 100644 --- a/cmd/policy/testdata/test0/policy.rego +++ b/cmd/policy/testdata/test0/policy.rego @@ -1,3 +1,4 @@ package org +policy_name["test"] enable_rule["branch_is_main"] branch_is_main = "branch must be main!" { input.branch != "main" } \ No newline at end of file diff --git a/go.mod b/go.mod index 4aa647954..002cf223b 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/CircleCI-Public/circleci-cli require ( github.com/AlecAivazis/survey/v2 v2.1.1 - github.com/CircleCI-Public/circle-policy-agent v0.0.54 + github.com/CircleCI-Public/circle-policy-agent v0.0.116 github.com/Masterminds/semver v1.4.2 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/blang/semver v3.5.1+incompatible @@ -21,7 +21,7 @@ require ( github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 github.com/pkg/errors v0.9.1 github.com/rhysd/go-github-selfupdate v0.0.0-20180520142321-41c1bbb0804a - github.com/spf13/cobra v1.4.0 + github.com/spf13/cobra v1.5.0 github.com/spf13/pflag v1.0.5 github.com/tcnksm/go-gitconfig v0.1.2 // indirect github.com/ulikunitz/xz v0.5.9 // indirect @@ -52,18 +52,18 @@ require ( github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/nxadm/tail v1.4.8 // indirect - github.com/open-policy-agent/opa v0.41.0 // indirect + github.com/open-policy-agent/opa v0.42.2 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.1.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect - github.com/vektah/gqlparser/v2 v2.4.5 // indirect + github.com/vektah/gqlparser/v2 v2.4.6 // indirect github.com/xanzy/ssh-agent v0.2.1 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yashtewari/glob-intersection v0.1.0 // indirect - golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect - golang.org/x/net v0.0.0-20220621193019-9d032be2e588 // indirect - golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect + golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect + golang.org/x/net v0.0.0-20220708220712-1185a9018129 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/protobuf v1.28.0 // indirect diff --git a/go.sum b/go.sum index 4dda53def..945a5c66f 100644 --- a/go.sum +++ b/go.sum @@ -51,11 +51,11 @@ github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSW github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY= +github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= +github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= @@ -65,8 +65,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CircleCI-Public/circle-policy-agent v0.0.54 h1:Z1eHtqm/l8Az28i22s3+O7nK/APRtSmrspUDEG3UWmU= -github.com/CircleCI-Public/circle-policy-agent v0.0.54/go.mod h1:9sif3N0AlIuqJ9A4PJ90wrvUZyAuNBmywYxScPHOY6E= +github.com/CircleCI-Public/circle-policy-agent v0.0.116 h1:ur8yWobPNig5GiPpriPoaMN8/W/80z48CDztCIoZIig= +github.com/CircleCI-Public/circle-policy-agent v0.0.116/go.mod h1:JB9mRsC/ua95h+E8rMgudFDWqE6fR4Y4l73qzdNpPbw= github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= @@ -89,6 +89,7 @@ github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwT github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= +github.com/Microsoft/hcsshim v0.9.3/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -132,7 +133,7 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.43.16/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -143,7 +144,6 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -234,7 +234,7 @@ github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTV github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= -github.com/containerd/containerd v1.6.4/go.mod h1:oWOqbuJUZmOVafhA0lj2NAXbiO1u7F0K5l1bUgdyo94= +github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -253,7 +253,7 @@ github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZH github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= -github.com/containerd/go-cni v1.1.5/go.mod h1:Rf2ZrMycr1El589IyuRzn7RkfdRZVKaFGaxSDHVAjj0= +github.com/containerd/go-cni v1.1.6/go.mod h1:BWtoWl5ghVymxu6MBjg79W9NZrCRyHIdUtk4cauMe34= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= @@ -288,7 +288,7 @@ github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= -github.com/containernetworking/cni v1.1.0/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= +github.com/containernetworking/cni v1.1.1/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= @@ -319,6 +319,7 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -344,15 +345,15 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUn github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= -github.com/distribution/distribution/v3 v3.0.0-20211118083504-a29a3c99a684/go.mod h1:UfCu3YXJJCI+IdnqGgYP82dk2+Joxmv+mUTVBES6wac= +github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269/go.mod h1:28YO/VJk9/64+sTGNuYaBjWxrXTPrj0C0XmgTIOjxX4= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.11+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= @@ -451,7 +452,6 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= @@ -473,6 +473,7 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= @@ -634,7 +635,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= @@ -662,11 +664,10 @@ github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY= +github.com/klauspost/compress v1.15.8 h1:JahtItbkWjf2jzm/T+qgMxkP9EMHsqEUA6vCMGmXvhA= 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/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -682,7 +683,6 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -788,8 +788,8 @@ github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDs github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/open-policy-agent/opa v0.41.0 h1:XDTkP8bcUVuY8WOVbRY4e/KZW31+f+/cxisPc0TPe5E= -github.com/open-policy-agent/opa v0.41.0/go.mod h1:+kB8/8/4meTlq6ZmYRnvrL5nNrykd2eckDx4O6rk/dA= +github.com/open-policy-agent/opa v0.42.2 h1:qocVAKyjrqMjCqsU02S/gHyLr4AQQ9xMtuV1kKnnyhM= +github.com/open-policy-agent/opa v0.42.2/go.mod h1:MrmoTi/BsKWT58kXlVayBb+rYVeaMwuBm3nYAN3923s= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -807,7 +807,7 @@ github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rm github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= -github.com/opencontainers/runc v1.1.1/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= +github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -827,7 +827,7 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -835,7 +835,6 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 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= @@ -849,8 +848,9 @@ github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQ github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= +github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -925,19 +925,16 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -947,13 +944,13 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -963,7 +960,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -983,9 +982,9 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ 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/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/vektah/gqlparser/v2 v2.4.4/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0= -github.com/vektah/gqlparser/v2 v2.4.5 h1:C02NsyEsL4TXJB7ndonqTfuQOL4XPIu0aAWugdmTgmc= github.com/vektah/gqlparser/v2 v2.4.5/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0= +github.com/vektah/gqlparser/v2 v2.4.6 h1:Yjzp66g6oVq93Jihbi0qhGnf/6zIWjcm8H6gA27zstE= +github.com/vektah/gqlparser/v2 v2.4.6/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= @@ -1090,7 +1089,6 @@ golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/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-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1100,9 +1098,11 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1198,9 +1198,9 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220621193019-9d032be2e588 h1:9ubFuySsnAJYGyJrZ3koiEv8FyqofCBdz3G9Mbf2YFc= -golang.org/x/net v0.0.0-20220621193019-9d032be2e588/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0= +golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 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/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1212,7 +1212,6 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= @@ -1228,6 +1227,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/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= @@ -1335,8 +1335,8 @@ golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1455,7 +1455,6 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1576,7 +1575,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= @@ -1655,7 +1653,7 @@ k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -oras.land/oras-go v1.1.1/go.mod h1:n2TE1ummt9MUyprGhT+Q7kGZUF4kVUpYysPFxeV2IpQ= +oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From 990c791ab36038b0ac3dc0d0be16bc840a11745f Mon Sep 17 00:00:00 2001 From: Sagar Gupta Date: Tue, 26 Jul 2022 14:33:29 -0400 Subject: [PATCH 34/48] Added some human-friendly outputs (#759) --- cmd/policy/policy.go | 6 +++++- cmd/policy/policy_test.go | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index 6b0650898..356e20f2f 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -78,6 +78,8 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com return fmt.Errorf("failed to create policy: %w", err) } + _, _ = io.WriteString(cmd.ErrOrStderr(), "Policy Created Successfully\n") + if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(result); err != nil { return fmt.Errorf("failed to encode result to stdout: %w", err) } @@ -129,7 +131,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com if err != nil { return fmt.Errorf("failed to delete policy: %v", err) } - _, _ = io.WriteString(cmd.OutOrStdout(), "Deleted Successfully\n") + _, _ = io.WriteString(cmd.ErrOrStderr(), "Policy Deleted Successfully\n") return nil }, Args: cobra.ExactArgs(1), @@ -170,6 +172,8 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com return fmt.Errorf("failed to update policy: %w", err) } + _, _ = io.WriteString(cmd.ErrOrStderr(), "Policy Updated Successfully\n") + if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(result); err != nil { return fmt.Errorf("failed to encode result to stdout: %w", err) } diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index 65fde4a31..006656c33 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -299,7 +299,6 @@ func TestDeletePolicy(t *testing.T) { assert.Equal(t, r.URL.String(), "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/policy/60b7e1a5-c1d7-4422-b813-7a12d353d7c6") w.WriteHeader(http.StatusNoContent) }, - ExpectedOutput: "Deleted Successfully\n", }, } From c69fe8259aa901af303bcb6e06a40dd9d91eb7b8 Mon Sep 17 00:00:00 2001 From: Navaneet Ramabadran <42252054+nramabad@users.noreply.github.com> Date: Thu, 21 Jul 2022 00:03:16 -0700 Subject: [PATCH 35/48] Feat: add SkipInsecureVerify to runner command --- api/rest/client.go | 14 +++++++++----- api/rest/client_test.go | 11 ++++++++++- api/runner/runner_test.go | 11 ++++++++++- cmd/runner/runner.go | 2 +- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/api/rest/client.go b/api/rest/client.go index 9b06d97a2..1167a9481 100644 --- a/api/rest/client.go +++ b/api/rest/client.go @@ -12,6 +12,7 @@ import ( "time" "github.com/CircleCI-Public/circleci-cli/api/header" + "github.com/CircleCI-Public/circleci-cli/settings" "github.com/CircleCI-Public/circleci-cli/version" ) @@ -21,19 +22,22 @@ type Client struct { client *http.Client } -func New(host, endpoint, circleToken string) *Client { +func New(host string, config *settings.Config) *Client { // Ensure endpoint ends with a slash + endpoint := config.RestEndpoint if !strings.HasSuffix(endpoint, "/") { endpoint += "/" } u, _ := url.Parse(host) + + client := config.HTTPClient + client.Timeout = 10 * time.Second + return &Client{ baseURL: u.ResolveReference(&url.URL{Path: endpoint}), - circleToken: circleToken, - client: &http.Client{ - Timeout: 10 * time.Second, - }, + circleToken: config.Token, + client: client, } } diff --git a/api/rest/client_test.go b/api/rest/client_test.go index 619dc87ea..b4761cac2 100644 --- a/api/rest/client_test.go +++ b/api/rest/client_test.go @@ -12,6 +12,7 @@ import ( "gotest.tools/v3/assert" "gotest.tools/v3/assert/cmp" + "github.com/CircleCI-Public/circleci-cli/settings" "github.com/CircleCI-Public/circleci-cli/version" ) @@ -167,5 +168,13 @@ func (f *fixture) Run(statusCode int, respBody string) (c *Client, cleanup func( }) server := httptest.NewServer(mux) - return New(server.URL, "api/v2", "fake-token"), server.Close + cfg := &settings.Config{ + Debug: false, + Token: "fake-token", + RestEndpoint: "api/v2", + Endpoint: "api/v2", + HTTPClient: http.DefaultClient, + } + + return New(server.URL, cfg), server.Close } diff --git a/api/runner/runner_test.go b/api/runner/runner_test.go index dd25a0d4b..77d9dcfe6 100644 --- a/api/runner/runner_test.go +++ b/api/runner/runner_test.go @@ -14,6 +14,7 @@ import ( "gotest.tools/v3/assert/cmp" "github.com/CircleCI-Public/circleci-cli/api/rest" + "github.com/CircleCI-Public/circleci-cli/settings" "github.com/CircleCI-Public/circleci-cli/version" ) @@ -489,5 +490,13 @@ func (f *fixture) Run(statusCode int, respBody string) (r *Runner, cleanup func( }) server := httptest.NewServer(mux) - return New(rest.New(server.URL, "api/v2", "fake-token")), server.Close + cfg := &settings.Config{ + Debug: false, + Token: "fake-token", + RestEndpoint: "api/v2", + Endpoint: "api/v2", + HTTPClient: http.DefaultClient, + } + + return New(rest.New(server.URL, cfg)), server.Close } diff --git a/cmd/runner/runner.go b/cmd/runner/runner.go index 436fc2792..fd647c6d7 100644 --- a/cmd/runner/runner.go +++ b/cmd/runner/runner.go @@ -27,7 +27,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com } else { host = config.Host } - opts.r = runner.New(rest.New(host, config.RestEndpoint, config.Token)) + opts.r = runner.New(rest.New(host, config)) }, } From 366cc5413053ee41072fbc30640b5fbe09870d14 Mon Sep 17 00:00:00 2001 From: Sagar Gupta Date: Thu, 28 Jul 2022 09:25:40 -0400 Subject: [PATCH 36/48] [SECENG-679] added `eval` policy subcommand to get raw opa evalution (#761) --- cmd/policy/policy.go | 164 ++++++++++++++---- cmd/policy/policy_test.go | 95 +++++++++- cmd/policy/testdata/policy-expected-usage.txt | 2 +- .../testdata/policy/create-expected-usage.txt | 6 +- .../testdata/policy/decide-expected-usage.txt | 2 +- .../testdata/policy/delete-expected-usage.txt | 4 +- .../testdata/policy/eval-expected-usage.txt | 11 ++ .../testdata/policy/get-expected-usage.txt | 4 +- .../testdata/policy/list-expected-usage.txt | 4 +- .../testdata/policy/logs-expected-usage.txt | 2 +- .../testdata/policy/update-expected-usage.txt | 6 +- 11 files changed, 255 insertions(+), 45 deletions(-) create mode 100644 cmd/policy/testdata/policy/eval-expected-usage.txt diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index 356e20f2f..e77592524 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -31,18 +31,14 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com } policyBaseURL := cmd.PersistentFlags().String("policy-base-url", "https://internal.circleci.com", "base url for policy api") - ownerID := cmd.PersistentFlags().String("owner-id", "", "the id of the policy's owner") - - if err := cmd.MarkPersistentFlagRequired("owner-id"); err != nil { - panic(err) - } list := func() *cobra.Command { + var ownerID string cmd := &cobra.Command{ Short: "List all policies", Use: "list", RunE: func(cmd *cobra.Command, _ []string) error { - policies, err := policy.NewClient(*policyBaseURL, config).ListPolicies(*ownerID) + policies, err := policy.NewClient(*policyBaseURL, config).ListPolicies(ownerID) if err != nil { return fmt.Errorf("failed to list policies: %v", err) } @@ -56,11 +52,17 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com Args: cobra.ExactArgs(0), Example: `policy list --owner-id 516425b2-e369-421b-838d-920e1f51b0f5`, } + + cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") + if err := cmd.MarkFlagRequired("owner-id"); err != nil { + panic(err) + } + return cmd }() create := func() *cobra.Command { - var policyPath string + var policyPath, ownerID string var creationRequest policy.CreationRequest cmd := &cobra.Command{ @@ -73,7 +75,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com } creationRequest.Content = string(policyData) - result, err := policy.NewClient(*policyBaseURL, config).CreatePolicy(*ownerID, creationRequest) + result, err := policy.NewClient(*policyBaseURL, config).CreatePolicy(ownerID, creationRequest) if err != nil { return fmt.Errorf("failed to create policy: %w", err) } @@ -92,7 +94,10 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com cmd.Flags().StringVar(&creationRequest.Context, "context", "config", "policy context") cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego policy file") - + cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") + if err := cmd.MarkFlagRequired("owner-id"); err != nil { + panic(err) + } if err := cmd.MarkFlagRequired("policy"); err != nil { panic(err) } @@ -101,11 +106,12 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com }() get := func() *cobra.Command { + var ownerID string cmd := &cobra.Command{ Short: "Get a policy", Use: "get ", RunE: func(cmd *cobra.Command, args []string) error { - p, err := policy.NewClient(*policyBaseURL, config).GetPolicy(*ownerID, args[0]) + p, err := policy.NewClient(*policyBaseURL, config).GetPolicy(ownerID, args[0]) if err != nil { return fmt.Errorf("failed to get policy: %v", err) } @@ -119,15 +125,21 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com Args: cobra.ExactArgs(1), Example: `policy get 60b7e1a5-c1d7-4422-b813-7a12d353d7c6 --owner-id 516425b2-e369-421b-838d-920e1f51b0f5`, } + cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") + if err := cmd.MarkFlagRequired("owner-id"); err != nil { + panic(err) + } + return cmd }() delete := func() *cobra.Command { + var ownerID string cmd := &cobra.Command{ Short: "Delete a policy", Use: "delete ", RunE: func(cmd *cobra.Command, args []string) error { - err := policy.NewClient(*policyBaseURL, config).DeletePolicy(*ownerID, args[0]) + err := policy.NewClient(*policyBaseURL, config).DeletePolicy(ownerID, args[0]) if err != nil { return fmt.Errorf("failed to delete policy: %v", err) } @@ -137,13 +149,16 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com Args: cobra.ExactArgs(1), Example: `policy delete 60b7e1a5-c1d7-4422-b813-7a12d353d7c6 --owner-id 516425b2-e369-421b-838d-920e1f51b0f5`, } + cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") + if err := cmd.MarkFlagRequired("owner-id"); err != nil { + panic(err) + } return cmd }() update := func() *cobra.Command { - var policyPath string - var context string + var policyPath, context, ownerID string var updateRequest policy.UpdateRequest cmd := &cobra.Command{ @@ -167,7 +182,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com updateRequest.Context = &context } - result, err := policy.NewClient(*policyBaseURL, config).UpdatePolicy(*ownerID, args[0], updateRequest) + result, err := policy.NewClient(*policyBaseURL, config).UpdatePolicy(ownerID, args[0], updateRequest) if err != nil { return fmt.Errorf("failed to update policy: %w", err) } @@ -186,12 +201,16 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com cmd.Flags().StringVar(&context, "context", "", "policy context (if set, must be config)") cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego file containing the updated policy") + cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") + if err := cmd.MarkFlagRequired("owner-id"); err != nil { + panic(err) + } return cmd }() logs := func() *cobra.Command { - var after, before, outputFile string + var after, before, outputFile, ownerID string var request policy.DecisionQueryRequest cmd := &cobra.Command{ @@ -243,7 +262,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com client := policy.NewClient(*policyBaseURL, config) for { - logsBatch, err := client.GetDecisionLogs(*ownerID, request) + logsBatch, err := client.GetDecisionLogs(ownerID, request) if err != nil { return fmt.Errorf("failed to get policy decision logs: %v", err) } @@ -272,6 +291,10 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com cmd.Flags().StringVar(&request.Branch, "branch", "", "filter decision logs based on branch name") cmd.Flags().StringVar(&request.ProjectID, "project-id", "", "filter decision logs based on project-id") cmd.Flags().StringVar(&outputFile, "out", "", "specify output file name ") + cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") + if err := cmd.MarkFlagRequired("owner-id"); err != nil { + panic(err) + } return cmd }() @@ -281,6 +304,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com inputPath string policyPath string metaFile string + ownerID string request policy.DecisionRequest ) @@ -288,7 +312,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com Short: "make a decision", Use: "decide", RunE: func(cmd *cobra.Command, _ []string) error { - if policyPath == "" && *ownerID == "" { + if policyPath == "" && ownerID == "" { return fmt.Errorf("--owner-id or --policy is required") } @@ -314,7 +338,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com } request.Input = string(input) request.Metadata = metadata - return policy.NewClient(*policyBaseURL, config).MakeDecision(*ownerID, request) + return policy.NewClient(*policyBaseURL, config).MakeDecision(ownerID, request) }() if err != nil { return fmt.Errorf("failed to make decision: %w", err) @@ -329,15 +353,66 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com Args: cobra.ExactArgs(0), } - // Redeclared flag to make optional - cmd.Flags().StringVar(ownerID, "owner-id", "", "the id of the policy's owner") - + cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") cmd.Flags().StringVar(&request.Context, "context", "config", "policy context for decision") cmd.Flags().StringVar(&inputPath, "input", "", "path to input file") cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego policy file or directory containing policy files") cmd.Flags().StringVar(&metaFile, "metafile", "", "decision metadata file") - _ = cmd.MarkFlagRequired("input") + if err := cmd.MarkFlagRequired("input"); err != nil { + panic(err) + } + + return cmd + }() + + eval := func() *cobra.Command { + var inputPath, policyPath, metaFile, query string + cmd := &cobra.Command{ + Short: "perform raw opa evaluation locally", + Use: "eval", + RunE: func(cmd *cobra.Command, _ []string) error { + input, err := os.ReadFile(inputPath) + if err != nil { + return fmt.Errorf("failed to read input file: %w", err) + } + + var metadata map[string]interface{} + if metaFile != "" { + raw, err := os.ReadFile(metaFile) + if err != nil { + return fmt.Errorf("failed to read meta file: %w", err) + } + if err := yaml.Unmarshal(raw, &metadata); err != nil { + return fmt.Errorf("failed to decode meta content: %w", err) + } + } + + decision, err := getPolicyEvaluationLocally(policyPath, input, metadata, query) + if err != nil { + return fmt.Errorf("failed to make decision: %w", err) + } + + if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(decision); err != nil { + return fmt.Errorf("failed to encode decision: %w", err) + } + + return nil + }, + Args: cobra.ExactArgs(0), + } + + cmd.Flags().StringVar(&inputPath, "input", "", "path to input file") + cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego policy file or directory containing policy files") + cmd.Flags().StringVar(&metaFile, "metafile", "", "decision metadata file") + cmd.Flags().StringVar(&query, "query", "data", "policy decision query") + + if err := cmd.MarkFlagRequired("input"); err != nil { + panic(err) + } + if err := cmd.MarkFlagRequired("policy"); err != nil { + panic(err) + } return cmd }() @@ -349,6 +424,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com cmd.AddCommand(update) cmd.AddCommand(logs) cmd.AddCommand(decide) + cmd.AddCommand(eval) return cmd } @@ -367,27 +443,51 @@ func getPolicyDecisionLocally(policyPath string, rawInput []byte, meta map[strin return nil, fmt.Errorf("invalid input: %w", err) } - pathInfo, err := os.Stat(policyPath) + p, err := loadPolicyFromPath(policyPath) if err != nil { - return nil, fmt.Errorf("failed to get path info: %w", err) + return nil, fmt.Errorf("failed to load policy files: %w", err) } - loadPolicy := func() func(string) (*cpa.Policy, error) { - if pathInfo.IsDir() { - return cpa.LoadPolicyDirectory - } - return cpa.LoadPolicyFile - }() + decision, err := p.Decide(context.Background(), input, cpa.Meta(meta)) + if err != nil { + return nil, fmt.Errorf("failed to make decision: %w", err) + } + + return decision, nil +} + +// getPolicyEvaluationLocally takes path of policy path/directory and input (eg build config) as string, and performs policy evaluation locally and returns raw opa evaluation response +func getPolicyEvaluationLocally(policyPath string, rawInput []byte, meta map[string]interface{}, query string) (interface{}, error) { + var input interface{} + if err := yaml.Unmarshal(rawInput, &input); err != nil { + return nil, fmt.Errorf("invalid input: %w", err) + } - policy, err := loadPolicy(policyPath) + p, err := loadPolicyFromPath(policyPath) if err != nil { return nil, fmt.Errorf("failed to load policy files: %w", err) } - decision, err := policy.Decide(context.Background(), input, cpa.Meta(meta)) + decision, err := p.Eval(context.Background(), query, input, cpa.Meta(meta)) if err != nil { return nil, fmt.Errorf("failed to make decision: %w", err) } return decision, nil } + +func loadPolicyFromPath(policyPath string) (*cpa.Policy, error) { + pathInfo, err := os.Stat(policyPath) + if err != nil { + return nil, fmt.Errorf("failed to get path info: %w", err) + } + + loadPolicy := func() func(string) (*cpa.Policy, error) { + if pathInfo.IsDir() { + return cpa.LoadPolicyDirectory + } + return cpa.LoadPolicyFile + }() + + return loadPolicy(policyPath) +} diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index 006656c33..dddaa141f 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -706,7 +706,7 @@ func TestMakeDecisionCommand(t *testing.T) { { Name: "fails for policy FILE/DIRECTORY not found", Args: []string{"decide", "--policy", "./testdata/no_such_file.rego", "--input", "./testdata/test.yml"}, - ExpectedErr: "failed to make decision: failed to get path info: ", + ExpectedErr: "failed to make decision: failed to load policy files: failed to get path info: ", }, { Name: "successfully performs decision for policy FILE provided locally", @@ -763,6 +763,99 @@ func TestMakeDecisionCommand(t *testing.T) { } } +func TestRawOPAEvaluationCommand(t *testing.T) { + testcases := []struct { + Name string + Args []string + ServerHandler http.HandlerFunc + ExpectedOutput string + ExpectedErr string + }{ + { + Name: "requires flags", + Args: []string{"eval"}, + ExpectedErr: `required flag(s) "input", "policy" not set`, + }, + { + Name: "fails if local-policy is not provided", + Args: []string{"eval", "--input", "./testdata/test.yml"}, + ExpectedErr: `required flag(s) "policy" not set`, + }, + { + Name: "fails if input is not provided", + Args: []string{"eval", "--policy", "./testdata/policy.rego"}, + ExpectedErr: `required flag(s) "input" not set`, + }, + { + Name: "fails for input file not found", + Args: []string{"eval", "--policy", "./testdata/policy.rego", "--input", "./testdata/no_such_file.yml"}, + ExpectedErr: "failed to read input file: open ./testdata/no_such_file.yml: ", + }, + { + Name: "fails for policy FILE/DIRECTORY not found", + Args: []string{"eval", "--policy", "./testdata/no_such_file.rego", "--input", "./testdata/test.yml"}, + ExpectedErr: "failed to make decision: failed to load policy files: failed to get path info: ", + }, + { + Name: "successfully performs raw opa evaluation for policy FILE provided locally, input and metadata", + Args: []string{ + "eval", "--metafile", "./testdata/meta.yml", "--policy", "./testdata/test0/meta-policy.rego", "--input", + "./testdata/test0/config.yml", + }, + ExpectedOutput: `{ + "meta": { + "branch": "main", + "project_id": "test-project-id" + }, + "org": { + "enable_rule": [ + "enabled" + ], + "policy_name": [ + "meta_policy_test" + ] + } +} +`, + }, + { + Name: "successfully performs raw opa evaluation for policy FILE provided locally, input, metadata and query", + Args: []string{ + "eval", "--metafile", "./testdata/meta.yml", "--policy", "./testdata/test0/meta-policy.rego", "--input", + "./testdata/test0/config.yml", "--query", "data.org.enable_rule", + }, + ExpectedOutput: `[ + "enabled" +] +`, + }, + } + + for _, tc := range testcases { + t.Run(tc.Name, func(t *testing.T) { + if tc.ServerHandler == nil { + tc.ServerHandler = func(w http.ResponseWriter, r *http.Request) {} + } + + svr := httptest.NewServer(tc.ServerHandler) + defer svr.Close() + + cmd, stdout, _ := makeCMD() + + cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) + + err := cmd.Execute() + if tc.ExpectedErr == "" { + assert.NilError(t, err) + } else { + assert.ErrorContains(t, err, tc.ExpectedErr) + return + } + assert.Equal(t, stdout.String(), tc.ExpectedOutput) + }) + } +} + func makeCMD() (*cobra.Command, *bytes.Buffer, *bytes.Buffer) { config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} cmd := NewCommand(config, nil) diff --git a/cmd/policy/testdata/policy-expected-usage.txt b/cmd/policy/testdata/policy-expected-usage.txt index 8b3339623..6218ba1c9 100644 --- a/cmd/policy/testdata/policy-expected-usage.txt +++ b/cmd/policy/testdata/policy-expected-usage.txt @@ -5,13 +5,13 @@ Available Commands: create create policy decide make a decision delete Delete a policy + eval perform raw opa evaluation locally get Get a policy list List all policies logs Get policy (decision) logs update Update a policy Flags: - --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") Use "policy [command] --help" for more information about a command. diff --git a/cmd/policy/testdata/policy/create-expected-usage.txt b/cmd/policy/testdata/policy/create-expected-usage.txt index d483057e6..120646a05 100644 --- a/cmd/policy/testdata/policy/create-expected-usage.txt +++ b/cmd/policy/testdata/policy/create-expected-usage.txt @@ -5,9 +5,9 @@ Examples: policy create --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --name policy_name --policy ./policy.rego Flags: - --context string policy context (default "config") - --policy string path to rego policy file + --context string policy context (default "config") + --owner-id string the id of the policy's owner + --policy string path to rego policy file Global Flags: - --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/decide-expected-usage.txt b/cmd/policy/testdata/policy/decide-expected-usage.txt index ede4b417c..49728afdc 100644 --- a/cmd/policy/testdata/policy/decide-expected-usage.txt +++ b/cmd/policy/testdata/policy/decide-expected-usage.txt @@ -5,8 +5,8 @@ Flags: --context string policy context for decision (default "config") --input string path to input file --metafile string decision metadata file + --owner-id string the id of the policy's owner --policy string path to rego policy file or directory containing policy files Global Flags: - --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/delete-expected-usage.txt b/cmd/policy/testdata/policy/delete-expected-usage.txt index fa01cb4a1..63845009e 100644 --- a/cmd/policy/testdata/policy/delete-expected-usage.txt +++ b/cmd/policy/testdata/policy/delete-expected-usage.txt @@ -4,6 +4,8 @@ Usage: Examples: policy delete 60b7e1a5-c1d7-4422-b813-7a12d353d7c6 --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 +Flags: + --owner-id string the id of the policy's owner + Global Flags: - --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/eval-expected-usage.txt b/cmd/policy/testdata/policy/eval-expected-usage.txt new file mode 100644 index 000000000..afdf989dd --- /dev/null +++ b/cmd/policy/testdata/policy/eval-expected-usage.txt @@ -0,0 +1,11 @@ +Usage: + policy eval [flags] + +Flags: + --input string path to input file + --metafile string decision metadata file + --policy string path to rego policy file or directory containing policy files + --query string policy decision query (default "data") + +Global Flags: + --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/get-expected-usage.txt b/cmd/policy/testdata/policy/get-expected-usage.txt index 4b5f76a43..e30225f48 100644 --- a/cmd/policy/testdata/policy/get-expected-usage.txt +++ b/cmd/policy/testdata/policy/get-expected-usage.txt @@ -4,6 +4,8 @@ Usage: Examples: policy get 60b7e1a5-c1d7-4422-b813-7a12d353d7c6 --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 +Flags: + --owner-id string the id of the policy's owner + Global Flags: - --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/list-expected-usage.txt b/cmd/policy/testdata/policy/list-expected-usage.txt index 1406bfab0..803a3040d 100644 --- a/cmd/policy/testdata/policy/list-expected-usage.txt +++ b/cmd/policy/testdata/policy/list-expected-usage.txt @@ -4,6 +4,8 @@ Usage: Examples: policy list --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 +Flags: + --owner-id string the id of the policy's owner + Global Flags: - --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/logs-expected-usage.txt b/cmd/policy/testdata/policy/logs-expected-usage.txt index 9e91bccdd..0c8f086b3 100644 --- a/cmd/policy/testdata/policy/logs-expected-usage.txt +++ b/cmd/policy/testdata/policy/logs-expected-usage.txt @@ -9,9 +9,9 @@ Flags: --before string filter decision logs triggered BEFORE this datetime --branch string filter decision logs based on branch name --out string specify output file name + --owner-id string the id of the policy's owner --project-id string filter decision logs based on project-id --status string filter decision logs based on their status Global Flags: - --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/update-expected-usage.txt b/cmd/policy/testdata/policy/update-expected-usage.txt index 24c70058d..799799873 100644 --- a/cmd/policy/testdata/policy/update-expected-usage.txt +++ b/cmd/policy/testdata/policy/update-expected-usage.txt @@ -5,9 +5,9 @@ Examples: policy update e9e300d1-5bab-4704-b610-addbd6e03b0b --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --name policy_name --policy ./policy.rego Flags: - --context string policy context (if set, must be config) - --policy string path to rego file containing the updated policy + --context string policy context (if set, must be config) + --owner-id string the id of the policy's owner + --policy string path to rego file containing the updated policy Global Flags: - --owner-id string the id of the policy's owner --policy-base-url string base url for policy api (default "https://internal.circleci.com") From e042377324976cf0cf63b2b930369ac5b1ea634b Mon Sep 17 00:00:00 2001 From: Sagar Gupta Date: Thu, 11 Aug 2022 14:18:46 -0400 Subject: [PATCH 37/48] [SECENG-639] Policy bundle commands (#765) --- api/policy/policy.go | 151 +---- api/policy/policy_test.go | 441 +++------------ cmd/policy/policy.go | 217 ++----- cmd/policy/policy_test.go | 528 +++++------------- cmd/policy/testdata/policy-expected-usage.txt | 7 +- .../testdata/policy/fetch-expected-usage.txt | 12 + .../testdata/policy/logs-expected-usage.txt | 1 + .../testdata/policy/push-expected-usage.txt | 12 + cmd/policy/testdata/test.rego | 1 - .../should_be_ignored.txt | 1 + cmd/policy/testdata/test0/policy.rego | 3 +- .../meta-policy-subdir}/meta-policy.rego | 1 - .../meta-policy-subdir/should_be_ignored.txt | 1 + cmd/policy/testdata/{ => test1}/meta.yml | 0 cmd/policy/testdata/{ => test1}/test.yml | 0 go.mod | 14 +- go.sum | 33 +- 17 files changed, 337 insertions(+), 1086 deletions(-) create mode 100644 cmd/policy/testdata/policy/fetch-expected-usage.txt create mode 100644 cmd/policy/testdata/policy/push-expected-usage.txt delete mode 100644 cmd/policy/testdata/test.rego create mode 100644 cmd/policy/testdata/test0/no-valid-policy-files/should_be_ignored.txt rename cmd/policy/testdata/test0/{ => subdir/meta-policy-subdir}/meta-policy.rego (99%) create mode 100644 cmd/policy/testdata/test0/subdir/meta-policy-subdir/should_be_ignored.txt rename cmd/policy/testdata/{ => test1}/meta.yml (100%) rename cmd/policy/testdata/{ => test1}/test.yml (100%) diff --git a/api/policy/policy.go b/api/policy/policy.go index 0d505dcf2..339c2bdf8 100644 --- a/api/policy/policy.go +++ b/api/policy/policy.go @@ -30,129 +30,49 @@ type httpError struct { Context map[string]interface{} `json:"context,omitempty"` } -// ListPolicies calls the view policy-service list policy API -func (c Client) ListPolicies(ownerID string) (interface{}, error) { - req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/owner/%s/policy", c.serverUrl, ownerID), nil) - if err != nil { - return nil, fmt.Errorf("failed to construct request: %v", err) - } - - resp, err := c.client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - var payload httpError - if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { - return nil, fmt.Errorf("unexpected status-code: %d", resp.StatusCode) - } - return nil, fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, payload.Error) - } - - var body interface{} - if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { - return nil, fmt.Errorf("failed to decode response body: %v", err) - } - - return body, nil -} - // Creation types taken from policy-service: internal/policy/api.go -// CreationRequest represents the json payload to create a Policy in the Policy-Service -type CreationRequest struct { - Context string `json:"context"` - Content string `json:"content"` +// CreatePolicyBundleRequest defines the fields for the Create-Policy-Bundle endpoint as defined in Policy Service +type CreatePolicyBundleRequest struct { + Policies map[string]string `json:"policies"` } -// CreatePolicy call the Create Policy API in the Policy-Service. It creates a policy for the specified owner and returns the created -// policy response as an interface{}. -func (c Client) CreatePolicy(ownerID string, policy CreationRequest) (interface{}, error) { +// CreatePolicyBundle calls the Create Policy Bundle API in the Policy-Service. +// It creates a policy bundle for the specified owner+context and returns the http status code as response +func (c Client) CreatePolicyBundle(ownerID string, context string, policy CreatePolicyBundleRequest) error { data, err := json.Marshal(policy) if err != nil { - return nil, fmt.Errorf("failed to encode policy payload: %w", err) + return fmt.Errorf("failed to encode policy payload: %w", err) } - req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/owner/%s/policy", c.serverUrl, ownerID), bytes.NewReader(data)) + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/owner/%s/context/%s/policy-bundle", c.serverUrl, ownerID, context), bytes.NewReader(data)) if err != nil { - return nil, fmt.Errorf("failed to construct request: %v", err) + return fmt.Errorf("failed to construct request: %v", err) } req.Header.Set("Content-Length", strconv.Itoa(len(data))) resp, err := c.client.Do(req) if err != nil { - return nil, fmt.Errorf("failed to get response from policy-service: %w", err) + return fmt.Errorf("failed to get response from policy-service: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { var response httpError if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { - return nil, fmt.Errorf("unexpected status-code: %d", resp.StatusCode) - } - return nil, fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, response.Error) - } - - var response interface{} - if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { - return nil, fmt.Errorf("failed to parse response: %w", err) - } - - return &response, nil -} - -type UpdateRequest struct { - Context *string `json:"context,omitempty"` - Content *string `json:"content,omitempty"` -} - -// UpdatePolicy calls the UPDATE policy API in the policy-service. It updates a policy in the policy-service matching the given owner-id and policy-id. -func (c Client) UpdatePolicy(ownerID string, policyID string, policy UpdateRequest) (interface{}, error) { - data, err := json.Marshal(policy) - if err != nil { - return nil, fmt.Errorf("failed to encode policy payload: %w", err) - } - - req, err := http.NewRequest( - "PATCH", - fmt.Sprintf("%s/api/v1/owner/%s/policy/%s", c.serverUrl, ownerID, policyID), - bytes.NewReader(data), - ) - if err != nil { - return nil, fmt.Errorf("failed to construct request: %v", err) - } - - req.Header.Set("Content-Length", strconv.Itoa(len(data))) - - resp, err := c.client.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get response from policy-service: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - var response httpError - if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { - return nil, fmt.Errorf("unexpected status-code: %d", resp.StatusCode) + return fmt.Errorf("unexpected status-code: %d", resp.StatusCode) } - return nil, fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, response.Error) - } - - var response interface{} - if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { - return nil, fmt.Errorf("failed to parse response: %w", err) + return fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, response.Error) } - - return &response, nil + return nil } -// GetPolicy calls the GET policy API in the policy-service.It fetches the policy from policy-service matching the given owner-id and policy-id. -// It returns an error if the call fails or the policy could not be found. -func (c Client) GetPolicy(ownerID string, policyID string) (interface{}, error) { - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/owner/%s/policy/%s", c.serverUrl, ownerID, policyID), nil) +// FetchPolicyBundle calls the GET policy-bundle API in the policy-service +// If policyName is empty, the full policy bundle would be fetched for given ownerID+context +// If a policyName is provided, only that matching policy would be fetched for given ownerID+context+policyName +func (c Client) FetchPolicyBundle(ownerID, context, policyName string) (interface{}, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/owner/%s/context/%s/policy-bundle/%s", c.serverUrl, ownerID, context, policyName), nil) if err != nil { return nil, fmt.Errorf("failed to construct request: %v", err) } @@ -179,32 +99,6 @@ func (c Client) GetPolicy(ownerID string, policyID string) (interface{}, error) return body, nil } -// DeletePolicy calls the DELETE Policy API in the policy-service. -// It attempts to delete the policy matching the given policy-id and belonging to the given ownerID. -// It returns an error if the call fails or the policy could not be deleted. -func (c Client) DeletePolicy(ownerID string, policyID string) error { - req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/api/v1/owner/%s/policy/%s", c.serverUrl, ownerID, policyID), nil) - if err != nil { - return fmt.Errorf("failed to construct request: %v", err) - } - - resp, err := c.client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusNoContent { - var payload httpError - if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { - return fmt.Errorf("unexpected status-code: %d", resp.StatusCode) - } - return fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, payload.Error) - } - - return nil -} - type DecisionQueryRequest struct { Status string After *time.Time @@ -216,8 +110,8 @@ type DecisionQueryRequest struct { // GetDecisionLogs calls the GET decision query API of policy-service. The endpoint accepts multiple filter values as // path query parameters (start-time, end-time, branch-name, project-id and offset). -func (c Client) GetDecisionLogs(ownerID string, request DecisionQueryRequest) ([]interface{}, error) { - req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/owner/%s/decision", c.serverUrl, ownerID), nil) +func (c Client) GetDecisionLogs(ownerID string, context string, request DecisionQueryRequest) ([]interface{}, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/owner/%s/context/%s/decision", c.serverUrl, ownerID, context), nil) if err != nil { return nil, fmt.Errorf("failed to construct request: %v", err) } @@ -270,18 +164,17 @@ func (c Client) GetDecisionLogs(ownerID string, request DecisionQueryRequest) ([ // The context determines which policies to apply. type DecisionRequest struct { Input string `json:"input"` - Context string `json:"context"` Metadata map[string]interface{} `json:"metadata,omitempty"` } // MakeDecision sends a requests to Policy-Service public decision endpoint and returns the decision response -func (c Client) MakeDecision(ownerID string, req DecisionRequest) (interface{}, error) { +func (c Client) MakeDecision(ownerID string, context string, req DecisionRequest) (interface{}, error) { payload, err := json.Marshal(req) if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } - endpoint := fmt.Sprintf("%s/api/v1/owner/%s/decision", c.serverUrl, ownerID) + endpoint := fmt.Sprintf("%s/api/v1/owner/%s/context/%s/decision", c.serverUrl, ownerID, context) request, err := http.NewRequest("POST", endpoint, bytes.NewReader(payload)) if err != nil { diff --git a/api/policy/policy_test.go b/api/policy/policy_test.go index 76f637e38..63ce5e29d 100644 --- a/api/policy/policy_test.go +++ b/api/policy/policy_test.go @@ -15,7 +15,7 @@ import ( "github.com/CircleCI-Public/circleci-cli/version" ) -func TestClientListPolicies(t *testing.T) { +func TestClientFetchPolicyBundle(t *testing.T) { t.Run("expected request", func(t *testing.T) { svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Header.Get("circle-token"), "testtoken") @@ -25,10 +25,10 @@ func TestClientListPolicies(t *testing.T) { assert.Equal(t, r.Header.Get("circle-token"), "testtoken") assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/policy") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/context/config/policy-bundle/my_policy") w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte("[]")) + _, err := w.Write([]byte("{}")) assert.NilError(t, err) })) defer svr.Close() @@ -36,11 +36,11 @@ func TestClientListPolicies(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - _, err := client.ListPolicies("ownerId") + _, err := client.FetchPolicyBundle("ownerId", "config", "my_policy") assert.NilError(t, err) }) - t.Run("List Policies - Forbidden", func(t *testing.T) { + t.Run("Fetch Policy Bundle - Forbidden", func(t *testing.T) { expectedResponse := `{"error": "Forbidden"}` svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusForbidden) @@ -52,12 +52,12 @@ func TestClientListPolicies(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - policies, err := client.ListPolicies("ownerId") + policies, err := client.FetchPolicyBundle("ownerId", "config", "") assert.Equal(t, policies, nil) assert.Error(t, err, "unexpected status-code: 403 - Forbidden") }) - t.Run("List Policies - Bad error json", func(t *testing.T) { + t.Run("Fetch Policy Bundle - Bad error json", func(t *testing.T) { expectedResponse := `{"this is bad json": }` svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusForbidden) @@ -69,13 +69,13 @@ func TestClientListPolicies(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - policies, err := client.ListPolicies("ownerId") + policies, err := client.FetchPolicyBundle("ownerId", "config", "") assert.Equal(t, policies, nil) assert.Error(t, err, "unexpected status-code: 403") }) - t.Run("List Policies - no policies", func(t *testing.T) { - expectedResponse := "[]" + t.Run("Fetch Policy Bundle - no policies", func(t *testing.T) { + expectedResponse := "{}" var expectedResponseValue interface{} assert.NilError(t, json.Unmarshal([]byte(expectedResponse), &expectedResponseValue)) @@ -89,12 +89,12 @@ func TestClientListPolicies(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - policies, err := client.ListPolicies("ownerId") + policies, err := client.FetchPolicyBundle("ownerId", "config", "") assert.DeepEqual(t, policies, expectedResponseValue) assert.NilError(t, err) }) - t.Run("List Policies - some policies", func(t *testing.T) { + t.Run("Fetch Policy Bundle - some policies", func(t *testing.T) { expectedResponse := `[ { "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", @@ -126,124 +126,16 @@ func TestClientListPolicies(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - policies, err := client.ListPolicies("ownerId") + policies, err := client.FetchPolicyBundle("ownerId", "config", "") assert.DeepEqual(t, policies, expectedResponseValue) assert.NilError(t, err) }) } -func TestClientGetPolicy(t *testing.T) { - t.Run("expected request", func(t *testing.T) { - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Header.Get("circle-token"), "testtoken") - assert.Equal(t, r.Header.Get("accept"), "application/json") - assert.Equal(t, r.Header.Get("content-type"), "application/json") - assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) - assert.Equal(t, r.Header.Get("circle-token"), "testtoken") - - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/policy/policyID") - - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte("[]")) - assert.NilError(t, err) - })) - defer svr.Close() - - config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} - client := NewClient(svr.URL, config) - - _, err := client.GetPolicy("ownerId", "policyID") - assert.NilError(t, err) - }) - - t.Run("Get Policy - Bad Request", func(t *testing.T) { - expectedResponse := `{"error": "PolicyID: must be a valid UUID."}` - - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - _, err := w.Write([]byte(expectedResponse)) - assert.NilError(t, err) - })) - defer svr.Close() - - config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} - client := NewClient(svr.URL, config) - - policy, err := client.GetPolicy("ownerId", "policyID") - assert.Equal(t, policy, nil) - assert.Error(t, err, "unexpected status-code: 400 - PolicyID: must be a valid UUID.") - }) - - t.Run("Get Policy - Forbidden", func(t *testing.T) { - expectedResponse := `{"error": "Forbidden"}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusForbidden) - _, err := w.Write([]byte(expectedResponse)) - assert.NilError(t, err) - })) - defer svr.Close() - - config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} - client := NewClient(svr.URL, config) - - policy, err := client.GetPolicy("ownerId", "policyID") - assert.Equal(t, policy, nil) - assert.Error(t, err, "unexpected status-code: 403 - Forbidden") - }) - - t.Run("Get Policy - Not Found", func(t *testing.T) { - expectedResponse := `{"error": "policy not found"}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, err := w.Write([]byte(expectedResponse)) - assert.NilError(t, err) - })) - defer svr.Close() - - config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} - client := NewClient(svr.URL, config) - - policy, err := client.GetPolicy("ownerId", "a917a0ab-ceb6-482d-9a4e-f2f6b8bdfdca") - assert.Equal(t, policy, nil) - assert.Error(t, err, "unexpected status-code: 404 - policy not found") - }) - - t.Run("Get Policy - successfully gets a policy", func(t *testing.T) { - expectedResponse := `{ - "document_version": 1, - "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", - "name": "policy_1", - "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f", - "context": "config", - "content": "package test", - "created_at": "2022-05-31T14:15:10.86097Z", - "modified_at": null - }` - - var expectedResponseValue interface{} - assert.NilError(t, json.Unmarshal([]byte(expectedResponse), &expectedResponseValue)) - - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _, err := w.Write([]byte(expectedResponse)) - assert.NilError(t, err) - })) - defer svr.Close() - - config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} - client := NewClient(svr.URL, config) - - policy, err := client.GetPolicy("462d67f8-b232-4da4-a7de-0c86dd667d3f", "60b7e1a5-c1d7-4422-b813-7a12d353d7c6") - assert.DeepEqual(t, policy, expectedResponseValue) - assert.NilError(t, err) - }) -} - func TestClientCreatePolicy(t *testing.T) { t.Run("expected request", func(t *testing.T) { - req := CreationRequest{ - Context: "config", - Content: "test-content", + req := CreatePolicyBundleRequest{ + Policies: map[string]string{"policy_a": "package org"}, } svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -254,161 +146,20 @@ func TestClientCreatePolicy(t *testing.T) { assert.Equal(t, r.Header.Get("circle-token"), "testtoken") assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/policy") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/context/config/policy-bundle") - var actual CreationRequest + var actual CreatePolicyBundleRequest assert.NilError(t, json.NewDecoder(r.Body).Decode(&actual)) assert.DeepEqual(t, actual, req) w.WriteHeader(http.StatusCreated) - _, err := w.Write([]byte("{}")) - assert.NilError(t, err) - })) - defer svr.Close() - - config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} - client := NewClient(svr.URL, config) - - _, err := client.CreatePolicy("ownerId", req) - assert.NilError(t, err) - }) - - t.Run("unexpected status code", func(t *testing.T) { - expectedResponse := `{"error": "Forbidden"}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusForbidden) - _, err := w.Write([]byte(expectedResponse)) - assert.NilError(t, err) - })) - defer svr.Close() - - config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} - client := NewClient(svr.URL, config) - - _, err := client.CreatePolicy("ownerId", CreationRequest{}) - assert.Error(t, err, "unexpected status-code: 403 - Forbidden") - }) -} - -func TestClientDeletePolicy(t *testing.T) { - t.Run("expected request", func(t *testing.T) { - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Header.Get("circle-token"), "testtoken") - assert.Equal(t, r.Header.Get("accept"), "application/json") - assert.Equal(t, r.Header.Get("content-type"), "application/json") - assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) - assert.Equal(t, r.Header.Get("circle-token"), "testtoken") - - assert.Equal(t, r.Method, "DELETE") - assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/policy/policyID") - - w.WriteHeader(http.StatusNoContent) - })) - defer svr.Close() - - config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} - client := NewClient(svr.URL, config) - - err := client.DeletePolicy("ownerId", "policyID") - assert.NilError(t, err) - }) - - t.Run("Delete Policy - Bad Request", func(t *testing.T) { - expectedResponse := `{"error": "PolicyID: must be a valid UUID."}` - - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - _, err := w.Write([]byte(expectedResponse)) - assert.NilError(t, err) - })) - defer svr.Close() - - config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} - client := NewClient(svr.URL, config) - - err := client.DeletePolicy("ownerId", "policyID") - assert.Error(t, err, "unexpected status-code: 400 - PolicyID: must be a valid UUID.") - }) - - t.Run("Delete Policy - Forbidden", func(t *testing.T) { - expectedResponse := `{"error": "Forbidden"}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusForbidden) - _, err := w.Write([]byte(expectedResponse)) - assert.NilError(t, err) - })) - defer svr.Close() - - config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} - client := NewClient(svr.URL, config) - - err := client.DeletePolicy("ownerId", "policyID") - assert.Error(t, err, "unexpected status-code: 403 - Forbidden") - }) - - t.Run("Delete Policy - Not Found", func(t *testing.T) { - expectedResponse := `{"error": "policy not found"}` - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, err := w.Write([]byte(expectedResponse)) - assert.NilError(t, err) - })) - defer svr.Close() - - config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} - client := NewClient(svr.URL, config) - - err := client.DeletePolicy("ownerId", "a917a0ab-ceb6-482d-9a4e-f2f6b8bdfdca") - assert.Error(t, err, "unexpected status-code: 404 - policy not found") - }) - - t.Run("Delete Policy - successfully deletes a policy", func(t *testing.T) { - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNoContent) - })) - defer svr.Close() - - config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} - client := NewClient(svr.URL, config) - - err := client.DeletePolicy("462d67f8-b232-4da4-a7de-0c86dd667d3f", "60b7e1a5-c1d7-4422-b813-7a12d353d7c6") - assert.NilError(t, err) - }) -} - -func TestClientUpdatePolicy(t *testing.T) { - t.Run("expected request", func(t *testing.T) { - context := "config" - content := "test-content" - req := UpdateRequest{ - Context: &context, - Content: &content, - } - - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Header.Get("circle-token"), "testtoken") - assert.Equal(t, r.Header.Get("accept"), "application/json") - assert.Equal(t, r.Header.Get("content-type"), "application/json") - assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) - assert.Equal(t, r.Header.Get("circle-token"), "testtoken") - - assert.Equal(t, r.Method, "PATCH") - assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerID/policy/policyID") - - var actual UpdateRequest - assert.NilError(t, json.NewDecoder(r.Body).Decode(&actual)) - assert.DeepEqual(t, actual, req) - - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte("{}")) - assert.NilError(t, err) })) defer svr.Close() config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} client := NewClient(svr.URL, config) - _, err := client.UpdatePolicy("ownerID", "policyID", req) + err := client.CreatePolicyBundle("ownerId", "config", req) assert.NilError(t, err) }) @@ -424,70 +175,9 @@ func TestClientUpdatePolicy(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - _, err := client.UpdatePolicy("ownerId", "policyId", UpdateRequest{}) + err := client.CreatePolicyBundle("ownerId", "config", CreatePolicyBundleRequest{}) assert.Error(t, err, "unexpected status-code: 403 - Forbidden") }) - - t.Run("no changes", func(t *testing.T) { - req := UpdateRequest{} - - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Header.Get("circle-token"), "testtoken") - assert.Equal(t, r.Header.Get("accept"), "application/json") - assert.Equal(t, r.Header.Get("content-type"), "application/json") - assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) - assert.Equal(t, r.Header.Get("circle-token"), "testtoken") - - assert.Equal(t, r.Method, "PATCH") - assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerID/policy/policyID") - - var actual UpdateRequest - assert.NilError(t, json.NewDecoder(r.Body).Decode(&actual)) - assert.DeepEqual(t, actual, req) - - expectedResponse := `{"error": "at least one of name, context, or content, cannot be blank"}` - w.WriteHeader(http.StatusBadRequest) - _, err := w.Write([]byte(expectedResponse)) - assert.NilError(t, err) - })) - defer svr.Close() - - config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} - client := NewClient(svr.URL, config) - - _, err := client.UpdatePolicy("ownerID", "policyID", req) - assert.Error(t, err, "unexpected status-code: 400 - at least one of name, context, or content, cannot be blank") - }) - - t.Run("one change", func(t *testing.T) { - req := UpdateRequest{} - - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Header.Get("circle-token"), "testtoken") - assert.Equal(t, r.Header.Get("accept"), "application/json") - assert.Equal(t, r.Header.Get("content-type"), "application/json") - assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) - assert.Equal(t, r.Header.Get("circle-token"), "testtoken") - - assert.Equal(t, r.Method, "PATCH") - assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerID/policy/policyID") - - var actual UpdateRequest - assert.NilError(t, json.NewDecoder(r.Body).Decode(&actual)) - assert.DeepEqual(t, actual, req) - - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte("{}")) - assert.NilError(t, err) - })) - defer svr.Close() - - config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} - client := NewClient(svr.URL, config) - - _, err := client.UpdatePolicy("ownerID", "policyID", req) - assert.NilError(t, err) - }) } func TestClientGetDecisionLogs(t *testing.T) { @@ -500,8 +190,8 @@ func TestClientGetDecisionLogs(t *testing.T) { assert.Equal(t, r.Header.Get("circle-token"), "testtoken") assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/decision") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerId/decision") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/context/config/decision") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerId/context/config/decision") w.WriteHeader(http.StatusOK) _, err := w.Write([]byte("[]")) @@ -512,7 +202,7 @@ func TestClientGetDecisionLogs(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - _, err := client.GetDecisionLogs("ownerId", DecisionQueryRequest{}) + _, err := client.GetDecisionLogs("ownerId", "config", DecisionQueryRequest{}) assert.NilError(t, err) }) @@ -525,7 +215,7 @@ func TestClientGetDecisionLogs(t *testing.T) { assert.Equal(t, r.Header.Get("circle-token"), "testtoken") assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/decision") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/context/config/decision") assert.Equal(t, r.URL.RawQuery, "project_id=projectIDValue") w.WriteHeader(http.StatusOK) @@ -537,7 +227,7 @@ func TestClientGetDecisionLogs(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - _, err := client.GetDecisionLogs("ownerId", DecisionQueryRequest{ProjectID: "projectIDValue"}) + _, err := client.GetDecisionLogs("ownerId", "config", DecisionQueryRequest{ProjectID: "projectIDValue"}) assert.NilError(t, err) }) @@ -552,7 +242,7 @@ func TestClientGetDecisionLogs(t *testing.T) { assert.Equal(t, r.Header.Get("circle-token"), "testtoken") assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/decision") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/context/config/decision") assert.Equal( t, r.URL.RawQuery, @@ -571,7 +261,7 @@ func TestClientGetDecisionLogs(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - _, err := client.GetDecisionLogs("ownerId", DecisionQueryRequest{ + _, err := client.GetDecisionLogs("ownerId", "config", DecisionQueryRequest{ Status: "PASS", After: &testTime, Before: &testTime, @@ -595,7 +285,7 @@ func TestClientGetDecisionLogs(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - logs, err := client.GetDecisionLogs("ownerId", DecisionQueryRequest{}) + logs, err := client.GetDecisionLogs("ownerId", "config", DecisionQueryRequest{}) assert.Error(t, err, "unexpected status-code: 400 - Offset: must be an integer number.") assert.Equal(t, len(logs), 0) }) @@ -612,7 +302,7 @@ func TestClientGetDecisionLogs(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - logs, err := client.GetDecisionLogs("ownerId", DecisionQueryRequest{}) + logs, err := client.GetDecisionLogs("ownerId", "config", DecisionQueryRequest{}) assert.Error(t, err, "unexpected status-code: 403 - Forbidden") assert.Equal(t, len(logs), 0) }) @@ -632,41 +322,44 @@ func TestClientGetDecisionLogs(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - logs, err := client.GetDecisionLogs("ownerId", DecisionQueryRequest{}) + logs, err := client.GetDecisionLogs("ownerId", "config", DecisionQueryRequest{}) assert.DeepEqual(t, logs, expectedResponseValue) assert.NilError(t, err) }) t.Run("Get Decision Logs - some logs", func(t *testing.T) { expectedResponse := `[ - { - "metadata": {}, - "created_at": "2022-06-08T16:56:22.179906Z", - "policies": [ - { - "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", - "version": 2 - } - ], - "decision": { - "status": "PASS" - } + { + "created_at": "2022-08-11T09:20:40.674594-04:00", + "decision": { + "enabled_rules": [ + "branch_is_main" + ], + "status": "PASS" + }, + "metadata": {}, + "policies": [ + "8c69adc542bcfd6e65f5d5a2b6a4e3764480db2253cd075d0954e64a1f827a9c695c916d5a49302991df781447b3951410824dce8a8282d11ed56302272cf6fb", + "3124131001ec20b4b524260ababa6411190a1bc9c5ac3219ccc2d21109fc5faf4bb9f7bbe38f3f798d9c232d68564390e0ca560877711f3f2ff7f89e10eef685" + ], + "time_taken_ms": 4 + }, + { + "created_at": "2022-08-11T09:21:31.66168-04:00", + "decision": { + "enabled_rules": [ + "branch_is_main" + ], + "status": "PASS" }, - { - "metadata": {}, - "created_at": "2022-06-08T17:06:14.591951Z", - "policies": [ - { - "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", - "version": 2 - } - ], - "decision": { - "status": "PASS" - } - } + "metadata": {}, + "policies": [ + "8c69adc542bcfd6e65f5d5a2b6a4e3764480db2253cd075d0954e64a1f827a9c695c916d5a49302991df781447b3951410824dce8a8282d11ed56302272cf6fb", + "3124131001ec20b4b524260ababa6411190a1bc9c5ac3219ccc2d21109fc5faf4bb9f7bbe38f3f798d9c232d68564390e0ca560877711f3f2ff7f89e10eef685" + ], + "time_taken_ms": 7 + } ]` - var expectedResponseValue []interface{} assert.NilError(t, json.Unmarshal([]byte(expectedResponse), &expectedResponseValue)) @@ -679,7 +372,7 @@ func TestClientGetDecisionLogs(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - logs, err := client.GetDecisionLogs("ownerId", DecisionQueryRequest{}) + logs, err := client.GetDecisionLogs("ownerId", "config", DecisionQueryRequest{}) assert.DeepEqual(t, logs, expectedResponseValue) assert.NilError(t, err) }) @@ -695,14 +388,13 @@ func TestMakeDecision(t *testing.T) { ExpectedDecision interface{} }{ { - Name: "sends expexted request", + Name: "sends expected request", OwnerID: "test-owner", Request: DecisionRequest{ - Input: "test-input", - Context: "test-context", + Input: "test-input", }, Handler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/decision") + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/context/config/decision") assert.Equal(t, r.Method, "POST") assert.Equal(t, r.Header.Get("Circle-Token"), "test-token") @@ -710,8 +402,7 @@ func TestMakeDecision(t *testing.T) { assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) assert.DeepEqual(t, payload, map[string]interface{}{ - "context": "test-context", - "input": "test-input", + "input": "test-input", }) _ = json.NewEncoder(w).Encode(map[string]string{"status": "PASS"}) @@ -719,7 +410,7 @@ func TestMakeDecision(t *testing.T) { ExpectedDecision: map[string]interface{}{"status": "PASS"}, }, { - Name: "unexpected statuscode", + Name: "unexpected status code", OwnerID: "test-owner", Request: DecisionRequest{}, Handler: func(w http.ResponseWriter, _ *http.Request) { @@ -730,7 +421,7 @@ func TestMakeDecision(t *testing.T) { }, { - Name: "unexpected statuscode no body", + Name: "unexpected status code no body", OwnerID: "test-owner", Request: DecisionRequest{}, Handler: func(w http.ResponseWriter, _ *http.Request) { @@ -756,7 +447,7 @@ func TestMakeDecision(t *testing.T) { client := NewClient(svr.URL, &settings.Config{Token: "test-token", HTTPClient: http.DefaultClient}) - decision, err := client.MakeDecision(tc.OwnerID, tc.Request) + decision, err := client.MakeDecision(tc.OwnerID, "config", tc.Request) if tc.ExpectedError == nil { assert.NilError(t, err) } else { diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index e77592524..ada9d62b4 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -5,16 +5,17 @@ import ( "encoding/json" "fmt" "io" + "io/fs" "os" + "path/filepath" "time" "github.com/CircleCI-Public/circle-policy-agent/cpa" + "github.com/araddon/dateparse" "github.com/briandowns/spinner" "github.com/spf13/cobra" "gopkg.in/yaml.v3" - "github.com/araddon/dateparse" - "github.com/CircleCI-Public/circleci-cli/api/policy" "github.com/CircleCI-Public/circleci-cli/cmd/validator" @@ -32,123 +33,47 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com policyBaseURL := cmd.PersistentFlags().String("policy-base-url", "https://internal.circleci.com", "base url for policy api") - list := func() *cobra.Command { - var ownerID string - cmd := &cobra.Command{ - Short: "List all policies", - Use: "list", - RunE: func(cmd *cobra.Command, _ []string) error { - policies, err := policy.NewClient(*policyBaseURL, config).ListPolicies(ownerID) - if err != nil { - return fmt.Errorf("failed to list policies: %v", err) - } - - if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(policies); err != nil { - return fmt.Errorf("failed to output policies in json format: %v", err) - } - - return nil - }, - Args: cobra.ExactArgs(0), - Example: `policy list --owner-id 516425b2-e369-421b-838d-920e1f51b0f5`, - } - - cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") - if err := cmd.MarkFlagRequired("owner-id"); err != nil { - panic(err) - } - - return cmd - }() - - create := func() *cobra.Command { - var policyPath, ownerID string - var creationRequest policy.CreationRequest + push := func() *cobra.Command { + var ownerID, context string + var creationRequest policy.CreatePolicyBundleRequest cmd := &cobra.Command{ - Short: "create policy", - Use: "create", - RunE: func(cmd *cobra.Command, _ []string) error { - policyData, err := os.ReadFile(policyPath) - if err != nil { - return fmt.Errorf("failed to read policy file: %w", err) - } - creationRequest.Content = string(policyData) + Short: "push policy bundle", + Use: "push", + RunE: func(cmd *cobra.Command, args []string) error { + policyPath := args[0] + creationRequest.Policies = make(map[string]string) - result, err := policy.NewClient(*policyBaseURL, config).CreatePolicy(ownerID, creationRequest) + err := filepath.WalkDir(policyPath, func(path string, f fs.DirEntry, err error) error { + if err != nil { + return err + } + if !f.IsDir() && (filepath.Ext(f.Name()) == ".rego") { + fileContent, err := os.ReadFile(filepath.Clean(path)) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + creationRequest.Policies[f.Name()] = string(fileContent) + } + return nil + }) if err != nil { - return fmt.Errorf("failed to create policy: %w", err) - } - - _, _ = io.WriteString(cmd.ErrOrStderr(), "Policy Created Successfully\n") - - if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(result); err != nil { - return fmt.Errorf("failed to encode result to stdout: %w", err) + return fmt.Errorf("failed to walk policy directory path: %w", err) } - return nil - }, - Args: cobra.ExactArgs(0), - Example: `policy create --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --name policy_name --policy ./policy.rego`, - } - - cmd.Flags().StringVar(&creationRequest.Context, "context", "config", "policy context") - cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego policy file") - cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") - if err := cmd.MarkFlagRequired("owner-id"); err != nil { - panic(err) - } - if err := cmd.MarkFlagRequired("policy"); err != nil { - panic(err) - } - - return cmd - }() - - get := func() *cobra.Command { - var ownerID string - cmd := &cobra.Command{ - Short: "Get a policy", - Use: "get ", - RunE: func(cmd *cobra.Command, args []string) error { - p, err := policy.NewClient(*policyBaseURL, config).GetPolicy(ownerID, args[0]) + err = policy.NewClient(*policyBaseURL, config).CreatePolicyBundle(ownerID, context, creationRequest) if err != nil { - return fmt.Errorf("failed to get policy: %v", err) - } - - if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(p); err != nil { - return fmt.Errorf("failed to output policy in json format: %v", err) + return fmt.Errorf("failed to push policy bundle: %w", err) } + _, _ = io.WriteString(cmd.ErrOrStderr(), "Policy Bundle Pushed Successfully\n") return nil }, Args: cobra.ExactArgs(1), - Example: `policy get 60b7e1a5-c1d7-4422-b813-7a12d353d7c6 --owner-id 516425b2-e369-421b-838d-920e1f51b0f5`, - } - cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") - if err := cmd.MarkFlagRequired("owner-id"); err != nil { - panic(err) + Example: `policy push ./policy_bundle_dir_path --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --context config`, } - return cmd - }() - - delete := func() *cobra.Command { - var ownerID string - cmd := &cobra.Command{ - Short: "Delete a policy", - Use: "delete ", - RunE: func(cmd *cobra.Command, args []string) error { - err := policy.NewClient(*policyBaseURL, config).DeletePolicy(ownerID, args[0]) - if err != nil { - return fmt.Errorf("failed to delete policy: %v", err) - } - _, _ = io.WriteString(cmd.ErrOrStderr(), "Policy Deleted Successfully\n") - return nil - }, - Args: cobra.ExactArgs(1), - Example: `policy delete 60b7e1a5-c1d7-4422-b813-7a12d353d7c6 --owner-id 516425b2-e369-421b-838d-920e1f51b0f5`, - } + cmd.Flags().StringVar(&context, "context", "config", "policy context") cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") if err := cmd.MarkFlagRequired("owner-id"); err != nil { panic(err) @@ -157,50 +82,31 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com return cmd }() - update := func() *cobra.Command { - var policyPath, context, ownerID string - var updateRequest policy.UpdateRequest - + fetch := func() *cobra.Command { + var ownerID, context, policyName string cmd := &cobra.Command{ - Short: "Update a policy", - Use: "update ", + Short: "Fetch policy bundle (or a single policy)", + Use: "fetch ", RunE: func(cmd *cobra.Command, args []string) error { - if !(cmd.Flag("policy").Changed || cmd.Flag("context").Changed) { - return fmt.Errorf("one of policy or context must be set") + if len(args) == 1 { + policyName = args[0] } - - if cmd.Flag("policy").Changed { - policyData, err := os.ReadFile(policyPath) - if err != nil { - return fmt.Errorf("failed to read policy file: %w", err) - } - content := string(policyData) - updateRequest.Content = &content - } - - if cmd.Flag("context").Changed { - updateRequest.Context = &context - } - - result, err := policy.NewClient(*policyBaseURL, config).UpdatePolicy(ownerID, args[0], updateRequest) + policies, err := policy.NewClient(*policyBaseURL, config).FetchPolicyBundle(ownerID, context, policyName) if err != nil { - return fmt.Errorf("failed to update policy: %w", err) + return fmt.Errorf("failed to fetch policy bundle: %v", err) } - _, _ = io.WriteString(cmd.ErrOrStderr(), "Policy Updated Successfully\n") - - if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(result); err != nil { - return fmt.Errorf("failed to encode result to stdout: %w", err) + if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(policies); err != nil { + return fmt.Errorf("failed to output policy bundle in json format: %v", err) } return nil }, - Args: cobra.ExactArgs(1), - Example: `policy update e9e300d1-5bab-4704-b610-addbd6e03b0b --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --name policy_name --policy ./policy.rego`, + Args: cobra.MaximumNArgs(1), + Example: `policy fetch policy_name --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 --context config`, } - cmd.Flags().StringVar(&context, "context", "", "policy context (if set, must be config)") - cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego file containing the updated policy") + cmd.Flags().StringVar(&context, "context", "config", "policy context") cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") if err := cmd.MarkFlagRequired("owner-id"); err != nil { panic(err) @@ -210,7 +116,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com }() logs := func() *cobra.Command { - var after, before, outputFile, ownerID string + var after, before, outputFile, ownerID, context string var request policy.DecisionQueryRequest cmd := &cobra.Command{ @@ -262,7 +168,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com client := policy.NewClient(*policyBaseURL, config) for { - logsBatch, err := client.GetDecisionLogs(ownerID, request) + logsBatch, err := client.GetDecisionLogs(ownerID, context, request) if err != nil { return fmt.Errorf("failed to get policy decision logs: %v", err) } @@ -291,6 +197,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com cmd.Flags().StringVar(&request.Branch, "branch", "", "filter decision logs based on branch name") cmd.Flags().StringVar(&request.ProjectID, "project-id", "", "filter decision logs based on project-id") cmd.Flags().StringVar(&outputFile, "out", "", "specify output file name ") + cmd.Flags().StringVar(&context, "context", "config", "policy context") cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") if err := cmd.MarkFlagRequired("owner-id"); err != nil { panic(err) @@ -305,6 +212,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com policyPath string metaFile string ownerID string + context string request policy.DecisionRequest ) @@ -338,7 +246,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com } request.Input = string(input) request.Metadata = metadata - return policy.NewClient(*policyBaseURL, config).MakeDecision(ownerID, request) + return policy.NewClient(*policyBaseURL, config).MakeDecision(ownerID, context, request) }() if err != nil { return fmt.Errorf("failed to make decision: %w", err) @@ -354,7 +262,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com } cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") - cmd.Flags().StringVar(&request.Context, "context", "config", "policy context for decision") + cmd.Flags().StringVar(&context, "context", "config", "policy context for decision") cmd.Flags().StringVar(&inputPath, "input", "", "path to input file") cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego policy file or directory containing policy files") cmd.Flags().StringVar(&metaFile, "metafile", "", "decision metadata file") @@ -417,11 +325,8 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com return cmd }() - cmd.AddCommand(list) - cmd.AddCommand(create) - cmd.AddCommand(get) - cmd.AddCommand(delete) - cmd.AddCommand(update) + cmd.AddCommand(push) + cmd.AddCommand(fetch) cmd.AddCommand(logs) cmd.AddCommand(decide) cmd.AddCommand(eval) @@ -443,7 +348,7 @@ func getPolicyDecisionLocally(policyPath string, rawInput []byte, meta map[strin return nil, fmt.Errorf("invalid input: %w", err) } - p, err := loadPolicyFromPath(policyPath) + p, err := cpa.LoadPolicyFromFS(policyPath) if err != nil { return nil, fmt.Errorf("failed to load policy files: %w", err) } @@ -463,7 +368,7 @@ func getPolicyEvaluationLocally(policyPath string, rawInput []byte, meta map[str return nil, fmt.Errorf("invalid input: %w", err) } - p, err := loadPolicyFromPath(policyPath) + p, err := cpa.LoadPolicyFromFS(policyPath) if err != nil { return nil, fmt.Errorf("failed to load policy files: %w", err) } @@ -475,19 +380,3 @@ func getPolicyEvaluationLocally(policyPath string, rawInput []byte, meta map[str return decision, nil } - -func loadPolicyFromPath(policyPath string) (*cpa.Policy, error) { - pathInfo, err := os.Stat(policyPath) - if err != nil { - return nil, fmt.Errorf("failed to get path info: %w", err) - } - - loadPolicy := func() func(string) (*cpa.Policy, error) { - if pathInfo.IsDir() { - return cpa.LoadPolicyDirectory - } - return cpa.LoadPolicyFile - }() - - return loadPolicy(policyPath) -} diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index dddaa141f..0c35a5363 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -3,7 +3,6 @@ package policy import ( "bytes" "encoding/json" - "fmt" "io" "net/http" "net/http/httptest" @@ -15,135 +14,69 @@ import ( "github.com/CircleCI-Public/circleci-cli/settings" ) -func TestListPolicies(t *testing.T) { +func TestPushPolicyBundle(t *testing.T) { testcases := []struct { - Name string - Args []string - ServerHandler http.HandlerFunc - ExpectedOutput string - ExpectedErr string + Name string + Args []string + ServerHandler http.HandlerFunc + ExpectedErr string }{ + { + Name: "requires policy bundle directory path ", + Args: []string{"push", "--owner-id", "ownerID"}, + ExpectedErr: "accepts 1 arg(s), received 0", + }, { Name: "requires owner-id", - Args: []string{"list"}, + Args: []string{"push", "./testdata/test0/policy.rego"}, ExpectedErr: "required flag(s) \"owner-id\" not set", }, { - Name: "gets error response", - Args: []string{"list", "--owner-id", "ownerID"}, - ExpectedErr: "failed to list policies: unexpected status-code: 403 - Forbidden", - ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy") - w.WriteHeader(http.StatusForbidden) - _, err := w.Write([]byte(`{"error": "Forbidden"}`)) - assert.NilError(t, err) - }, + Name: "fails for policy bundle directory path not found", + Args: []string{"push", "./testdata/directory_not_present", "--owner-id", "test-org"}, + ExpectedErr: "failed to walk policy directory path: ", }, { - Name: "gets bad json response", - Args: []string{"list", "--owner-id", "ownerID"}, - ExpectedErr: "failed to list policies: failed to decode response body: invalid character '}' looking for beginning of value", + Name: "no policy files in given policy directory path", + Args: []string{"push", "./testdata/test0/no-valid-policy-files", "--owner-id", "test-org", "--context", "custom"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy") - _, err := w.Write([]byte(`{"bad json": }`)) - assert.NilError(t, err) + var body map[string]interface{} + assert.Equal(t, r.Method, "POST") + assert.Equal(t, r.URL.String(), "/api/v1/owner/test-org/context/custom/policy-bundle") + assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.DeepEqual(t, body, map[string]interface{}{ + "policies": map[string]interface{}{}, + }) + w.WriteHeader(http.StatusCreated) }, }, - { - Name: "successfully gets a policy", - Args: []string{"list", "--owner-id", "ownerID"}, - ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy") - _, err := w.Write([]byte(`[ - { - "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", - "name": "policy_1", - "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f", - "context": "config", - "created_at": "2022-05-31T14:15:10.86097Z", - "modified_at": null - } - ]`)) - assert.NilError(t, err) - }, - ExpectedOutput: `[ - { - "context": "config", - "created_at": "2022-05-31T14:15:10.86097Z", - "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", - "modified_at": null, - "name": "policy_1", - "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f" - } -] -`, - }, - } - - for _, tc := range testcases { - t.Run(tc.Name, func(t *testing.T) { - if tc.ServerHandler == nil { - tc.ServerHandler = func(w http.ResponseWriter, r *http.Request) {} - } - - svr := httptest.NewServer(tc.ServerHandler) - defer svr.Close() - - cmd, stdout, _ := makeCMD() - - cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) - - err := cmd.Execute() - if tc.ExpectedErr == "" { - assert.NilError(t, err) - } else { - assert.Error(t, err, tc.ExpectedErr) - return - } - assert.Equal(t, stdout.String(), tc.ExpectedOutput) - }) - } -} - -func TestCreatePolicy(t *testing.T) { - testcases := []struct { - Name string - Args []string - ServerHandler http.HandlerFunc - ExpectedOutput string - ExpectedErr string - }{ - { - Name: "requires owner-id and policy", - Args: []string{"create"}, - ExpectedErr: "required flag(s) \"owner-id\", \"policy\" not set", - }, - { - Name: "fails for policy file not found", - Args: []string{"create", "--owner-id", "test-org", "--policy", "./testdata/file_not_present.rego"}, - ExpectedErr: "failed to read policy file: open ./testdata/file_not_present.rego: ", - }, { Name: "sends appropriate desired request", - Args: []string{"create", "--owner-id", "test-org", "--policy", "./testdata/test.rego"}, + Args: []string{"push", "./testdata/test0", "--owner-id", "test-org", "--context", "custom"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.String(), "/api/v1/owner/test-org/policy") + assert.Equal(t, r.URL.String(), "/api/v1/owner/test-org/context/custom/policy-bundle") assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) assert.DeepEqual(t, body, map[string]interface{}{ - "content": "package test", - "context": "config", + "policies": map[string]interface{}{ + "meta-policy.rego": `package org + +policy_name["meta_policy_test"] +enable_rule["enabled"] { data.meta.branch == "main" } +enable_rule["disabled"] { data.meta.project_id != "test-project-id" } +`, + "policy.rego": `package org + +policy_name["test"] +enable_rule["branch_is_main"] +branch_is_main = "branch must be main!" { input.branch != "main" } +`, + }, }) w.WriteHeader(http.StatusCreated) - _, err := w.Write([]byte("{}")) - assert.NilError(t, err) }, - ExpectedOutput: "{}\n", }, } @@ -156,7 +89,7 @@ func TestCreatePolicy(t *testing.T) { svr := httptest.NewServer(tc.ServerHandler) defer svr.Close() - cmd, stdout, _ := makeCMD() + cmd, _, _ := makeCMD() cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) @@ -167,13 +100,11 @@ func TestCreatePolicy(t *testing.T) { assert.ErrorContains(t, err, tc.ExpectedErr) return } - - assert.Equal(t, stdout.String(), tc.ExpectedOutput) }) } } -func TestGetPolicy(t *testing.T) { +func TestFetchPolicyBundle(t *testing.T) { testcases := []struct { Name string Args []string @@ -181,261 +112,82 @@ func TestGetPolicy(t *testing.T) { ExpectedOutput string ExpectedErr string }{ - { - Name: "requires policy-id", - Args: []string{"get", "--owner-id", "ownerID"}, - ExpectedErr: "accepts 1 arg(s), received 0", - }, { Name: "requires owner-id", - Args: []string{"get", "policyID"}, + Args: []string{"fetch", "policyID"}, ExpectedErr: "required flag(s) \"owner-id\" not set", }, { Name: "gets error response", - Args: []string{"get", "policyID", "--owner-id", "ownerID"}, - ExpectedErr: "failed to get policy: unexpected status-code: 403 - Forbidden", + Args: []string{"fetch", "policyName", "--owner-id", "ownerID", "--context", "someContext"}, + ExpectedErr: "failed to fetch policy bundle: unexpected status-code: 403 - Forbidden", ServerHandler: func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy/policyID") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/someContext/policy-bundle/policyName") w.WriteHeader(http.StatusForbidden) _, err := w.Write([]byte(`{"error": "Forbidden"}`)) assert.NilError(t, err) }, }, { - Name: "successfully gets a policy", - Args: []string{"get", "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", "--owner-id", "462d67f8-b232-4da4-a7de-0c86dd667d3f"}, + Name: "successfully fetches single policy", + Args: []string{"fetch", "my_policy", "--owner-id", "462d67f8-b232-4da4-a7de-0c86dd667d3f"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/policy/60b7e1a5-c1d7-4422-b813-7a12d353d7c6") + assert.Equal(t, r.URL.String(), "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/context/config/policy-bundle/my_policy") _, err := w.Write([]byte(`{ - "document_version": 1, - "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", - "name": "policy_1", - "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f", - "context": "config", - "content": "package test", - "created_at": "2022-05-31T14:15:10.86097Z", - "modified_at": null + "content": "package org\n\npolicy_name[\"my_policy\"] { true }", + "created_at": "2022-08-10T10:47:01.859756-04:00", + "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", + "name": "my_policy" }`)) assert.NilError(t, err) }, ExpectedOutput: `{ - "content": "package test", - "context": "config", - "created_at": "2022-05-31T14:15:10.86097Z", - "document_version": 1, - "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", - "modified_at": null, - "name": "policy_1", - "owner_id": "462d67f8-b232-4da4-a7de-0c86dd667d3f" + "content": "package org\n\npolicy_name[\"my_policy\"] { true }", + "created_at": "2022-08-10T10:47:01.859756-04:00", + "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", + "name": "my_policy" } `, }, - } - - for _, tc := range testcases { - t.Run(tc.Name, func(t *testing.T) { - if tc.ServerHandler == nil { - tc.ServerHandler = func(w http.ResponseWriter, r *http.Request) {} - } - - svr := httptest.NewServer(tc.ServerHandler) - defer svr.Close() - - cmd, stdout, _ := makeCMD() - - cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) - - err := cmd.Execute() - if tc.ExpectedErr == "" { - assert.NilError(t, err) - } else { - assert.Error(t, err, tc.ExpectedErr) - return - } - - assert.Equal(t, stdout.String(), tc.ExpectedOutput) - }) - } -} - -func TestDeletePolicy(t *testing.T) { - testcases := []struct { - Name string - Args []string - ServerHandler http.HandlerFunc - ExpectedOutput string - ExpectedErr string - }{ - { - Name: "requires policy-id", - Args: []string{"delete", "--owner-id", "ownerID"}, - ExpectedErr: "accepts 1 arg(s), received 0", - }, - { - Name: "requires owner-id", - Args: []string{"delete", "policyID"}, - ExpectedErr: "required flag(s) \"owner-id\" not set", - }, { - Name: "gets error response", - Args: []string{"delete", "policyID", "--owner-id", "ownerID"}, - ExpectedErr: "failed to delete policy: unexpected status-code: 403 - Forbidden", + Name: "successfully fetches policy bundle", + Args: []string{"fetch", "--owner-id", "462d67f8-b232-4da4-a7de-0c86dd667d3f"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "DELETE") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/policy/policyID") - w.WriteHeader(http.StatusForbidden) - _, err := w.Write([]byte(`{"error": "Forbidden"}`)) + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/context/config/policy-bundle/") + _, err := w.Write([]byte(`{ + "a": { + "content": "package org\n\npolicy_name[\"a\"] { true }", + "created_at": "2022-08-10T10:47:01.859756-04:00", + "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", + "name": "a" + }, + "b": { + "content": "package org\n\npolicy_name[\"b\"] { true }", + "created_at": "2022-08-10T10:47:01.859756-04:00", + "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", + "name": "b" + } +}`)) assert.NilError(t, err) }, - }, - { - Name: "successfully deletes a policy", - Args: []string{"delete", "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", "--owner-id", "462d67f8-b232-4da4-a7de-0c86dd667d3f"}, - ServerHandler: func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, "DELETE") - assert.Equal(t, r.URL.String(), "/api/v1/owner/462d67f8-b232-4da4-a7de-0c86dd667d3f/policy/60b7e1a5-c1d7-4422-b813-7a12d353d7c6") - w.WriteHeader(http.StatusNoContent) - }, - }, - } - - for _, tc := range testcases { - t.Run(tc.Name, func(t *testing.T) { - if tc.ServerHandler == nil { - tc.ServerHandler = func(w http.ResponseWriter, r *http.Request) {} - } - - svr := httptest.NewServer(tc.ServerHandler) - defer svr.Close() - - cmd, stdout, _ := makeCMD() - - cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) - - err := cmd.Execute() - if tc.ExpectedErr == "" { - assert.NilError(t, err) - } else { - assert.Error(t, err, tc.ExpectedErr) - return - } - - assert.Equal(t, stdout.String(), tc.ExpectedOutput) - }) - } + ExpectedOutput: `{ + "a": { + "content": "package org\n\npolicy_name[\"a\"] { true }", + "created_at": "2022-08-10T10:47:01.859756-04:00", + "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", + "name": "a" + }, + "b": { + "content": "package org\n\npolicy_name[\"b\"] { true }", + "created_at": "2022-08-10T10:47:01.859756-04:00", + "created_by": "737fc204-4048-49fd-9aee-96c97698ed28", + "name": "b" + } } - -func TestUpdatePolicy(t *testing.T) { - makeCMD := func() (*cobra.Command, *bytes.Buffer, *bytes.Buffer) { - config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} - cmd := NewCommand(config, nil) - - stdout := new(bytes.Buffer) - stderr := new(bytes.Buffer) - cmd.SetOut(stdout) - cmd.SetErr(stderr) - - return cmd, stdout, stderr - } - - testcases := []struct { - Name string - Args []string - ServerHandler http.HandlerFunc - ExpectedOutput string - ExpectedErr string - }{ - { - Name: "requires owner-id flag", - Args: []string{"update", "testID"}, - ExpectedErr: "required flag(s) \"owner-id\" not set", - }, - { - Name: "requires policy id", - Args: []string{"update", "--owner-id", "test-org"}, - ExpectedErr: "accepts 1 arg(s), received 0", - }, - { - Name: "fails if policy file not found", - Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--policy", "./testdata/file_not_present.rego"}, - ExpectedErr: "failed to read policy file: open ./testdata/file_not_present.rego: ", - }, - { - Name: "gets error response", - Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--policy", "./testdata/test.rego"}, - ServerHandler: func(w http.ResponseWriter, r *http.Request) { - var body map[string]interface{} - assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-org/policy/test-policy-id") - assert.DeepEqual(t, body, map[string]interface{}{ - "content": "package test", - }) - - w.WriteHeader(http.StatusForbidden) - _, err := w.Write([]byte(`{"error": "Forbidden"}`)) - assert.NilError(t, err) - }, - ExpectedErr: "failed to update policy: unexpected status-code: 403 - Forbidden", - }, - { - Name: "sends appropriate desired request", - Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--policy", "./testdata/test.rego"}, - ServerHandler: func(w http.ResponseWriter, r *http.Request) { - var body map[string]interface{} - assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-org/policy/test-policy-id") - assert.DeepEqual(t, body, map[string]interface{}{ - "content": "package test", - }) - - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte("{}")) - assert.NilError(t, err) - }, - ExpectedOutput: "{}\n", - }, - { - Name: "explicitly set config", - Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--policy", "./testdata/test.rego", "--context", "config"}, - ServerHandler: func(w http.ResponseWriter, r *http.Request) { - var body map[string]interface{} - assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-org/policy/test-policy-id") - assert.DeepEqual(t, body, map[string]interface{}{ - "content": "package test", - "context": "config", - }) - - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte("{}")) - assert.NilError(t, err) - }, - ExpectedOutput: "{}\n", - }, - { - Name: "sends appropriate desired request with only policy path", - Args: []string{"update", "test-policy-id", "--owner-id", "test-org", "--policy", "./testdata/test.rego"}, - ServerHandler: func(w http.ResponseWriter, r *http.Request) { - var body map[string]interface{} - assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-org/policy/test-policy-id") - assert.DeepEqual(t, body, map[string]interface{}{ - "content": "package test", - }) - - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte("{}")) - assert.NilError(t, err) - }, - ExpectedOutput: "{}\n", - }, - { - Name: "check at least one field is changed", - Args: []string{"update", "test-policy-id", "--owner-id", "test-org"}, - ExpectedErr: "one of policy or context must be set", +`, }, } @@ -456,7 +208,7 @@ func TestUpdatePolicy(t *testing.T) { if tc.ExpectedErr == "" { assert.NilError(t, err) } else { - assert.ErrorContains(t, err, tc.ExpectedErr) + assert.Error(t, err, tc.ExpectedErr) return } @@ -493,7 +245,7 @@ func TestGetDecisionLogs(t *testing.T) { Args: []string{"logs", "--owner-id", "ownerID"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/decision") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/config/decision") _, err := w.Write([]byte("[]")) assert.NilError(t, err) }, @@ -507,7 +259,7 @@ func TestGetDecisionLogs(t *testing.T) { }, ServerHandler: func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/decision?after=2022-03-14T00%3A00%3A00Z&before=2022-03-15T00%3A00%3A00Z&branch=branchValue&project_id=projectIDValue&status=PASS") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/config/decision?after=2022-03-14T00%3A00%3A00Z&before=2022-03-15T00%3A00%3A00Z&branch=branchValue&project_id=projectIDValue&status=PASS") _, err := w.Write([]byte("[]")) assert.NilError(t, err) }, @@ -519,7 +271,7 @@ func TestGetDecisionLogs(t *testing.T) { ExpectedErr: "failed to get policy decision logs: unexpected status-code: 403 - Forbidden", ServerHandler: func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Method, "GET") - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/decision") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/config/decision") w.WriteHeader(http.StatusForbidden) _, err := w.Write([]byte(`{"error": "Forbidden"}`)) assert.NilError(t, err) @@ -536,27 +288,29 @@ func TestGetDecisionLogs(t *testing.T) { assert.Equal(t, r.Method, "GET") if count == 0 { - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/decision") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/config/decision") _, err := w.Write([]byte(` [ - { - "metadata": {}, - "created_at": "2022-06-08T16:56:22.179906Z", - "policies": [ - { - "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", - "version": 2 - } - ], - "decision": { - "status": "PASS" - } - } + { + "created_at": "2022-08-11T09:20:40.674594-04:00", + "decision": { + "enabled_rules": [ + "branch_is_main" + ], + "status": "PASS" + }, + "metadata": {}, + "policies": [ + "8c69adc542bcfd6e65f5d5a2b6a4e3764480db2253cd075d0954e64a1f827a9c695c916d5a49302991df781447b3951410824dce8a8282d11ed56302272cf6fb", + "3124131001ec20b4b524260ababa6411190a1bc9c5ac3219ccc2d21109fc5faf4bb9f7bbe38f3f798d9c232d68564390e0ca560877711f3f2ff7f89e10eef685" + ], + "time_taken_ms": 4 + } ]`), ) assert.NilError(t, err) } else if count == 1 { - assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/decision?offset=1") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/config/decision?offset=1") _, err := w.Write([]byte("[]")) assert.NilError(t, err) } else { @@ -566,17 +320,19 @@ func TestGetDecisionLogs(t *testing.T) { }(), ExpectedOutput: `[ { - "created_at": "2022-06-08T16:56:22.179906Z", + "created_at": "2022-08-11T09:20:40.674594-04:00", "decision": { + "enabled_rules": [ + "branch_is_main" + ], "status": "PASS" }, "metadata": {}, "policies": [ - { - "id": "60b7e1a5-c1d7-4422-b813-7a12d353d7c6", - "version": 2 - } - ] + "8c69adc542bcfd6e65f5d5a2b6a4e3764480db2253cd075d0954e64a1f827a9c695c916d5a49302991df781447b3951410824dce8a8282d11ed56302272cf6fb", + "3124131001ec20b4b524260ababa6411190a1bc9c5ac3219ccc2d21109fc5faf4bb9f7bbe38f3f798d9c232d68564390e0ca560877711f3f2ff7f89e10eef685" + ], + "time_taken_ms": 4 } ] `, @@ -603,7 +359,6 @@ func TestGetDecisionLogs(t *testing.T) { assert.Error(t, err, tc.ExpectedErr) return } - fmt.Println(stdout.String()) assert.Equal(t, stdout.String(), tc.ExpectedOutput) }) } @@ -624,17 +379,16 @@ func TestMakeDecisionCommand(t *testing.T) { }, { Name: "sends expected request", - Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test.yml"}, + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/decision") + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/context/config/decision") var payload map[string]interface{} assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) assert.DeepEqual(t, payload, map[string]interface{}{ - "context": "config", - "input": "test: config\n", + "input": "test: config\n", }) _, _ = io.WriteString(w, `{"status":"PASS"}`) @@ -643,17 +397,16 @@ func TestMakeDecisionCommand(t *testing.T) { }, { Name: "sends expected request with context", - Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test.yml", "--context", "custom"}, + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--context", "custom"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/decision") + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/context/custom/decision") var payload map[string]interface{} assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) assert.DeepEqual(t, payload, map[string]interface{}{ - "context": "custom", - "input": "test: config\n", + "input": "test: config\n", }) _, _ = io.WriteString(w, `{"status":"PASS"}`) @@ -662,17 +415,16 @@ func TestMakeDecisionCommand(t *testing.T) { }, { Name: "sends expected request with metadata", - Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test.yml", "--context", "custom", "--metafile", "./testdata/meta.yml"}, + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--context", "custom", "--metafile", "./testdata/test1/meta.yml"}, ServerHandler: func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Method, "POST") - assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/decision") + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/context/custom/decision") var payload map[string]interface{} assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) assert.DeepEqual(t, payload, map[string]interface{}{ - "context": "custom", - "input": "test: config\n", + "input": "test: config\n", "metadata": map[string]interface{}{ "project_id": "test-project-id", "branch": "main", @@ -685,7 +437,7 @@ func TestMakeDecisionCommand(t *testing.T) { }, { Name: "fails on unexpected status code", - Args: []string{"decide", "--input", "./testdata/test.yml", "--owner-id", "test-owner"}, + Args: []string{"decide", "--input", "./testdata/test1/test.yml", "--owner-id", "test-owner"}, ServerHandler: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(500) _, _ = io.WriteString(w, `{"error":"oopsie!"}`) @@ -695,18 +447,18 @@ func TestMakeDecisionCommand(t *testing.T) { }, { Name: "fails if neither local-policy nor owner-id is provided", - Args: []string{"decide", "--input", "./testdata/test.yml"}, + Args: []string{"decide", "--input", "./testdata/test1/test.yml"}, ExpectedErr: "--owner-id or --policy is required", }, { Name: "fails for input file not found", - Args: []string{"decide", "--policy", "./testdata/policy.rego", "--input", "./testdata/no_such_file.yml"}, + Args: []string{"decide", "--policy", "./testdata/test0/policy.rego", "--input", "./testdata/no_such_file.yml"}, ExpectedErr: "failed to read input file: open ./testdata/no_such_file.yml: ", }, { Name: "fails for policy FILE/DIRECTORY not found", - Args: []string{"decide", "--policy", "./testdata/no_such_file.rego", "--input", "./testdata/test.yml"}, - ExpectedErr: "failed to make decision: failed to load policy files: failed to get path info: ", + Args: []string{"decide", "--policy", "./testdata/no_such_file.rego", "--input", "./testdata/test1/test.yml"}, + ExpectedErr: "failed to make decision: failed to load policy files: failed to walk root: ", }, { Name: "successfully performs decision for policy FILE provided locally", @@ -725,7 +477,7 @@ func TestMakeDecisionCommand(t *testing.T) { { Name: "successfully performs decision with metadata for policy FILE provided locally", Args: []string{ - "decide", "--metafile", "./testdata/meta.yml", "--policy", "./testdata/test0/meta-policy.rego", "--input", + "decide", "--metafile", "./testdata/test1/meta.yml", "--policy", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--input", "./testdata/test0/config.yml", }, ExpectedOutput: `{ @@ -778,28 +530,28 @@ func TestRawOPAEvaluationCommand(t *testing.T) { }, { Name: "fails if local-policy is not provided", - Args: []string{"eval", "--input", "./testdata/test.yml"}, + Args: []string{"eval", "--input", "./testdata/test1/test.yml"}, ExpectedErr: `required flag(s) "policy" not set`, }, { Name: "fails if input is not provided", - Args: []string{"eval", "--policy", "./testdata/policy.rego"}, + Args: []string{"eval", "--policy", "./testdata/test0/policy.rego"}, ExpectedErr: `required flag(s) "input" not set`, }, { Name: "fails for input file not found", - Args: []string{"eval", "--policy", "./testdata/policy.rego", "--input", "./testdata/no_such_file.yml"}, + Args: []string{"eval", "--policy", "./testdata/test0/policy.rego", "--input", "./testdata/no_such_file.yml"}, ExpectedErr: "failed to read input file: open ./testdata/no_such_file.yml: ", }, { Name: "fails for policy FILE/DIRECTORY not found", - Args: []string{"eval", "--policy", "./testdata/no_such_file.rego", "--input", "./testdata/test.yml"}, - ExpectedErr: "failed to make decision: failed to load policy files: failed to get path info: ", + Args: []string{"eval", "--policy", "./testdata/no_such_file.rego", "--input", "./testdata/test1/test.yml"}, + ExpectedErr: "failed to make decision: failed to load policy files: failed to walk root: ", }, { Name: "successfully performs raw opa evaluation for policy FILE provided locally, input and metadata", Args: []string{ - "eval", "--metafile", "./testdata/meta.yml", "--policy", "./testdata/test0/meta-policy.rego", "--input", + "eval", "--metafile", "./testdata/test1/meta.yml", "--policy", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--input", "./testdata/test0/config.yml", }, ExpectedOutput: `{ @@ -821,7 +573,7 @@ func TestRawOPAEvaluationCommand(t *testing.T) { { Name: "successfully performs raw opa evaluation for policy FILE provided locally, input, metadata and query", Args: []string{ - "eval", "--metafile", "./testdata/meta.yml", "--policy", "./testdata/test0/meta-policy.rego", "--input", + "eval", "--metafile", "./testdata/test1/meta.yml", "--policy", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--input", "./testdata/test0/config.yml", "--query", "data.org.enable_rule", }, ExpectedOutput: `[ diff --git a/cmd/policy/testdata/policy-expected-usage.txt b/cmd/policy/testdata/policy-expected-usage.txt index 6218ba1c9..61b51179e 100644 --- a/cmd/policy/testdata/policy-expected-usage.txt +++ b/cmd/policy/testdata/policy-expected-usage.txt @@ -2,14 +2,11 @@ Usage: policy [command] Available Commands: - create create policy decide make a decision - delete Delete a policy eval perform raw opa evaluation locally - get Get a policy - list List all policies + fetch Fetch policy bundle (or a single policy) logs Get policy (decision) logs - update Update a policy + push push policy bundle Flags: --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/fetch-expected-usage.txt b/cmd/policy/testdata/policy/fetch-expected-usage.txt new file mode 100644 index 000000000..279278391 --- /dev/null +++ b/cmd/policy/testdata/policy/fetch-expected-usage.txt @@ -0,0 +1,12 @@ +Usage: + policy fetch [flags] + +Examples: +policy fetch policy_name --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 --context config + +Flags: + --context string policy context (default "config") + --owner-id string the id of the policy's owner + +Global Flags: + --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/logs-expected-usage.txt b/cmd/policy/testdata/policy/logs-expected-usage.txt index 0c8f086b3..2bfdc4274 100644 --- a/cmd/policy/testdata/policy/logs-expected-usage.txt +++ b/cmd/policy/testdata/policy/logs-expected-usage.txt @@ -8,6 +8,7 @@ Flags: --after string filter decision logs triggered AFTER this datetime --before string filter decision logs triggered BEFORE this datetime --branch string filter decision logs based on branch name + --context string policy context (default "config") --out string specify output file name --owner-id string the id of the policy's owner --project-id string filter decision logs based on project-id diff --git a/cmd/policy/testdata/policy/push-expected-usage.txt b/cmd/policy/testdata/policy/push-expected-usage.txt new file mode 100644 index 000000000..ead0331c4 --- /dev/null +++ b/cmd/policy/testdata/policy/push-expected-usage.txt @@ -0,0 +1,12 @@ +Usage: + policy push [flags] + +Examples: +policy push ./policy_bundle_dir_path --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --context config + +Flags: + --context string policy context (default "config") + --owner-id string the id of the policy's owner + +Global Flags: + --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/test.rego b/cmd/policy/testdata/test.rego deleted file mode 100644 index 761254b6c..000000000 --- a/cmd/policy/testdata/test.rego +++ /dev/null @@ -1 +0,0 @@ -package test \ No newline at end of file diff --git a/cmd/policy/testdata/test0/no-valid-policy-files/should_be_ignored.txt b/cmd/policy/testdata/test0/no-valid-policy-files/should_be_ignored.txt new file mode 100644 index 000000000..eb1dca8a3 --- /dev/null +++ b/cmd/policy/testdata/test0/no-valid-policy-files/should_be_ignored.txt @@ -0,0 +1 @@ +this file should be ignored while looking for policy files, based on its file extension \ No newline at end of file diff --git a/cmd/policy/testdata/test0/policy.rego b/cmd/policy/testdata/test0/policy.rego index c6056bba0..fb722360a 100644 --- a/cmd/policy/testdata/test0/policy.rego +++ b/cmd/policy/testdata/test0/policy.rego @@ -1,4 +1,5 @@ package org + policy_name["test"] enable_rule["branch_is_main"] -branch_is_main = "branch must be main!" { input.branch != "main" } \ No newline at end of file +branch_is_main = "branch must be main!" { input.branch != "main" } diff --git a/cmd/policy/testdata/test0/meta-policy.rego b/cmd/policy/testdata/test0/subdir/meta-policy-subdir/meta-policy.rego similarity index 99% rename from cmd/policy/testdata/test0/meta-policy.rego rename to cmd/policy/testdata/test0/subdir/meta-policy-subdir/meta-policy.rego index a083a8472..3553e4c74 100644 --- a/cmd/policy/testdata/test0/meta-policy.rego +++ b/cmd/policy/testdata/test0/subdir/meta-policy-subdir/meta-policy.rego @@ -1,6 +1,5 @@ package org policy_name["meta_policy_test"] - enable_rule["enabled"] { data.meta.branch == "main" } enable_rule["disabled"] { data.meta.project_id != "test-project-id" } diff --git a/cmd/policy/testdata/test0/subdir/meta-policy-subdir/should_be_ignored.txt b/cmd/policy/testdata/test0/subdir/meta-policy-subdir/should_be_ignored.txt new file mode 100644 index 000000000..eb1dca8a3 --- /dev/null +++ b/cmd/policy/testdata/test0/subdir/meta-policy-subdir/should_be_ignored.txt @@ -0,0 +1 @@ +this file should be ignored while looking for policy files, based on its file extension \ No newline at end of file diff --git a/cmd/policy/testdata/meta.yml b/cmd/policy/testdata/test1/meta.yml similarity index 100% rename from cmd/policy/testdata/meta.yml rename to cmd/policy/testdata/test1/meta.yml diff --git a/cmd/policy/testdata/test.yml b/cmd/policy/testdata/test1/test.yml similarity index 100% rename from cmd/policy/testdata/test.yml rename to cmd/policy/testdata/test1/test.yml diff --git a/go.mod b/go.mod index 002cf223b..e0f0d03d7 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/CircleCI-Public/circleci-cli require ( github.com/AlecAivazis/survey/v2 v2.1.1 - github.com/CircleCI-Public/circle-policy-agent v0.0.116 + github.com/CircleCI-Public/circle-policy-agent v0.0.137 github.com/Masterminds/semver v1.4.2 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/blang/semver v3.5.1+incompatible @@ -52,21 +52,21 @@ require ( github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/nxadm/tail v1.4.8 // indirect - github.com/open-policy-agent/opa v0.42.2 // indirect + github.com/open-policy-agent/opa v0.43.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rivo/uniseg v0.1.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect - github.com/vektah/gqlparser/v2 v2.4.6 // indirect + github.com/vektah/gqlparser/v2 v2.4.7 // indirect github.com/xanzy/ssh-agent v0.2.1 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yashtewari/glob-intersection v0.1.0 // indirect - golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect - golang.org/x/net v0.0.0-20220708220712-1185a9018129 // indirect - golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect + golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect + golang.org/x/net v0.0.0-20220809184613-07c6da5e1ced // indirect + golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect - google.golang.org/protobuf v1.28.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 945a5c66f..264928925 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CircleCI-Public/circle-policy-agent v0.0.116 h1:ur8yWobPNig5GiPpriPoaMN8/W/80z48CDztCIoZIig= -github.com/CircleCI-Public/circle-policy-agent v0.0.116/go.mod h1:JB9mRsC/ua95h+E8rMgudFDWqE6fR4Y4l73qzdNpPbw= +github.com/CircleCI-Public/circle-policy-agent v0.0.137 h1:/fjxEoZN7Pr2MUc8zewXlKkcu7xwG4JHcmag1mZ3fiU= +github.com/CircleCI-Public/circle-policy-agent v0.0.137/go.mod h1:oggrt0v/95EQdYKWMb5525GreoF6ZGey9PcAoItI9OY= github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= @@ -664,7 +664,7 @@ github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.8 h1:JahtItbkWjf2jzm/T+qgMxkP9EMHsqEUA6vCMGmXvhA= +github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= 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/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -788,8 +788,8 @@ github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDs github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/open-policy-agent/opa v0.42.2 h1:qocVAKyjrqMjCqsU02S/gHyLr4AQQ9xMtuV1kKnnyhM= -github.com/open-policy-agent/opa v0.42.2/go.mod h1:MrmoTi/BsKWT58kXlVayBb+rYVeaMwuBm3nYAN3923s= +github.com/open-policy-agent/opa v0.43.0 h1:UKTpyFUPMs4wYYL1qsXpYQQurBCPoEjFbxaD/4V46gY= +github.com/open-policy-agent/opa v0.43.0/go.mod h1:xfTsKQEMvy7CxxgsCFoYuzT9jA/8C4JWLignCkN4Dzw= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -914,8 +914,9 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -982,9 +983,9 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ 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/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/vektah/gqlparser/v2 v2.4.5/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0= -github.com/vektah/gqlparser/v2 v2.4.6 h1:Yjzp66g6oVq93Jihbi0qhGnf/6zIWjcm8H6gA27zstE= github.com/vektah/gqlparser/v2 v2.4.6/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0= +github.com/vektah/gqlparser/v2 v2.4.7 h1:yub2WLoSIr+chP1zMv6bjrsgTasfubxGZJeC8ISEpgE= +github.com/vektah/gqlparser/v2 v2.4.7/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= @@ -1101,8 +1102,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1199,8 +1200,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0= -golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220809184613-07c6da5e1ced h1:3dYNDff0VT5xj+mbj2XucFst9WKk6PdGOrb9n+SbIvw= +golang.org/x/net v0.0.0-20220809184613-07c6da5e1ced/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 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/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1335,8 +1336,9 @@ golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 h1:v1W7bwXHsnLLloWYTVEdvGvA7BHMeBYsPcF0GLDxIRs= +golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1544,7 +1546,7 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= 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= @@ -1558,8 +1560,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= 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= From 5ef677621a6de54215edc8df0a9bbb6d6713d291 Mon Sep 17 00:00:00 2001 From: David Desmarais-Michaud Date: Thu, 18 Aug 2022 13:36:03 -0400 Subject: [PATCH 38/48] added dry mode to policy creation (#767) * Add prompt during policy push * Add policy diff subcommand * made policy path positional argument in policy decide and eval commands Co-authored-by: Sagar Gupta --- api/policy/policy.go | 31 ++- api/policy/policy_test.go | 38 +++- cmd/policy/policy.go | 146 +++++++++++--- cmd/policy/policy_test.go | 181 +++++++++++++----- cmd/policy/testdata/policy-expected-usage.txt | 1 + .../testdata/policy/decide-expected-usage.txt | 1 - .../testdata/policy/diff-expected-usage.txt | 9 + .../testdata/policy/eval-expected-usage.txt | 1 - .../testdata/policy/push-expected-usage.txt | 1 + go.mod | 1 + go.sum | 3 +- 11 files changed, 327 insertions(+), 86 deletions(-) create mode 100644 cmd/policy/testdata/policy/diff-expected-usage.txt diff --git a/api/policy/policy.go b/api/policy/policy.go index 339c2bdf8..cbb13ce63 100644 --- a/api/policy/policy.go +++ b/api/policy/policy.go @@ -35,37 +35,50 @@ type httpError struct { // CreatePolicyBundleRequest defines the fields for the Create-Policy-Bundle endpoint as defined in Policy Service type CreatePolicyBundleRequest struct { Policies map[string]string `json:"policies"` + DryRun bool `json:"-"` } // CreatePolicyBundle calls the Create Policy Bundle API in the Policy-Service. // It creates a policy bundle for the specified owner+context and returns the http status code as response -func (c Client) CreatePolicyBundle(ownerID string, context string, policy CreatePolicyBundleRequest) error { - data, err := json.Marshal(policy) +func (c Client) CreatePolicyBundle(ownerID string, context string, request CreatePolicyBundleRequest) (interface{}, error) { + data, err := json.Marshal(request) if err != nil { - return fmt.Errorf("failed to encode policy payload: %w", err) + return nil, fmt.Errorf("failed to encode policy payload: %w", err) } req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/owner/%s/context/%s/policy-bundle", c.serverUrl, ownerID, context), bytes.NewReader(data)) if err != nil { - return fmt.Errorf("failed to construct request: %v", err) + return nil, fmt.Errorf("failed to construct request: %v", err) } req.Header.Set("Content-Length", strconv.Itoa(len(data))) + if request.DryRun { + q := req.URL.Query() + q.Set("dry", "true") + req.URL.RawQuery = q.Encode() + } + resp, err := c.client.Do(req) if err != nil { - return fmt.Errorf("failed to get response from policy-service: %w", err) + return nil, fmt.Errorf("failed to get response from policy-service: %w", err) } defer resp.Body.Close() - if resp.StatusCode != http.StatusCreated { + if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK { var response httpError if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { - return fmt.Errorf("unexpected status-code: %d", resp.StatusCode) + return nil, fmt.Errorf("unexpected status-code: %d", resp.StatusCode) } - return fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, response.Error) + return nil, fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, response.Error) } - return nil + + var body interface{} + if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { + return nil, fmt.Errorf("failed to decode response body: %v", err) + } + + return body, nil } // FetchPolicyBundle calls the GET policy-bundle API in the policy-service diff --git a/api/policy/policy_test.go b/api/policy/policy_test.go index 63ce5e29d..9128021be 100644 --- a/api/policy/policy_test.go +++ b/api/policy/policy_test.go @@ -153,13 +153,47 @@ func TestClientCreatePolicy(t *testing.T) { assert.DeepEqual(t, actual, req) w.WriteHeader(http.StatusCreated) + _, _ = w.Write([]byte("{}")) })) defer svr.Close() config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} client := NewClient(svr.URL, config) - err := client.CreatePolicyBundle("ownerId", "config", req) + _, err := client.CreatePolicyBundle("ownerId", "config", req) + assert.NilError(t, err) + }) + + t.Run("expected dry request", func(t *testing.T) { + req := CreatePolicyBundleRequest{ + Policies: map[string]string{"policy_a": "package org"}, + DryRun: true, + } + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + assert.Equal(t, r.Header.Get("accept"), "application/json") + assert.Equal(t, r.Header.Get("content-type"), "application/json") + assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + + assert.Equal(t, r.Method, "POST") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/context/config/policy-bundle") + assert.Equal(t, r.URL.RawQuery, "dry=true") + + var actual CreatePolicyBundleRequest + assert.NilError(t, json.NewDecoder(r.Body).Decode(&actual)) + assert.DeepEqual(t, actual, CreatePolicyBundleRequest{Policies: req.Policies}) + + w.WriteHeader(http.StatusCreated) + _, _ = w.Write([]byte("{}")) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} + client := NewClient(svr.URL, config) + + _, err := client.CreatePolicyBundle("ownerId", "config", req) assert.NilError(t, err) }) @@ -175,7 +209,7 @@ func TestClientCreatePolicy(t *testing.T) { config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} client := NewClient(svr.URL, config) - err := client.CreatePolicyBundle("ownerId", "config", CreatePolicyBundleRequest{}) + _, err := client.CreatePolicyBundle("ownerId", "config", CreatePolicyBundleRequest{}) assert.Error(t, err, "unexpected status-code: 403 - Forbidden") }) } diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index ada9d62b4..864671a80 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -8,6 +8,7 @@ import ( "io/fs" "os" "path/filepath" + "strings" "time" "github.com/CircleCI-Public/circle-policy-agent/cpa" @@ -35,44 +36,89 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com push := func() *cobra.Command { var ownerID, context string - var creationRequest policy.CreatePolicyBundleRequest + var noPrompt bool + var request policy.CreatePolicyBundleRequest cmd := &cobra.Command{ Short: "push policy bundle", Use: "push", RunE: func(cmd *cobra.Command, args []string) error { - policyPath := args[0] - creationRequest.Policies = make(map[string]string) + bundle, err := loadBundleFromFS(args[0]) + if err != nil { + return fmt.Errorf("failed to walk policy directory path: %w", err) + } - err := filepath.WalkDir(policyPath, func(path string, f fs.DirEntry, err error) error { + request.Policies = bundle + + client := policy.NewClient(*policyBaseURL, config) + + if !noPrompt { + request.DryRun = true + diff, err := client.CreatePolicyBundle(ownerID, context, request) if err != nil { - return err + return fmt.Errorf("failed to get bundle diff: %v", err) } - if !f.IsDir() && (filepath.Ext(f.Name()) == ".rego") { - fileContent, err := os.ReadFile(filepath.Clean(path)) - if err != nil { - return fmt.Errorf("failed to read file: %w", err) - } - creationRequest.Policies[f.Name()] = string(fileContent) + + _, _ = io.WriteString(cmd.ErrOrStderr(), "The following changes are going to be made: ") + _ = prettyJSONEncoder(cmd.ErrOrStderr()).Encode(diff) + _, _ = io.WriteString(cmd.ErrOrStderr(), "\n") + + if !Confirm(cmd.ErrOrStderr(), cmd.InOrStdin(), "Do you wish to continue? (y/N)") { + return nil } - return nil - }) - if err != nil { - return fmt.Errorf("failed to walk policy directory path: %w", err) + _, _ = io.WriteString(cmd.ErrOrStderr(), "\n") } - err = policy.NewClient(*policyBaseURL, config).CreatePolicyBundle(ownerID, context, creationRequest) + request.DryRun = false + + diff, err := client.CreatePolicyBundle(ownerID, context, request) if err != nil { return fmt.Errorf("failed to push policy bundle: %w", err) } _, _ = io.WriteString(cmd.ErrOrStderr(), "Policy Bundle Pushed Successfully\n") + _, _ = io.WriteString(cmd.ErrOrStderr(), "\ndiff: ") + _ = prettyJSONEncoder(cmd.OutOrStdout()).Encode(diff) + return nil }, Args: cobra.ExactArgs(1), Example: `policy push ./policy_bundle_dir_path --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --context config`, } + cmd.Flags().StringVar(&context, "context", "config", "policy context") + cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") + cmd.Flags().BoolVar(&noPrompt, "no-prompt", false, "removes the prompt") + if err := cmd.MarkFlagRequired("owner-id"); err != nil { + panic(err) + } + + return cmd + }() + + diff := func() *cobra.Command { + var ownerID, context string + cmd := &cobra.Command{ + Short: "Get diff between local and remote policy bundles", + Use: "diff", + RunE: func(cmd *cobra.Command, args []string) error { + bundle, err := loadBundleFromFS(args[0]) + if err != nil { + return fmt.Errorf("failed to walk policy directory path: %w", err) + } + + diff, err := policy.NewClient(*policyBaseURL, config).CreatePolicyBundle(ownerID, context, policy.CreatePolicyBundleRequest{ + Policies: bundle, + DryRun: true, + }) + if err != nil { + return fmt.Errorf("failed to get diff: %w", err) + } + + return prettyJSONEncoder(cmd.OutOrStdout()).Encode(diff) + }, + Args: cobra.ExactArgs(1), + } cmd.Flags().StringVar(&context, "context", "config", "policy context") cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") if err := cmd.MarkFlagRequired("owner-id"); err != nil { @@ -219,9 +265,12 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com cmd := &cobra.Command{ Short: "make a decision", Use: "decide", - RunE: func(cmd *cobra.Command, _ []string) error { - if policyPath == "" && ownerID == "" { - return fmt.Errorf("--owner-id or --policy is required") + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 1 { + policyPath = args[0] + } + if (policyPath == "" && ownerID == "") || (policyPath != "" && ownerID != "") { + return fmt.Errorf("either policy-path or --owner-id is required") } input, err := os.ReadFile(inputPath) @@ -258,13 +307,12 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com return nil }, - Args: cobra.ExactArgs(0), + Args: cobra.MaximumNArgs(1), } cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") cmd.Flags().StringVar(&context, "context", "config", "policy context for decision") cmd.Flags().StringVar(&inputPath, "input", "", "path to input file") - cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego policy file or directory containing policy files") cmd.Flags().StringVar(&metaFile, "metafile", "", "decision metadata file") if err := cmd.MarkFlagRequired("input"); err != nil { @@ -275,11 +323,12 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com }() eval := func() *cobra.Command { - var inputPath, policyPath, metaFile, query string + var inputPath, metaFile, query string cmd := &cobra.Command{ Short: "perform raw opa evaluation locally", Use: "eval", - RunE: func(cmd *cobra.Command, _ []string) error { + RunE: func(cmd *cobra.Command, args []string) error { + policyPath := args[0] input, err := os.ReadFile(inputPath) if err != nil { return fmt.Errorf("failed to read input file: %w", err) @@ -307,25 +356,22 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com return nil }, - Args: cobra.ExactArgs(0), + Args: cobra.ExactArgs(1), } cmd.Flags().StringVar(&inputPath, "input", "", "path to input file") - cmd.Flags().StringVar(&policyPath, "policy", "", "path to rego policy file or directory containing policy files") cmd.Flags().StringVar(&metaFile, "metafile", "", "decision metadata file") cmd.Flags().StringVar(&query, "query", "data", "policy decision query") if err := cmd.MarkFlagRequired("input"); err != nil { panic(err) } - if err := cmd.MarkFlagRequired("policy"); err != nil { - panic(err) - } return cmd }() cmd.AddCommand(push) + cmd.AddCommand(diff) cmd.AddCommand(fetch) cmd.AddCommand(logs) cmd.AddCommand(decide) @@ -380,3 +426,47 @@ func getPolicyEvaluationLocally(policyPath string, rawInput []byte, meta map[str return decision, nil } + +func loadBundleFromFS(root string) (map[string]string, error) { + root = filepath.Clean(root) + + rootInfo, err := os.Stat(root) + if err != nil { + return nil, fmt.Errorf("failed to get path info: %w", err) + } + if !rootInfo.IsDir() { + return nil, fmt.Errorf("policy path is not a directory") + } + + bundle := make(map[string]string) + + err = filepath.WalkDir(root, func(path string, f fs.DirEntry, err error) error { + if err != nil { + return err + } + if f.IsDir() || filepath.Ext(path) != ".rego" { + return nil + } + + fileContent, err := os.ReadFile(filepath.Clean(path)) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + + bundle[path] = string(fileContent) + + return nil + }) + + return bundle, err +} + +func Confirm(w io.Writer, r io.Reader, question string) bool { + fmt.Fprint(w, question+" ") + var answer string + + _, _ = fmt.Fscanln(r, &answer) + + answer = strings.ToLower(answer) + return answer == "y" || answer == "yes" +} diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index 0c35a5363..80f6328d4 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -2,11 +2,16 @@ package policy import ( "bytes" + "embed" "encoding/json" "io" "net/http" "net/http/httptest" + "path" + "path/filepath" + "sync" "testing" + "time" "github.com/spf13/cobra" "gotest.tools/v3/assert" @@ -14,12 +19,86 @@ import ( "github.com/CircleCI-Public/circleci-cli/settings" ) -func TestPushPolicyBundle(t *testing.T) { +//go:embed testdata +var testdata embed.FS + +func testdataContent(t *testing.T, filePath string) string { + data, err := testdata.ReadFile(path.Join(".", "testdata", filePath)) + assert.NilError(t, err) + return string(data) +} + +func TestPushPolicyWithPrompt(t *testing.T) { + var requestCount int + + expectedURLs := []string{ + "/api/v1/owner/test-org/context/config/policy-bundle?dry=true", + "/api/v1/owner/test-org/context/config/policy-bundle", + } + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var body map[string]interface{} + assert.Equal(t, r.Method, "POST") + assert.Equal(t, r.URL.String(), expectedURLs[requestCount]) + assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.DeepEqual(t, body, map[string]interface{}{ + "policies": map[string]interface{}{ + filepath.Join("testdata", "test0", "policy.rego"): testdataContent(t, "test0/policy.rego"), + filepath.Join("testdata", "test0", "subdir", "meta-policy-subdir", "meta-policy.rego"): testdataContent(t, "test0/subdir/meta-policy-subdir/meta-policy.rego"), + }, + }) + w.WriteHeader(http.StatusCreated) + _, _ = w.Write([]byte("{}")) + requestCount++ + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient} + cmd := NewCommand(config, nil) + + buffer := makeSafeBuffer() + + pr, pw := io.Pipe() + + cmd.SetOut(buffer) + cmd.SetErr(buffer) + cmd.SetIn(pr) + + cmd.SetArgs([]string{ + "push", "./testdata/test0", + "--owner-id", "test-org", + "--policy-base-url", svr.URL, + }) + + done := make(chan struct{}) + go func() { + assert.NilError(t, cmd.Execute()) + close(done) + }() + + time.Sleep(50 * time.Millisecond) + + expectedMessage := "The following changes are going to be made: {}\n\nDo you wish to continue? (y/N) " + assert.Equal(t, buffer.String(), expectedMessage) + + _, err := pw.Write([]byte("y\n")) + assert.NilError(t, err) + + time.Sleep(50 * time.Millisecond) + + assert.Equal(t, buffer.String()[len(expectedMessage):], "\nPolicy Bundle Pushed Successfully\n\ndiff: {}\n") + + <-done +} + +func TestPushPolicyBundleNoPrompt(t *testing.T) { testcases := []struct { - Name string - Args []string - ServerHandler http.HandlerFunc - ExpectedErr string + Name string + Args []string + ServerHandler http.HandlerFunc + ExpectedErr string + ExpectedStdErr string + ExpectedStdOut string }{ { Name: "requires policy bundle directory path ", @@ -48,7 +127,10 @@ func TestPushPolicyBundle(t *testing.T) { "policies": map[string]interface{}{}, }) w.WriteHeader(http.StatusCreated) + _, _ = w.Write([]byte("{}")) }, + ExpectedStdOut: "{}\n", + ExpectedStdErr: "Policy Bundle Pushed Successfully\n\ndiff: ", }, { Name: "sends appropriate desired request", @@ -60,23 +142,16 @@ func TestPushPolicyBundle(t *testing.T) { assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) assert.DeepEqual(t, body, map[string]interface{}{ "policies": map[string]interface{}{ - "meta-policy.rego": `package org - -policy_name["meta_policy_test"] -enable_rule["enabled"] { data.meta.branch == "main" } -enable_rule["disabled"] { data.meta.project_id != "test-project-id" } -`, - "policy.rego": `package org - -policy_name["test"] -enable_rule["branch_is_main"] -branch_is_main = "branch must be main!" { input.branch != "main" } -`, + filepath.Join("testdata", "test0", "policy.rego"): testdataContent(t, "test0/policy.rego"), + filepath.Join("testdata", "test0", "subdir", "meta-policy-subdir", "meta-policy.rego"): testdataContent(t, "test0/subdir/meta-policy-subdir/meta-policy.rego"), }, }) w.WriteHeader(http.StatusCreated) + _, _ = w.Write([]byte("{}")) }, + ExpectedStdOut: "{}\n", + ExpectedStdErr: "Policy Bundle Pushed Successfully\n\ndiff: ", }, } @@ -89,17 +164,19 @@ branch_is_main = "branch must be main!" { input.branch != "main" } svr := httptest.NewServer(tc.ServerHandler) defer svr.Close() - cmd, _, _ := makeCMD() + cmd, stdout, stderr := makeCMD() - cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) + cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL, "--no-prompt")) err := cmd.Execute() - if tc.ExpectedErr == "" { - assert.NilError(t, err) - } else { + if tc.ExpectedErr != "" { assert.ErrorContains(t, err, tc.ExpectedErr) return } + + assert.NilError(t, err) + assert.Equal(t, stdout.String(), tc.ExpectedStdOut) + assert.Equal(t, stderr.String(), tc.ExpectedStdErr) }) } } @@ -448,24 +525,21 @@ func TestMakeDecisionCommand(t *testing.T) { { Name: "fails if neither local-policy nor owner-id is provided", Args: []string{"decide", "--input", "./testdata/test1/test.yml"}, - ExpectedErr: "--owner-id or --policy is required", + ExpectedErr: "either policy-path or --owner-id is required", }, { Name: "fails for input file not found", - Args: []string{"decide", "--policy", "./testdata/test0/policy.rego", "--input", "./testdata/no_such_file.yml"}, + Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/no_such_file.yml"}, ExpectedErr: "failed to read input file: open ./testdata/no_such_file.yml: ", }, { Name: "fails for policy FILE/DIRECTORY not found", - Args: []string{"decide", "--policy", "./testdata/no_such_file.rego", "--input", "./testdata/test1/test.yml"}, + Args: []string{"decide", "./testdata/no_such_file.rego", "--input", "./testdata/test1/test.yml"}, ExpectedErr: "failed to make decision: failed to load policy files: failed to walk root: ", }, { Name: "successfully performs decision for policy FILE provided locally", - Args: []string{ - "decide", "--policy", "./testdata/test0/policy.rego", "--input", - "./testdata/test0/config.yml", - }, + Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/test0/config.yml"}, ExpectedOutput: `{ "status": "PASS", "enabled_rules": [ @@ -477,8 +551,8 @@ func TestMakeDecisionCommand(t *testing.T) { { Name: "successfully performs decision with metadata for policy FILE provided locally", Args: []string{ - "decide", "--metafile", "./testdata/test1/meta.yml", "--policy", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--input", - "./testdata/test0/config.yml", + "decide", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--metafile", + "./testdata/test1/meta.yml", "--input", "./testdata/test0/config.yml", }, ExpectedOutput: `{ "status": "PASS", @@ -523,36 +597,31 @@ func TestRawOPAEvaluationCommand(t *testing.T) { ExpectedOutput string ExpectedErr string }{ - { - Name: "requires flags", - Args: []string{"eval"}, - ExpectedErr: `required flag(s) "input", "policy" not set`, - }, { Name: "fails if local-policy is not provided", Args: []string{"eval", "--input", "./testdata/test1/test.yml"}, - ExpectedErr: `required flag(s) "policy" not set`, + ExpectedErr: `accepts 1 arg(s), received 0`, }, { Name: "fails if input is not provided", - Args: []string{"eval", "--policy", "./testdata/test0/policy.rego"}, + Args: []string{"eval", "./testdata/test0/policy.rego"}, ExpectedErr: `required flag(s) "input" not set`, }, { Name: "fails for input file not found", - Args: []string{"eval", "--policy", "./testdata/test0/policy.rego", "--input", "./testdata/no_such_file.yml"}, + Args: []string{"eval", "./testdata/test0/policy.rego", "--input", "./testdata/no_such_file.yml"}, ExpectedErr: "failed to read input file: open ./testdata/no_such_file.yml: ", }, { Name: "fails for policy FILE/DIRECTORY not found", - Args: []string{"eval", "--policy", "./testdata/no_such_file.rego", "--input", "./testdata/test1/test.yml"}, + Args: []string{"eval", "./testdata/no_such_file.rego", "--input", "./testdata/test1/test.yml"}, ExpectedErr: "failed to make decision: failed to load policy files: failed to walk root: ", }, { Name: "successfully performs raw opa evaluation for policy FILE provided locally, input and metadata", Args: []string{ - "eval", "--metafile", "./testdata/test1/meta.yml", "--policy", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--input", - "./testdata/test0/config.yml", + "eval", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--metafile", + "./testdata/test1/meta.yml", "--input", "./testdata/test0/config.yml", }, ExpectedOutput: `{ "meta": { @@ -573,8 +642,8 @@ func TestRawOPAEvaluationCommand(t *testing.T) { { Name: "successfully performs raw opa evaluation for policy FILE provided locally, input, metadata and query", Args: []string{ - "eval", "--metafile", "./testdata/test1/meta.yml", "--policy", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--input", - "./testdata/test0/config.yml", "--query", "data.org.enable_rule", + "eval", "./testdata/test0/subdir/meta-policy-subdir/meta-policy.rego", "--metafile", + "./testdata/test1/meta.yml", "--input", "./testdata/test0/config.yml", "--query", "data.org.enable_rule", }, ExpectedOutput: `[ "enabled" @@ -619,3 +688,27 @@ func makeCMD() (*cobra.Command, *bytes.Buffer, *bytes.Buffer) { return cmd, stdout, stderr } + +type SafeBuffer struct { + *bytes.Buffer + mu *sync.Mutex +} + +func (buf SafeBuffer) Write(data []byte) (int, error) { + buf.mu.Lock() + defer buf.mu.Unlock() + return buf.Buffer.Write(data) +} + +func (buf SafeBuffer) String() string { + buf.mu.Lock() + defer buf.mu.Unlock() + return buf.Buffer.String() +} + +func makeSafeBuffer() SafeBuffer { + return SafeBuffer{ + Buffer: &bytes.Buffer{}, + mu: &sync.Mutex{}, + } +} diff --git a/cmd/policy/testdata/policy-expected-usage.txt b/cmd/policy/testdata/policy-expected-usage.txt index 61b51179e..7d1636b9e 100644 --- a/cmd/policy/testdata/policy-expected-usage.txt +++ b/cmd/policy/testdata/policy-expected-usage.txt @@ -3,6 +3,7 @@ Usage: Available Commands: decide make a decision + diff Get diff between local and remote policy bundles eval perform raw opa evaluation locally fetch Fetch policy bundle (or a single policy) logs Get policy (decision) logs diff --git a/cmd/policy/testdata/policy/decide-expected-usage.txt b/cmd/policy/testdata/policy/decide-expected-usage.txt index 49728afdc..20f3faf3a 100644 --- a/cmd/policy/testdata/policy/decide-expected-usage.txt +++ b/cmd/policy/testdata/policy/decide-expected-usage.txt @@ -6,7 +6,6 @@ Flags: --input string path to input file --metafile string decision metadata file --owner-id string the id of the policy's owner - --policy string path to rego policy file or directory containing policy files Global Flags: --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/diff-expected-usage.txt b/cmd/policy/testdata/policy/diff-expected-usage.txt new file mode 100644 index 000000000..c6317f9eb --- /dev/null +++ b/cmd/policy/testdata/policy/diff-expected-usage.txt @@ -0,0 +1,9 @@ +Usage: + policy diff [flags] + +Flags: + --context string policy context (default "config") + --owner-id string the id of the policy's owner + +Global Flags: + --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/policy/eval-expected-usage.txt b/cmd/policy/testdata/policy/eval-expected-usage.txt index afdf989dd..7a860d472 100644 --- a/cmd/policy/testdata/policy/eval-expected-usage.txt +++ b/cmd/policy/testdata/policy/eval-expected-usage.txt @@ -4,7 +4,6 @@ Usage: Flags: --input string path to input file --metafile string decision metadata file - --policy string path to rego policy file or directory containing policy files --query string policy decision query (default "data") Global Flags: diff --git a/cmd/policy/testdata/policy/push-expected-usage.txt b/cmd/policy/testdata/policy/push-expected-usage.txt index ead0331c4..f178b094a 100644 --- a/cmd/policy/testdata/policy/push-expected-usage.txt +++ b/cmd/policy/testdata/policy/push-expected-usage.txt @@ -6,6 +6,7 @@ policy push ./policy_bundle_dir_path --owner-id 462d67f8-b232-4da4-a7de-0c86dd66 Flags: --context string policy context (default "config") + --no-prompt removes the prompt --owner-id string the id of the policy's owner Global Flags: diff --git a/go.mod b/go.mod index e0f0d03d7..dfe27631e 100644 --- a/go.mod +++ b/go.mod @@ -42,6 +42,7 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.7 // indirect + github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect diff --git a/go.sum b/go.sum index 264928925..49bc8474c 100644 --- a/go.sum +++ b/go.sum @@ -611,8 +611,9 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO 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/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= +github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 h1:AgcIVYPa6XJnU3phs104wLj8l5GEththEw6+F79YsIY= +github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= From ec7d8d3bff677e0bdce7830fdefaee6dcab64da7 Mon Sep 17 00:00:00 2001 From: Sagar Gupta Date: Thu, 18 Aug 2022 15:40:13 -0400 Subject: [PATCH 39/48] added tests for policy diff subcommand (#768) --- cmd/policy/policy_test.go | 103 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index 80f6328d4..01666ab2f 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -115,6 +115,11 @@ func TestPushPolicyBundleNoPrompt(t *testing.T) { Args: []string{"push", "./testdata/directory_not_present", "--owner-id", "test-org"}, ExpectedErr: "failed to walk policy directory path: ", }, + { + Name: "fails if policy path points to a file instead of directory", + Args: []string{"push", "./testdata/test0/policy.rego", "--owner-id", "test-org"}, + ExpectedErr: "failed to walk policy directory path: policy path is not a directory", + }, { Name: "no policy files in given policy directory path", Args: []string{"push", "./testdata/test0/no-valid-policy-files", "--owner-id", "test-org", "--context", "custom"}, @@ -181,6 +186,99 @@ func TestPushPolicyBundleNoPrompt(t *testing.T) { } } +func TestDiffPolicyBundle(t *testing.T) { + testcases := []struct { + Name string + Args []string + ServerHandler http.HandlerFunc + ExpectedErr string + ExpectedStdErr string + ExpectedStdOut string + }{ + { + Name: "requires policy bundle directory path ", + Args: []string{"diff", "--owner-id", "ownerID"}, + ExpectedErr: "accepts 1 arg(s), received 0", + }, + { + Name: "requires owner-id", + Args: []string{"diff", "./testdata/test0/policy.rego"}, + ExpectedErr: "required flag(s) \"owner-id\" not set", + }, + { + Name: "fails for policy bundle directory path not found", + Args: []string{"diff", "./testdata/directory_not_present", "--owner-id", "test-org"}, + ExpectedErr: "failed to walk policy directory path: ", + }, + { + Name: "fails if policy path points to a file instead of directory", + Args: []string{"diff", "./testdata/test0/policy.rego", "--owner-id", "test-org"}, + ExpectedErr: "failed to walk policy directory path: policy path is not a directory", + }, + { + Name: "no policy files in given policy directory path", + Args: []string{"diff", "./testdata/test0/no-valid-policy-files", "--owner-id", "test-org", "--context", "custom"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + var body map[string]interface{} + assert.Equal(t, r.Method, "POST") + assert.Equal(t, r.URL.String(), "/api/v1/owner/test-org/context/custom/policy-bundle?dry=true") + assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.DeepEqual(t, body, map[string]interface{}{ + "policies": map[string]interface{}{}, + }) + w.WriteHeader(http.StatusCreated) + _, _ = w.Write([]byte("{}")) + }, + ExpectedStdOut: "{}\n", + }, + { + Name: "sends appropriate desired request", + Args: []string{"diff", "./testdata/test0", "--owner-id", "test-org", "--context", "custom"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + var body map[string]interface{} + assert.Equal(t, r.Method, "POST") + assert.Equal(t, r.URL.String(), "/api/v1/owner/test-org/context/custom/policy-bundle?dry=true") + assert.NilError(t, json.NewDecoder(r.Body).Decode(&body)) + assert.DeepEqual(t, body, map[string]interface{}{ + "policies": map[string]interface{}{ + filepath.Join("testdata", "test0", "policy.rego"): testdataContent(t, "test0/policy.rego"), + filepath.Join("testdata", "test0", "subdir", "meta-policy-subdir", "meta-policy.rego"): testdataContent(t, "test0/subdir/meta-policy-subdir/meta-policy.rego"), + }, + }) + + w.WriteHeader(http.StatusCreated) + _, _ = w.Write([]byte("{}")) + }, + ExpectedStdOut: "{}\n", + }, + } + + for _, tc := range testcases { + t.Run(tc.Name, func(t *testing.T) { + if tc.ServerHandler == nil { + tc.ServerHandler = func(w http.ResponseWriter, r *http.Request) {} + } + + svr := httptest.NewServer(tc.ServerHandler) + defer svr.Close() + + cmd, stdout, stderr := makeCMD() + + cmd.SetArgs(append(tc.Args, "--policy-base-url", svr.URL)) + + err := cmd.Execute() + if tc.ExpectedErr != "" { + assert.ErrorContains(t, err, tc.ExpectedErr) + return + } + + assert.NilError(t, err) + assert.Equal(t, stdout.String(), tc.ExpectedStdOut) + assert.Equal(t, stderr.String(), tc.ExpectedStdErr) + }) + } +} + func TestFetchPolicyBundle(t *testing.T) { testcases := []struct { Name string @@ -527,6 +625,11 @@ func TestMakeDecisionCommand(t *testing.T) { Args: []string{"decide", "--input", "./testdata/test1/test.yml"}, ExpectedErr: "either policy-path or --owner-id is required", }, + { + Name: "fails if both local-policy and owner-id are provided", + Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/test1/test.yml", "--owner-id", "test-owner"}, + ExpectedErr: "either policy-path or --owner-id is required", + }, { Name: "fails for input file not found", Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/no_such_file.yml"}, From 168582f34e3efa133c0eab7bd51df2b6c1857c6f Mon Sep 17 00:00:00 2001 From: Sagar Gupta Date: Mon, 22 Aug 2022 09:31:42 -0400 Subject: [PATCH 40/48] Added examples for policy commands (#769) * added examples for policy commands * updated example --- cmd/policy/policy.go | 29 ++++++++++--------- cmd/policy/policy_test.go | 4 +-- cmd/policy/testdata/policy-expected-usage.txt | 2 +- .../testdata/policy/decide-expected-usage.txt | 5 +++- .../testdata/policy/diff-expected-usage.txt | 5 +++- .../testdata/policy/eval-expected-usage.txt | 5 +++- .../testdata/policy/fetch-expected-usage.txt | 4 +-- .../testdata/policy/logs-expected-usage.txt | 2 +- .../testdata/policy/push-expected-usage.txt | 4 +-- 9 files changed, 36 insertions(+), 24 deletions(-) diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index 864671a80..c63b7ebc2 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -41,7 +41,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com cmd := &cobra.Command{ Short: "push policy bundle", - Use: "push", + Use: "push ", RunE: func(cmd *cobra.Command, args []string) error { bundle, err := loadBundleFromFS(args[0]) if err != nil { @@ -83,7 +83,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com return nil }, Args: cobra.ExactArgs(1), - Example: `policy push ./policy_bundle_dir_path --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --context config`, + Example: `policy push ./policies --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f`, } cmd.Flags().StringVar(&context, "context", "config", "policy context") @@ -100,7 +100,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com var ownerID, context string cmd := &cobra.Command{ Short: "Get diff between local and remote policy bundles", - Use: "diff", + Use: "diff ", RunE: func(cmd *cobra.Command, args []string) error { bundle, err := loadBundleFromFS(args[0]) if err != nil { @@ -117,7 +117,8 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com return prettyJSONEncoder(cmd.OutOrStdout()).Encode(diff) }, - Args: cobra.ExactArgs(1), + Args: cobra.ExactArgs(1), + Example: `policy diff ./policies --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f`, } cmd.Flags().StringVar(&context, "context", "config", "policy context") cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") @@ -132,7 +133,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com var ownerID, context, policyName string cmd := &cobra.Command{ Short: "Fetch policy bundle (or a single policy)", - Use: "fetch ", + Use: "fetch [policy_name]", RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 1 { policyName = args[0] @@ -149,7 +150,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com return nil }, Args: cobra.MaximumNArgs(1), - Example: `policy fetch policy_name --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 --context config`, + Example: `policy fetch --owner-id 516425b2-e369-421b-838d-920e1f51b0f5`, } cmd.Flags().StringVar(&context, "context", "config", "policy context") @@ -166,7 +167,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com var request policy.DecisionQueryRequest cmd := &cobra.Command{ - Short: "Get policy (decision) logs", + Short: "Get policy decision logs", Use: "logs", RunE: func(cmd *cobra.Command, _ []string) (err error) { if cmd.Flag("after").Changed { @@ -234,7 +235,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com return nil }, Args: cobra.ExactArgs(0), - Example: `policy logs --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --after 2022/03/14 --out output.json`, + Example: `policy logs --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --after 2022/03/14 --out output.json`, } cmd.Flags().StringVar(&request.Status, "status", "", "filter decision logs based on their status") @@ -264,13 +265,13 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com cmd := &cobra.Command{ Short: "make a decision", - Use: "decide", + Use: "decide [policy_file_or_dir_path]", RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 1 { policyPath = args[0] } if (policyPath == "" && ownerID == "") || (policyPath != "" && ownerID != "") { - return fmt.Errorf("either policy-path or --owner-id is required") + return fmt.Errorf("either [policy_file_or_dir_path] or --owner-id is required") } input, err := os.ReadFile(inputPath) @@ -307,7 +308,8 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com return nil }, - Args: cobra.MaximumNArgs(1), + Args: cobra.MaximumNArgs(1), + Example: `policy decide ./policies --input ./.circleci/config.yml`, } cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") @@ -326,7 +328,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com var inputPath, metaFile, query string cmd := &cobra.Command{ Short: "perform raw opa evaluation locally", - Use: "eval", + Use: "eval ", RunE: func(cmd *cobra.Command, args []string) error { policyPath := args[0] input, err := os.ReadFile(inputPath) @@ -356,7 +358,8 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com return nil }, - Args: cobra.ExactArgs(1), + Args: cobra.ExactArgs(1), + Example: `policy eval ./policies --input ./.circleci/config.yml`, } cmd.Flags().StringVar(&inputPath, "input", "", "path to input file") diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index 01666ab2f..0cc2cefb4 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -623,12 +623,12 @@ func TestMakeDecisionCommand(t *testing.T) { { Name: "fails if neither local-policy nor owner-id is provided", Args: []string{"decide", "--input", "./testdata/test1/test.yml"}, - ExpectedErr: "either policy-path or --owner-id is required", + ExpectedErr: "either [policy_file_or_dir_path] or --owner-id is required", }, { Name: "fails if both local-policy and owner-id are provided", Args: []string{"decide", "./testdata/test0/policy.rego", "--input", "./testdata/test1/test.yml", "--owner-id", "test-owner"}, - ExpectedErr: "either policy-path or --owner-id is required", + ExpectedErr: "either [policy_file_or_dir_path] or --owner-id is required", }, { Name: "fails for input file not found", diff --git a/cmd/policy/testdata/policy-expected-usage.txt b/cmd/policy/testdata/policy-expected-usage.txt index 7d1636b9e..e97a911b8 100644 --- a/cmd/policy/testdata/policy-expected-usage.txt +++ b/cmd/policy/testdata/policy-expected-usage.txt @@ -6,7 +6,7 @@ Available Commands: diff Get diff between local and remote policy bundles eval perform raw opa evaluation locally fetch Fetch policy bundle (or a single policy) - logs Get policy (decision) logs + logs Get policy decision logs push push policy bundle Flags: diff --git a/cmd/policy/testdata/policy/decide-expected-usage.txt b/cmd/policy/testdata/policy/decide-expected-usage.txt index 20f3faf3a..734c852e7 100644 --- a/cmd/policy/testdata/policy/decide-expected-usage.txt +++ b/cmd/policy/testdata/policy/decide-expected-usage.txt @@ -1,5 +1,8 @@ Usage: - policy decide [flags] + policy decide [policy_file_or_dir_path] [flags] + +Examples: +policy decide ./policies --input ./.circleci/config.yml Flags: --context string policy context for decision (default "config") diff --git a/cmd/policy/testdata/policy/diff-expected-usage.txt b/cmd/policy/testdata/policy/diff-expected-usage.txt index c6317f9eb..ae69485ed 100644 --- a/cmd/policy/testdata/policy/diff-expected-usage.txt +++ b/cmd/policy/testdata/policy/diff-expected-usage.txt @@ -1,5 +1,8 @@ Usage: - policy diff [flags] + policy diff [flags] + +Examples: +policy diff ./policies --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f Flags: --context string policy context (default "config") diff --git a/cmd/policy/testdata/policy/eval-expected-usage.txt b/cmd/policy/testdata/policy/eval-expected-usage.txt index 7a860d472..68624f672 100644 --- a/cmd/policy/testdata/policy/eval-expected-usage.txt +++ b/cmd/policy/testdata/policy/eval-expected-usage.txt @@ -1,5 +1,8 @@ Usage: - policy eval [flags] + policy eval [flags] + +Examples: +policy eval ./policies --input ./.circleci/config.yml Flags: --input string path to input file diff --git a/cmd/policy/testdata/policy/fetch-expected-usage.txt b/cmd/policy/testdata/policy/fetch-expected-usage.txt index 279278391..189051768 100644 --- a/cmd/policy/testdata/policy/fetch-expected-usage.txt +++ b/cmd/policy/testdata/policy/fetch-expected-usage.txt @@ -1,8 +1,8 @@ Usage: - policy fetch [flags] + policy fetch [policy_name] [flags] Examples: -policy fetch policy_name --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 --context config +policy fetch --owner-id 516425b2-e369-421b-838d-920e1f51b0f5 Flags: --context string policy context (default "config") diff --git a/cmd/policy/testdata/policy/logs-expected-usage.txt b/cmd/policy/testdata/policy/logs-expected-usage.txt index 2bfdc4274..55fda9bf6 100644 --- a/cmd/policy/testdata/policy/logs-expected-usage.txt +++ b/cmd/policy/testdata/policy/logs-expected-usage.txt @@ -2,7 +2,7 @@ Usage: policy logs [flags] Examples: -policy logs --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --after 2022/03/14 --out output.json +policy logs --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --after 2022/03/14 --out output.json Flags: --after string filter decision logs triggered AFTER this datetime diff --git a/cmd/policy/testdata/policy/push-expected-usage.txt b/cmd/policy/testdata/policy/push-expected-usage.txt index f178b094a..940461be3 100644 --- a/cmd/policy/testdata/policy/push-expected-usage.txt +++ b/cmd/policy/testdata/policy/push-expected-usage.txt @@ -1,8 +1,8 @@ Usage: - policy push [flags] + policy push [flags] Examples: -policy push ./policy_bundle_dir_path --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --context config +policy push ./policies --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f Flags: --context string policy context (default "config") From 0b26028c1a584b2ed1d227243244ab1ea346ee20 Mon Sep 17 00:00:00 2001 From: corinnesollows Date: Tue, 23 Aug 2022 16:10:50 -0300 Subject: [PATCH 41/48] Ext 587 codecov (#772) * Removing Codecov from CLI --- .circleci/config.yml | 4 ---- README.md | 1 - md_docs/md_docs.go | 1 - 3 files changed, 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 820cfd7d3..ab76af1fb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,6 @@ version: 2.1 orbs: - codecov: codecov/codecov@1.1.3 shellcheck: circleci/shellcheck@1.2.0 windows: circleci/windows@2.2.0 @@ -23,7 +22,6 @@ commands: force-http-1: steps: - run: - # Uploading to codecov has been failing due to HTTP 2.0 issues. # https://app.circleci.com/jobs/github/CircleCI-Public/circleci-cli/6480 # curl: (92) HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1) # The issue seems to be on the server-side, so force HTTP 1.1 @@ -155,8 +153,6 @@ jobs: - store_artifacts: path: ./coverage.txt destination: coverage.txt - - codecov/upload: - file: coverage.txt docs: executor: go diff --git a/README.md b/README.md index 4fde9ef6f..f5411624a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ This is CircleCI's command-line application. [![CircleCI](https://circleci.com/gh/CircleCI-Public/circleci-cli.svg?style=shield)](https://circleci.com/gh/CircleCI-Public/circleci-cli) [![GitHub release](https://img.shields.io/github/tag/CircleCI-Public/circleci-cli.svg?label=latest)](https://github.com/CircleCI-Public/circleci-cli/releases) [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/CircleCI-Public/circleci-cli) -[![Codecov](https://codecov.io/gh/CircleCI-Public/circleci-cli/branch/master/graph/badge.svg)](https://codecov.io/gh/CircleCI-Public/circleci-cli) [![License](https://img.shields.io/badge/license-MIT-red.svg)](./LICENSE) ## Getting Started diff --git a/md_docs/md_docs.go b/md_docs/md_docs.go index f60d1180e..9106511d7 100644 --- a/md_docs/md_docs.go +++ b/md_docs/md_docs.go @@ -22,7 +22,6 @@ var introHeader = ` [![CircleCI](https://circleci.com/gh/CircleCI-Public/circleci-cli.svg?style=svg)](https://circleci.com/gh/CircleCI-Public/circleci-cli) [![GitHub release](https://img.shields.io/github/tag/CircleCI-Public/circleci-cli.svg?label=latest)](https://github.com/CircleCI-Public/circleci-cli/releases) [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/CircleCI-Public/circleci-cli) -[![Codecov](https://codecov.io/gh/CircleCI-Public/circleci-cli/branch/master/graph/badge.svg)](https://codecov.io/gh/CircleCI-Public/circleci-cli) [![License](https://img.shields.io/badge/license-MIT-red.svg)](./LICENSE) ` From 15e1fd4f85432c7fe7bf23e4eb5630a761f79dda Mon Sep 17 00:00:00 2001 From: Kyle T <33272306+KyleTryon@users.noreply.github.com> Date: Wed, 24 Aug 2022 14:39:01 -0400 Subject: [PATCH 42/48] fix clone url (#425) URL was previously not suitable for cloning. This change will allow the user to copy and paste the command. PR is switching to use the SSH for cloning Co-authored-by: Marc O'Morain Co-authored-by: corinnesollows --- HACKING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HACKING.md b/HACKING.md index 6319cf2c2..717d4729a 100644 --- a/HACKING.md +++ b/HACKING.md @@ -24,7 +24,7 @@ You should already have [installed Go](https://golang.org/doc/install). Clone the repo. ``` -$ git clone github.com/CircleCI-Public/circleci-cli +$ git clone git@github.com:CircleCI-Public/circleci-cli.git $ cd circleci-cli ``` From 991bd2aae331cf5668175acc77c7c8fb8e49accb Mon Sep 17 00:00:00 2001 From: Eric Ribeiro Date: Mon, 29 Aug 2022 13:24:38 -0300 Subject: [PATCH 43/48] fix: orb first commit message --- cmd/orb.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/orb.go b/cmd/orb.go index aa832e0f0..16ac6f160 100644 --- a/cmd/orb.go +++ b/cmd/orb.go @@ -1383,13 +1383,13 @@ func initOrb(opts orbOptions) error { } if version.PackageManager() != "snap" { - _, err = w.Commit("[semver:skip] Initial commit.", &git.CommitOptions{}) + _, err = w.Commit("feat: Initial commit.", &git.CommitOptions{}) if err != nil { return err } } else { fmt.Println("We detected you installed the CLI via snap\nThe commit generated will not match your actual git username or email due to sandboxing.") - _, err = w.Commit("[semver:skip] Initial commit.", &git.CommitOptions{ + _, err = w.Commit("feat: Initial commit.", &git.CommitOptions{ Author: &object.Signature{ Name: "CircleCI", Email: "community-partner@circleci.com", From 56e791be647558532901ce7c566bcdf47964daed Mon Sep 17 00:00:00 2001 From: Sagar Gupta Date: Fri, 9 Sep 2022 11:40:50 -0400 Subject: [PATCH 44/48] [SECENG-821] Get policy decision log by decision id (#776) --- api/policy/policy.go | 34 +++++ api/policy/policy_test.go | 138 ++++++++++++++++++ cmd/policy/policy.go | 83 +++++++---- cmd/policy/policy_test.go | 36 +++++ cmd/policy/testdata/policy-expected-usage.txt | 2 +- .../testdata/policy/logs-expected-usage.txt | 3 +- 6 files changed, 265 insertions(+), 31 deletions(-) diff --git a/api/policy/policy.go b/api/policy/policy.go index cbb13ce63..662172fed 100644 --- a/api/policy/policy.go +++ b/api/policy/policy.go @@ -173,6 +173,40 @@ func (c Client) GetDecisionLogs(ownerID string, context string, request Decision return body, nil } +// GetDecisionLog calls the GET decision query API of policy-service for a DecisionID. +// It also accepts a policyBundle bool param; If set to true will return only the policy bundle corresponding to that decision log. +func (c Client) GetDecisionLog(ownerID string, context string, decisionID string, policyBundle bool) (interface{}, error) { + path := fmt.Sprintf("%s/api/v1/owner/%s/context/%s/decision/%s", c.serverUrl, ownerID, context, decisionID) + if policyBundle { + path += "/policy-bundle" + } + req, err := http.NewRequest("GET", path, nil) + if err != nil { + return nil, fmt.Errorf("failed to construct request: %v", err) + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + var payload httpError + if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { + return nil, fmt.Errorf("unexpected status-code: %d", resp.StatusCode) + } + return nil, fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, payload.Error) + } + + var body interface{} + if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { + return nil, fmt.Errorf("failed to decode response body: %v", err) + } + + return body, nil +} + // DecisionRequest represents a request to Policy-Service to evaluate a given input against an organization's policies. // The context determines which policies to apply. type DecisionRequest struct { diff --git a/api/policy/policy_test.go b/api/policy/policy_test.go index 9128021be..9113b5490 100644 --- a/api/policy/policy_test.go +++ b/api/policy/policy_test.go @@ -412,6 +412,144 @@ func TestClientGetDecisionLogs(t *testing.T) { }) } +func TestClientGetDecisionLog(t *testing.T) { + t.Run("expected request with policyBundle=false", func(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + assert.Equal(t, r.Header.Get("accept"), "application/json") + assert.Equal(t, r.Header.Get("content-type"), "application/json") + assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/context/config/decision/decisionID") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerId/context/config/decision/decisionID") + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("[]")) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + _, err := client.GetDecisionLog("ownerId", "config", "decisionID", false) + assert.NilError(t, err) + }) + + t.Run("expected request without policyBundle=true", func(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + assert.Equal(t, r.Header.Get("accept"), "application/json") + assert.Equal(t, r.Header.Get("content-type"), "application/json") + assert.Equal(t, r.Header.Get("user-agent"), version.UserAgent()) + assert.Equal(t, r.Header.Get("circle-token"), "testtoken") + + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.Path, "/api/v1/owner/ownerId/context/config/decision/decisionID/policy-bundle") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerId/context/config/decision/decisionID/policy-bundle") + + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("[]")) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + _, err := client.GetDecisionLog("ownerId", "config", "decisionID", true) + assert.NilError(t, err) + }) + + t.Run("Get Decision Log - Bad Request", func(t *testing.T) { + expectedResponse := `{"error": "some error"}` + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + log, err := client.GetDecisionLog("ownerId", "config", "decisionID", false) + assert.Error(t, err, "unexpected status-code: 400 - some error") + assert.Equal(t, log, nil) + }) + + t.Run("Get Decision Log - Forbidden", func(t *testing.T) { + expectedResponse := `{"error": "Forbidden"}` + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusForbidden) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + log, err := client.GetDecisionLog("ownerId", "config", "decisionID", false) + assert.Error(t, err, "unexpected status-code: 403 - Forbidden") + assert.Equal(t, log, nil) + }) + + t.Run("Get Decision Log - decision log not found", func(t *testing.T) { + expectedResponse := `{"error": "decision log not found"}` + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + log, err := client.GetDecisionLog("ownerId", "config", "decisionID", false) + assert.Error(t, err, "unexpected status-code: 404 - decision log not found") + assert.Equal(t, log, nil) + }) + + t.Run("Get Decision Log - successfully finds decision log", func(t *testing.T) { + expectedResponse := `{ + "id": "fdc5311d-6d4d-480c-8ba8-b86b215ee86a", + "created_at": "2022-08-11T09:20:40.674594-04:00", + "decision": { + "enabled_rules": [ + "branch_is_main" + ], + "status": "PASS" + }, + "metadata": {}, + "policies": { + "policy_name1": "8c69adc542bcfd6e65f5d5a2b6a4e3764480db2253cd075d0954e64a1f827a9c695c916d5a49302991df781447b3951410824dce8a8282d11ed56302272cf6fb", + "policy_name2": "3124131001ec20b4b524260ababa6411190a1bc9c5ac3219ccc2d21109fc5faf4bb9f7bbe38f3f798d9c232d68564390e0ca560877711f3f2ff7f89e10eef685" + }, + "time_taken_ms": 4 + }` + var expectedResponseValue interface{} + assert.NilError(t, json.Unmarshal([]byte(expectedResponse), &expectedResponseValue)) + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, err := w.Write([]byte(expectedResponse)) + assert.NilError(t, err) + })) + defer svr.Close() + + config := &settings.Config{Token: "testtoken", HTTPClient: &http.Client{}} + client := NewClient(svr.URL, config) + + log, err := client.GetDecisionLog("ownerId", "config", "fdc5311d-6d4d-480c-8ba8-b86b215ee86a", false) + assert.DeepEqual(t, log, expectedResponseValue) + assert.NilError(t, err) + }) +} + func TestMakeDecision(t *testing.T) { testcases := []struct { Name string diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index c63b7ebc2..94ddfb909 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -163,13 +163,23 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com }() logs := func() *cobra.Command { - var after, before, outputFile, ownerID, context string + var after, before, outputFile, ownerID, context, decisionID string + var policyBundle bool var request policy.DecisionQueryRequest cmd := &cobra.Command{ - Short: "Get policy decision logs", - Use: "logs", - RunE: func(cmd *cobra.Command, _ []string) (err error) { + Short: "Get policy decision logs / Get decision log (or policy bundle) by decision ID", + Use: "logs [decision_id]", + RunE: func(cmd *cobra.Command, args []string) (err error) { + if len(args) == 1 { + decisionID = args[0] + } + if decisionID != "" && (after != "" || before != "" || request.Status != "" || request.Offset != 0 || request.Branch != "" || request.ProjectID != "") { + return fmt.Errorf("filters are not accepted when decision_id is provided") + } + if policyBundle && decisionID == "" { + return fmt.Errorf("decision_id is required when --policy-bundle flag is used") + } if cmd.Flag("after").Changed { request.After = new(time.Time) *request.After, err = dateparse.ParseStrict(after) @@ -200,41 +210,26 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com }() } - allLogs := make([]interface{}, 0) - - spr := spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(cmd.ErrOrStderr())) - spr.Suffix = " Fetching Policy Decision Logs..." - - spr.PostUpdate = func(s *spinner.Spinner) { - s.Suffix = fmt.Sprintf(" Fetching Policy Decision Logs... downloaded %d logs...", len(allLogs)) - } - - spr.Start() - defer spr.Stop() - + var output interface{} client := policy.NewClient(*policyBaseURL, config) - for { - logsBatch, err := client.GetDecisionLogs(ownerID, context, request) - if err != nil { - return fmt.Errorf("failed to get policy decision logs: %v", err) - } - - if len(logsBatch) == 0 { - break - } + if decisionID != "" { + output, err = client.GetDecisionLog(ownerID, context, decisionID, policyBundle) + } else { + output, err = getAllDecisionLogs(client, ownerID, context, request, cmd.ErrOrStderr()) + } - allLogs = append(allLogs, logsBatch...) - request.Offset = len(allLogs) + if err != nil { + return fmt.Errorf("failed to get policy decision logs: %v", err) } - if err := prettyJSONEncoder(dst).Encode(allLogs); err != nil { + if err := prettyJSONEncoder(dst).Encode(output); err != nil { return fmt.Errorf("failed to output policy decision logs in json format: %v", err) } return nil }, - Args: cobra.ExactArgs(0), + Args: cobra.MaximumNArgs(1), Example: `policy logs --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --after 2022/03/14 --out output.json`, } @@ -244,6 +239,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com cmd.Flags().StringVar(&request.Branch, "branch", "", "filter decision logs based on branch name") cmd.Flags().StringVar(&request.ProjectID, "project-id", "", "filter decision logs based on project-id") cmd.Flags().StringVar(&outputFile, "out", "", "specify output file name ") + cmd.Flags().BoolVar(&policyBundle, "policy-bundle", false, "get only the policy bundle for given decisionID") cmd.Flags().StringVar(&context, "context", "config", "policy context") cmd.Flags().StringVar(&ownerID, "owner-id", "", "the id of the policy's owner") if err := cmd.MarkFlagRequired("owner-id"); err != nil { @@ -464,6 +460,35 @@ func loadBundleFromFS(root string) (map[string]string, error) { return bundle, err } +func getAllDecisionLogs(client *policy.Client, ownerID string, context string, request policy.DecisionQueryRequest, spinnerOutputDst io.Writer) (interface{}, error) { + allLogs := make([]interface{}, 0) + + spr := spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(spinnerOutputDst)) + spr.Suffix = " Fetching Policy Decision Logs..." + + spr.PostUpdate = func(s *spinner.Spinner) { + s.Suffix = fmt.Sprintf(" Fetching Policy Decision Logs... downloaded %d logs...", len(allLogs)) + } + + spr.Start() + defer spr.Stop() + + for { + logsBatch, err := client.GetDecisionLogs(ownerID, context, request) + if err != nil { + return nil, err + } + + if len(logsBatch) == 0 { + break + } + + allLogs = append(allLogs, logsBatch...) + request.Offset = len(allLogs) + } + return allLogs, nil +} + func Confirm(w io.Writer, r io.Reader, question string) bool { fmt.Fprint(w, question+" ") var answer string diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index 0cc2cefb4..8d32dfaee 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -415,6 +415,16 @@ func TestGetDecisionLogs(t *testing.T) { Args: []string{"logs", "--owner-id", "ownerID", "--before", "1/2/2022"}, ExpectedErr: `error in parsing --before value: This date has ambiguous mm/dd vs dd/mm type format`, }, + { + Name: "gives error when a filter is provided when decisionID is also provided", + Args: []string{"logs", "decisionID", "--owner-id", "ownerID", "--branch", "main"}, + ExpectedErr: `filters are not accepted when decision_id is provided`, + }, + { + Name: "gives error when --policy-bundle flag is used but decisionID is not provided", + Args: []string{"logs", "--owner-id", "ownerID", "--policy-bundle"}, + ExpectedErr: `decision_id is required when --policy-bundle flag is used`, + }, { Name: "no filter is set", Args: []string{"logs", "--owner-id", "ownerID"}, @@ -512,6 +522,32 @@ func TestGetDecisionLogs(t *testing.T) { ] `, }, + { + Name: "successfully gets a decision log for given decision ID", + Args: []string{"logs", "--owner-id", "ownerID", "decisionID"}, + ServerHandler: func() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/config/decision/decisionID") + _, err := w.Write([]byte("{}")) + assert.NilError(t, err) + } + }(), + ExpectedOutput: "{}\n", + }, + { + Name: "successfully gets policy-bundle for given decision ID", + Args: []string{"logs", "--owner-id", "ownerID", "decisionID", "--policy-bundle"}, + ServerHandler: func() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "GET") + assert.Equal(t, r.URL.String(), "/api/v1/owner/ownerID/context/config/decision/decisionID/policy-bundle") + _, err := w.Write([]byte("{}")) + assert.NilError(t, err) + } + }(), + ExpectedOutput: "{}\n", + }, } for _, tc := range testcases { diff --git a/cmd/policy/testdata/policy-expected-usage.txt b/cmd/policy/testdata/policy-expected-usage.txt index e97a911b8..bbab15737 100644 --- a/cmd/policy/testdata/policy-expected-usage.txt +++ b/cmd/policy/testdata/policy-expected-usage.txt @@ -6,7 +6,7 @@ Available Commands: diff Get diff between local and remote policy bundles eval perform raw opa evaluation locally fetch Fetch policy bundle (or a single policy) - logs Get policy decision logs + logs Get policy decision logs / Get decision log (or policy bundle) by decision ID push push policy bundle Flags: diff --git a/cmd/policy/testdata/policy/logs-expected-usage.txt b/cmd/policy/testdata/policy/logs-expected-usage.txt index 55fda9bf6..3cd586b1f 100644 --- a/cmd/policy/testdata/policy/logs-expected-usage.txt +++ b/cmd/policy/testdata/policy/logs-expected-usage.txt @@ -1,5 +1,5 @@ Usage: - policy logs [flags] + policy logs [decision_id] [flags] Examples: policy logs --owner-id 462d67f8-b232-4da4-a7de-0c86dd667d3f --after 2022/03/14 --out output.json @@ -11,6 +11,7 @@ Flags: --context string policy context (default "config") --out string specify output file name --owner-id string the id of the policy's owner + --policy-bundle get only the policy bundle for given decisionID --project-id string filter decision logs based on project-id --status string filter decision logs based on their status From 45afa93d0ae256aded8a2c3bbd3daae8adfa8842 Mon Sep 17 00:00:00 2001 From: Sagar Gupta Date: Mon, 12 Sep 2022 13:55:01 -0400 Subject: [PATCH 45/48] return non-zero status codes for some cases (#778) --- api/policy/policy.go | 8 ++- api/policy/policy_test.go | 3 +- cmd/policy/policy.go | 36 ++++++++---- cmd/policy/policy_test.go | 58 +++++++++++++++++++ .../testdata/policy/decide-expected-usage.txt | 1 + .../testdata/test2/hard_fail_policy.rego | 6 ++ 6 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 cmd/policy/testdata/test2/hard_fail_policy.rego diff --git a/api/policy/policy.go b/api/policy/policy.go index 662172fed..7f7bf99f1 100644 --- a/api/policy/policy.go +++ b/api/policy/policy.go @@ -10,6 +10,8 @@ import ( "strings" "time" + "github.com/CircleCI-Public/circle-policy-agent/cpa" + "github.com/CircleCI-Public/circleci-cli/api/header" "github.com/CircleCI-Public/circleci-cli/settings" "github.com/CircleCI-Public/circleci-cli/version" @@ -215,7 +217,7 @@ type DecisionRequest struct { } // MakeDecision sends a requests to Policy-Service public decision endpoint and returns the decision response -func (c Client) MakeDecision(ownerID string, context string, req DecisionRequest) (interface{}, error) { +func (c Client) MakeDecision(ownerID string, context string, req DecisionRequest) (*cpa.Decision, error) { payload, err := json.Marshal(req) if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) @@ -244,12 +246,12 @@ func (c Client) MakeDecision(ownerID string, context string, req DecisionRequest return nil, fmt.Errorf("unexpected status-code: %d - %s", resp.StatusCode, payload.Error) } - var body interface{} + var body cpa.Decision if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { return nil, fmt.Errorf("failed to decode response body: %w", err) } - return body, nil + return &body, nil } // NewClient returns a new policy client that will use the provided settings.Config to automatically inject appropriate diff --git a/api/policy/policy_test.go b/api/policy/policy_test.go index 9113b5490..d1008a163 100644 --- a/api/policy/policy_test.go +++ b/api/policy/policy_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/CircleCI-Public/circle-policy-agent/cpa" "gotest.tools/v3/assert" "github.com/CircleCI-Public/circleci-cli/settings" @@ -579,7 +580,7 @@ func TestMakeDecision(t *testing.T) { _ = json.NewEncoder(w).Encode(map[string]string{"status": "PASS"}) }, - ExpectedDecision: map[string]interface{}{"status": "PASS"}, + ExpectedDecision: &cpa.Decision{Status: cpa.StatusPass}, }, { Name: "unexpected status code", diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index 94ddfb909..a4972a121 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -63,7 +63,11 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com _ = prettyJSONEncoder(cmd.ErrOrStderr()).Encode(diff) _, _ = io.WriteString(cmd.ErrOrStderr(), "\n") - if !Confirm(cmd.ErrOrStderr(), cmd.InOrStdin(), "Do you wish to continue? (y/N)") { + proceed, err := Confirm(cmd.ErrOrStderr(), cmd.InOrStdin(), "Do you wish to continue? (y/N)") + if err != nil { + return err + } + if !proceed { return nil } _, _ = io.WriteString(cmd.ErrOrStderr(), "\n") @@ -210,14 +214,14 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com }() } - var output interface{} client := policy.NewClient(*policyBaseURL, config) - if decisionID != "" { - output, err = client.GetDecisionLog(ownerID, context, decisionID, policyBundle) - } else { - output, err = getAllDecisionLogs(client, ownerID, context, request, cmd.ErrOrStderr()) - } + output, err := func() (interface{}, error) { + if decisionID != "" { + return client.GetDecisionLog(ownerID, context, decisionID, policyBundle) + } + return getAllDecisionLogs(client, ownerID, context, request, cmd.ErrOrStderr()) + }() if err != nil { return fmt.Errorf("failed to get policy decision logs: %v", err) @@ -256,6 +260,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com metaFile string ownerID string context string + strict bool request policy.DecisionRequest ) @@ -286,7 +291,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com } } - decision, err := func() (interface{}, error) { + decision, err := func() (*cpa.Decision, error) { if policyPath != "" { return getPolicyDecisionLocally(policyPath, input, metadata) } @@ -298,6 +303,10 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com return fmt.Errorf("failed to make decision: %w", err) } + if strict && decision.Status == cpa.StatusHardFail { + return fmt.Errorf("policy decision status: HARD_FAIL") + } + if err := prettyJSONEncoder(cmd.OutOrStdout()).Encode(decision); err != nil { return fmt.Errorf("failed to encode decision: %w", err) } @@ -312,6 +321,7 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com cmd.Flags().StringVar(&context, "context", "config", "policy context for decision") cmd.Flags().StringVar(&inputPath, "input", "", "path to input file") cmd.Flags().StringVar(&metaFile, "metafile", "", "decision metadata file") + cmd.Flags().BoolVar(&strict, "strict", false, "return non-zero status code for decision resulting in HARD_FAIL") if err := cmd.MarkFlagRequired("input"); err != nil { panic(err) @@ -489,12 +499,14 @@ func getAllDecisionLogs(client *policy.Client, ownerID string, context string, r return allLogs, nil } -func Confirm(w io.Writer, r io.Reader, question string) bool { +func Confirm(w io.Writer, r io.Reader, question string) (bool, error) { fmt.Fprint(w, question+" ") var answer string - _, _ = fmt.Fscanln(r, &answer) - + n, err := fmt.Fscanln(r, &answer) + if err != nil || n == 0 { + return false, fmt.Errorf("error in input") + } answer = strings.ToLower(answer) - return answer == "y" || answer == "yes" + return answer == "y" || answer == "yes", nil } diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go index 8d32dfaee..8ca131660 100644 --- a/cmd/policy/policy_test.go +++ b/cmd/policy/policy_test.go @@ -606,6 +606,42 @@ func TestMakeDecisionCommand(t *testing.T) { }, ExpectedOutput: "{\n \"status\": \"PASS\"\n}\n", }, + { + Name: "passes when decision status = HARD_FAIL AND --strict is OFF", + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "POST") + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/context/config/decision") + + var payload map[string]interface{} + assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) + + assert.DeepEqual(t, payload, map[string]interface{}{ + "input": "test: config\n", + }) + + _, _ = io.WriteString(w, `{"status":"HARD_FAIL"}`) + }, + ExpectedOutput: "{\n \"status\": \"HARD_FAIL\"\n}\n", + }, + { + Name: "fails when decision status = HARD_FAIL AND --strict is ON", + Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--strict"}, + ServerHandler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, "POST") + assert.Equal(t, r.URL.Path, "/api/v1/owner/test-owner/context/config/decision") + + var payload map[string]interface{} + assert.NilError(t, json.NewDecoder(r.Body).Decode(&payload)) + + assert.DeepEqual(t, payload, map[string]interface{}{ + "input": "test: config\n", + }) + + _, _ = io.WriteString(w, `{"status":"HARD_FAIL"}`) + }, + ExpectedErr: "policy decision status: HARD_FAIL", + }, { Name: "sends expected request with context", Args: []string{"decide", "--owner-id", "test-owner", "--input", "./testdata/test1/test.yml", "--context", "custom"}, @@ -687,6 +723,28 @@ func TestMakeDecisionCommand(t *testing.T) { } `, }, + { + Name: "successfully performs decision for policy FILE provided locally, passes when decision = HARD_FAIL and strict = OFF", + Args: []string{"decide", "./testdata/test2/hard_fail_policy.rego", "--input", "./testdata/test0/config.yml"}, + ExpectedOutput: `{ + "status": "HARD_FAIL", + "enabled_rules": [ + "always_hard_fails" + ], + "hard_failures": [ + { + "rule": "always_hard_fails", + "reason": "0 is not equals 1" + } + ] +} +`, + }, + { + Name: "successfully performs decision for policy FILE provided locally, fails when decision = HARD_FAIL and strict = ON", + Args: []string{"decide", "./testdata/test2/hard_fail_policy.rego", "--input", "./testdata/test0/config.yml", "--strict"}, + ExpectedErr: "policy decision status: HARD_FAIL", + }, { Name: "successfully performs decision with metadata for policy FILE provided locally", Args: []string{ diff --git a/cmd/policy/testdata/policy/decide-expected-usage.txt b/cmd/policy/testdata/policy/decide-expected-usage.txt index 734c852e7..cd6f3bf3e 100644 --- a/cmd/policy/testdata/policy/decide-expected-usage.txt +++ b/cmd/policy/testdata/policy/decide-expected-usage.txt @@ -9,6 +9,7 @@ Flags: --input string path to input file --metafile string decision metadata file --owner-id string the id of the policy's owner + --strict return non-zero status code for decision resulting in HARD_FAIL Global Flags: --policy-base-url string base url for policy api (default "https://internal.circleci.com") diff --git a/cmd/policy/testdata/test2/hard_fail_policy.rego b/cmd/policy/testdata/test2/hard_fail_policy.rego new file mode 100644 index 000000000..16c730d2e --- /dev/null +++ b/cmd/policy/testdata/test2/hard_fail_policy.rego @@ -0,0 +1,6 @@ +package org + +policy_name["hard_fail_test"] +enable_rule["always_hard_fails"] +hard_fail["always_hard_fails"] +always_hard_fails = "0 is not equals 1" { 0 != 1 } From 0f1ff57542b90c399b7e5675c16f1bd1a09d970c Mon Sep 17 00:00:00 2001 From: corinnesollows Date: Mon, 19 Sep 2022 11:19:35 -0300 Subject: [PATCH 46/48] Charm Prompt changes (#781) --- go.mod | 20 ++++++++++++++++---- go.sum | 47 +++++++++++++++++++++++++++++++++++++++++------ prompt/prompt.go | 38 ++++++++++++++------------------------ 3 files changed, 71 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index dfe27631e..20b36f83b 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect github.com/google/uuid v1.3.0 github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect - github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/mapstructure v1.4.1 github.com/olekukonko/tablewriter v0.0.5 github.com/onsi/ginkgo v1.16.4 @@ -31,9 +31,16 @@ require ( gotest.tools/v3 v3.0.3 ) +require github.com/erikgeiser/promptkit v0.7.0 + require ( github.com/OneOfOne/xxhash v1.2.8 // indirect github.com/agnivade/levenshtein v1.1.1 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/charmbracelet/bubbles v0.11.0 // indirect + github.com/charmbracelet/bubbletea v0.21.0 // indirect + github.com/charmbracelet/lipgloss v0.5.0 // indirect + github.com/containerd/console v1.0.3 // indirect github.com/emirpasic/gods v1.12.0 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/ghodss/yaml v1.0.0 // indirect @@ -48,14 +55,19 @@ require ( github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.4 // indirect - github.com/mattn/go-runewidth v0.0.10 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect + github.com/muesli/cancelreader v0.2.0 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.12.0 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/open-policy-agent/opa v0.43.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - github.com/rivo/uniseg v0.1.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect github.com/vektah/gqlparser/v2 v2.4.7 // indirect github.com/xanzy/ssh-agent v0.2.1 // indirect @@ -65,7 +77,7 @@ require ( golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect golang.org/x/net v0.0.0-20220809184613-07c6da5e1ced // indirect golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect diff --git a/go.sum b/go.sum index 49bc8474c..8a5287c5c 100644 --- a/go.sum +++ b/go.sum @@ -132,6 +132,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.43.16/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= @@ -170,6 +172,13 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/bubbles v0.11.0 h1:fBLyY0PvJnd56Vlu5L84JJH6f4axhgIJ9P3NET78f0Q= +github.com/charmbracelet/bubbles v0.11.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= +github.com/charmbracelet/bubbletea v0.21.0 h1:f3y+kanzgev5PA916qxmDybSHU3N804uOnKnhRPXTcI= +github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8= +github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= @@ -216,6 +225,7 @@ github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= @@ -383,6 +393,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikgeiser/promptkit v0.7.0 h1:Yi28iN6JRs8/0x+wjQRPfWb+vWz1pFmZ5fu2uoFipD8= +github.com/erikgeiser/promptkit v0.7.0/go.mod h1:Jj9bhN+N8RbMjB1jthkr9A4ydmczZ1WZJ8xTXnP12dg= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -681,7 +693,10 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -698,12 +713,14 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -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-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= @@ -749,6 +766,18 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA= +github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/cancelreader v0.2.0 h1:SOpr+CfyVNce341kKqvbhhzQhBPyJRXQaCtn03Pae1Q= +github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.12.0 h1:KuQRUE3PgxRFWhq4gHvZtPSLCGDqM5q/cYr1pZ39ytc= +github.com/muesli/termenv v0.12.0/go.mod h1:WCCv32tusQ/EEZ5S8oUIIrC/nIuBcxCVqlN4Xfkv+7A= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -887,8 +916,9 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5X github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rhysd/go-github-selfupdate v0.0.0-20180520142321-41c1bbb0804a h1:YNh/SV+Z0p7kQDUE9Ux+46ruTucvQP43XB06DfZa8Es= github.com/rhysd/go-github-selfupdate v0.0.0-20180520142321-41c1bbb0804a/go.mod h1:mOFQaTkPA4plTgFW6Gnejb/RsEIqAoIqOACC2XaZX04= -github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -898,6 +928,7 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= @@ -1274,7 +1305,6 @@ golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1327,6 +1357,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1336,7 +1367,10 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 h1:v1W7bwXHsnLLloWYTVEdvGvA7BHMeBYsPcF0GLDxIRs= golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1344,8 +1378,9 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/prompt/prompt.go b/prompt/prompt.go index 90934b019..cb2bd52dd 100644 --- a/prompt/prompt.go +++ b/prompt/prompt.go @@ -1,48 +1,38 @@ package prompt -import "github.com/AlecAivazis/survey/v2" +import ( + "github.com/erikgeiser/promptkit/confirmation" + "github.com/erikgeiser/promptkit/textinput" +) // ReadSecretStringFromUser can be used to read a value from the user by masking their input. // It's useful for token input in our case. func ReadSecretStringFromUser(message string) (string, error) { secret := "" - prompt := &survey.Password{ - Message: message, - } - err := survey.AskOne(prompt, &secret) + input := textinput.New(message) + input.Hidden = true + secret, err := input.RunPrompt() if err != nil { return "", err } - return secret, nil } // ReadStringFromUser can be used to read any value from the user or the defaultValue when provided. func ReadStringFromUser(message string, defaultValue string) string { - token := "" - prompt := &survey.Input{ - Message: message, - } - - if defaultValue != "" { - prompt.Default = defaultValue - } - - err := survey.AskOne(prompt, &token) + input := textinput.New(message) + input.Placeholder = defaultValue + input.InitialValue = defaultValue + result, err := input.RunPrompt() if err != nil { panic(err) } - - return token + return result } // AskUserToConfirm will prompt the user to confirm with the provided message. func AskUserToConfirm(message string) bool { - result := true - prompt := &survey.Confirm{ - Message: message, - } - - err := survey.AskOne(prompt, &result) + input := confirmation.New(message, confirmation.No) + result, err := input.RunPrompt() return err == nil && result } From 3b49940db83d496241211861c4d3e209d48f1afb Mon Sep 17 00:00:00 2001 From: corinnesollows Date: Wed, 21 Sep 2022 15:21:33 -0300 Subject: [PATCH 47/48] Hack week help menu (#782) * Overwrote default cobra command help menu * Added testing env var --- .circleci/config.yml | 5 +- Makefile | 4 +- cmd/context.go | 9 +- cmd/policy/policy.go | 5 +- cmd/root.go | 133 ++++++++++++++++-- go.mod | 6 +- .../features/root_commands.feature | 6 +- 7 files changed, 143 insertions(+), 25 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ab76af1fb..1d7c27638 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -89,6 +89,8 @@ jobs: - run: setx GOPATH %USERPROFILE%\go - run: go get gotest.tools/gotestsum - run: mkdir test_results + - run: setx TESTING "true" + - run: name: Run tests command: | @@ -133,7 +135,8 @@ jobs: - run: command: bundle exec cucumber working_directory: integration_tests - + environment: + TESTING: "true" test: executor: go steps: diff --git a/Makefile b/Makefile index cd4d130de..604d50148 100644 --- a/Makefile +++ b/Makefile @@ -18,11 +18,11 @@ clean: .PHONY: test test: - go test -v ./... + TESTING=true go test -v ./... .PHONY: cover cover: - go test -race -coverprofile=coverage.txt ./... + TESTING=true go test -race -coverprofile=coverage.txt ./... .PHONY: lint lint: diff --git a/cmd/context.go b/cmd/context.go index 67f8b1aae..8369d0439 100644 --- a/cmd/context.go +++ b/cmd/context.go @@ -47,9 +47,12 @@ func newContextCommand(config *settings.Config) *cobra.Command { } command := &cobra.Command{ - Use: "context", - Short: "Contexts provide a mechanism for securing and sharing environment variables across projects. The environment variables are defined as name/value pairs and are injected at runtime.", - } + Use: "context", + Long: ` +Contexts provide a mechanism for securing and sharing environment variables across +projects. The environment variables are defined as name/value pairs and +are injected at runtime.`, + Short: "For securing and sharing environment variables across projects"} listCommand := &cobra.Command{ Short: "List all contexts", diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go index a4972a121..31b3ca47c 100644 --- a/cmd/policy/policy.go +++ b/cmd/policy/policy.go @@ -28,8 +28,9 @@ func NewCommand(config *settings.Config, preRunE validator.Validator) *cobra.Com cmd := &cobra.Command{ Use: "policy", PersistentPreRunE: preRunE, - Short: "Policies ensures security of build configs via security policy management framework. " + - "This group of commands allows the management of polices to be verified against build configs.", + Short: "Manage security policies", + Long: `Policies ensures security of build configs via security policy management framework. +This group of commands allows the management of polices to be verified against build configs.`, } policyBaseURL := cmd.PersistentFlags().String("policy-base-url", "https://internal.circleci.com", "base url for policy api") diff --git a/cmd/root.go b/cmd/root.go index 5261e5829..174f764eb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,11 +2,10 @@ package cmd import ( "fmt" + "log" "os" "strings" - "github.com/spf13/cobra" - "github.com/CircleCI-Public/circleci-cli/api/header" "github.com/CircleCI-Public/circleci-cli/cmd/info" "github.com/CircleCI-Public/circleci-cli/cmd/policy" @@ -15,11 +14,14 @@ import ( "github.com/CircleCI-Public/circleci-cli/md_docs" "github.com/CircleCI-Public/circleci-cli/settings" "github.com/CircleCI-Public/circleci-cli/version" + "github.com/charmbracelet/lipgloss" + "github.com/spf13/cobra" ) var defaultEndpoint = "graphql-unstable" var defaultHost = "https://circleci.com" var defaultRestEndpoint = "api/v2" +var trueString = "true" // rootCmd is used internally and global to the package but not exported // therefore we can use it in other commands, like `usage` @@ -88,6 +90,7 @@ Global Flags: Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} + ` // MakeCommands creates the top level commands @@ -108,8 +111,9 @@ func MakeCommands() *cobra.Command { rootOptions.Data = &data.Data rootCmd = &cobra.Command{ - Use: "circleci", - Long: rootHelpLong(rootOptions), + Use: "circleci", + Long: rootHelpLong(), + Short: rootHelpShort(rootOptions), PersistentPreRunE: func(_ *cobra.Command, _ []string) error { return rootCmdPreRun(rootOptions) }, @@ -119,6 +123,11 @@ func MakeCommands() *cobra.Command { cobra.AddTemplateFunc("HasAnnotations", hasAnnotations) cobra.AddTemplateFunc("PositionalArgs", md_docs.PositionalArgs) cobra.AddTemplateFunc("FormatPositionalArg", md_docs.FormatPositionalArg) + + if os.Getenv("TESTING") != trueString { + helpCmd := helpCmd{cmd: rootCmd} + rootCmd.SetHelpFunc(helpCmd.helpTemplate) + } rootCmd.SetUsageTemplate(usageTemplate) rootCmd.DisableAutoGenTag = true @@ -279,21 +288,125 @@ func isUpdateIncluded(packageManager string) bool { } } -func rootHelpLong(config *settings.Config) string { - long := `Use CircleCI from the command line. +func skipUpdateByDefault() bool { + return os.Getenv("CI") == trueString || os.Getenv("CIRCLECI_CLI_SKIP_UPDATE_CHECK") == trueString +} +/**************** Help Menu Functions ****************/ + +//rootHelpLong creates content for the long field in the command +func rootHelpLong() string { + long := ` + /?? /?? /?? + |__/ | ?? |__/ + /???????? /??????? /?? /?????? /???????| ?? /?????? /??????? /?? + /_______/?? /??_____/| ?? /??__ ?? /??_____/| ?? /??__ ?? /??_____/| ?? + /?? | ?? | ?? | ??| ?? \__/| ?? | ??| ???????? | ?? | ?? + |__/ | ?? | ?? | ??| ?? | ?? | ??| ??_____/ | ?? | ?? + /???????? | ???????| ??| ?? | ???????| ??| ??????? | ???????| ?? + /________/ \_______/|__/|__/ \_______/|__/ \_______/ \_______/|__/` + return long +} + +// rootHelpShort creates content for the short feild in the command +func rootHelpShort(config *settings.Config) string { + short := `Use CircleCI from the command line. This project is the seed for CircleCI's new command-line application.` // We should only print this for cloud users if config.Host != defaultHost { - return long + return short } return fmt.Sprintf(`%s +For more help, see the documentation here: %s`, short, config.Data.Links.CLIDocs) +} -For more help, see the documentation here: %s`, long, config.Data.Links.CLIDocs) +type helpCmd struct { + cmd *cobra.Command } -func skipUpdateByDefault() bool { - return os.Getenv("CI") == "true" || os.Getenv("CIRCLECI_CLI_SKIP_UPDATE_CHECK") == "true" +// helpTemplate Building a custom help template with more finess and pizazz +func (helpCmd *helpCmd) helpTemplate(cmd *cobra.Command, s []string) { + + /***Styles ***/ + titleStyle := lipgloss.NewStyle().Bold(true). + Foreground(lipgloss.AdaptiveColor{Light: `#003740`, Dark: `#3B6385`}). + BorderBottom(true). + Margin(1, 0, 1, 0). + Padding(0, 1, 0, 1).Align(lipgloss.Center) + subCmdStyle := lipgloss.NewStyle(). + Foreground(lipgloss.AdaptiveColor{Light: `#161616`, Dark: `#FFFFFF`}). + Padding(0, 4, 0, 4).Align(lipgloss.Left) + subCmdInfoStyle := lipgloss.NewStyle(). + Foreground(lipgloss.AdaptiveColor{Light: `#161616`, Dark: `#FFFFFF`}).Bold(true) + textStyle := lipgloss.NewStyle(). + Foreground(lipgloss.AdaptiveColor{Light: `#161616`, Dark: `#FFFFFF`}).Align(lipgloss.Left).Margin(0).Padding(0) + + /** Building Usage String **/ + usageText := strings.Builder{} + + //get command path + usageText.WriteString(titleStyle.Render(cmd.CommandPath())) + + //get command short or long + cmdDesc := titleStyle.Render(cmd.Long) + if strings.TrimSpace(cmdDesc) == "" || cmd.Name() == "circleci" { + if cmd.Name() == "circleci" { + cmdDesc += "\n\n" //add some spaces for circleci command + } + cmdDesc += subCmdStyle.Render(cmd.Short) + } + usageText.WriteString(cmdDesc + "\n") + + if len(cmd.Aliases) > 0 { + aliases := titleStyle.Render("Aliases:") + aliases += textStyle.Render(cmd.NameAndAliases()) + usageText.WriteString(aliases + "\n") + } + + if cmd.Runnable() { + usage := titleStyle.Render("Usage:") + usage += textStyle.Render(cmd.UseLine()) + usageText.WriteString(usage + "\n") + } + + if cmd.HasExample() { + examples := titleStyle.Render("Example:") + examples += textStyle.Render(cmd.Example) + usageText.WriteString(examples + "\n") + } + + if cmd.HasAvailableSubCommands() { + subCmds := cmd.Commands() + subTitle := titleStyle.Render("Available Commands:") + subs := "" + for i := range subCmds { + if subCmds[i].IsAvailableCommand() { + subs += subCmdStyle.Render(subCmds[i].Name()) + subCmdInfoStyle. + PaddingLeft(subCmds[i].NamePadding()-len(subCmds[i].Name())+1).Render(subCmds[i].Short) + "\n" + } + } + usageText.WriteString(lipgloss.JoinVertical(lipgloss.Left, subTitle, subs)) + } + + if cmd.HasAvailableLocalFlags() { + flags := titleStyle.Render("Local Flags:") + flags += textStyle.Render("\n" + cmd.LocalFlags().FlagUsages()) + usageText.WriteString(flags) + } + if cmd.HasAvailableInheritedFlags() { + flags := titleStyle.Render("Global Flags:") + flags += textStyle.Render("\n" + cmd.InheritedFlags().FlagUsages()) + usageText.WriteString(flags) + } + + //Border styles + borderStyle := lipgloss.NewStyle(). + Padding(0, 1, 0, 1). + Width(120). + BorderForeground(lipgloss.AdaptiveColor{Light: `#3B6385`, Dark: `#47A359`}). + Border(lipgloss.ThickBorder()) + + log.Println("\n" + borderStyle.Render(usageText.String()+"\n")) } diff --git a/go.mod b/go.mod index 20b36f83b..d515fd50c 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,10 @@ require ( gotest.tools/v3 v3.0.3 ) -require github.com/erikgeiser/promptkit v0.7.0 +require ( + github.com/charmbracelet/lipgloss v0.5.0 + github.com/erikgeiser/promptkit v0.7.0 +) require ( github.com/OneOfOne/xxhash v1.2.8 // indirect @@ -39,7 +42,6 @@ require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/charmbracelet/bubbles v0.11.0 // indirect github.com/charmbracelet/bubbletea v0.21.0 // indirect - github.com/charmbracelet/lipgloss v0.5.0 // indirect github.com/containerd/console v1.0.3 // indirect github.com/emirpasic/gods v1.12.0 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect diff --git a/integration_tests/features/root_commands.feature b/integration_tests/features/root_commands.feature index 13da2602f..3b3fa5d61 100644 --- a/integration_tests/features/root_commands.feature +++ b/integration_tests/features/root_commands.feature @@ -7,11 +7,7 @@ Feature: Root Commands When I run `circleci help` Then the output should contain: """ - Use CircleCI from the command line. - - This project is the seed for CircleCI's new command-line application. - - For more help, see the documentation here: https://circleci.com/docs/2.0/local-cli/ + circleci """ And the exit status should be 0 From eec8fd6dbad9b117245f3bd593ce9bbc9a212b8b Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Fri, 23 Sep 2022 14:34:26 -0400 Subject: [PATCH 48/48] chore: Dynamically print flag options in error message (#785) * chore: Dynamically print flag options Signed-off-by: Adam Harvey * fix: Forcibly sort keys to ensure consistency Signed-off-by: Adam Harvey * fix: Alphabetical sorting of flag values Signed-off-by: Adam Harvey Signed-off-by: Adam Harvey --- cmd/orb.go | 16 ++++++++++++---- cmd/orb_test.go | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/cmd/orb.go b/cmd/orb.go index 16ac6f160..2650a720e 100644 --- a/cmd/orb.go +++ b/cmd/orb.go @@ -590,12 +590,20 @@ var validSortFlag = map[string]bool{ "projects": true, "orgs": true} -func validateSortFlag(sort string) error { - if _, valid := validSortFlag[sort]; valid { +func validateSortFlag(sortFlag string) error { + if _, valid := validSortFlag[sortFlag]; valid { return nil } - // TODO(zzak): we could probably reuse the map above to print the valid values - return fmt.Errorf("expected `%s` to be one of \"builds\", \"projects\", or \"orgs\"", sort) + + keys := make([]string, 0, len(validSortFlag)) + for key := range validSortFlag { + keys = append(keys, key) + } + sort.Strings(keys) + + validFlags := fmt.Sprint(strings.Join(keys, ", ")) + + return fmt.Errorf("expected `%s` to be one of: %s", sortFlag, validFlags) } func listOrbs(opts orbOptions) error { diff --git a/cmd/orb_test.go b/cmd/orb_test.go index 8d0b89cb6..f10100bb7 100644 --- a/cmd/orb_test.go +++ b/cmd/orb_test.go @@ -1979,7 +1979,7 @@ Search, filter, and view sources for all Orbs online at https://circleci.com/dev Eventually(session).Should(clitest.ShouldFail()) stderr := session.Wait().Err.Contents() - Expect(string(stderr)).To(Equal("Error: expected `idontknow` to be one of \"builds\", \"projects\", or \"orgs\"\n")) + Expect(string(stderr)).To(Equal("Error: expected `idontknow` to be one of: builds, orgs, projects\n")) }) })