From 42cd1544c8acda7b7410b3b92c757c3903c1cf27 Mon Sep 17 00:00:00 2001 From: Stephen Augustus Date: Sat, 27 Apr 2024 06:49:31 -0400 Subject: [PATCH 1/2] Remove third_party directory Signed-off-by: Stephen Augustus --- third_party/k8s.io/test-infra/LICENSE | 202 - .../test-infra/ghproxy/ghcache/README.md | 18 - .../test-infra/ghproxy/ghcache/coalesce.go | 271 - .../test-infra/ghproxy/ghcache/ghcache.go | 542 -- .../test-infra/ghproxy/ghcache/partitioner.go | 85 - .../test-infra/ghproxy/ghmetrics/ghmetrics.go | 196 - .../test-infra/ghproxy/ghmetrics/ghpath.go | 181 - .../test-infra/ghproxy/ghmetrics/hash.go | 64 - .../k8s.io/test-infra/prow/config/org/org.go | 176 - .../test-infra/prow/config/secret/agent.go | 183 - .../test-infra/prow/config/secret/reloader.go | 99 - .../test-infra/prow/config/secret/secret.go | 41 - .../k8s.io/test-infra/prow/flagutil/bool.go | 48 - .../k8s.io/test-infra/prow/flagutil/doc.go | 19 - .../k8s.io/test-infra/prow/flagutil/git.go | 99 - .../k8s.io/test-infra/prow/flagutil/github.go | 420 -- .../prow/flagutil/github_enablement.go | 94 - .../prow/flagutil/instrumentation.go | 69 - .../test-infra/prow/flagutil/strings.go | 76 - .../test-infra/prow/gerrit/source/source.go | 106 - third_party/k8s.io/test-infra/prow/git/git.go | 639 -- .../k8s.io/test-infra/prow/git/types/types.go | 29 - .../k8s.io/test-infra/prow/git/v2/adapter.go | 118 - .../test-infra/prow/git/v2/client_factory.go | 314 - .../k8s.io/test-infra/prow/git/v2/executor.go | 78 - .../k8s.io/test-infra/prow/git/v2/fakes.go | 52 - .../test-infra/prow/git/v2/interactor.go | 461 -- .../test-infra/prow/git/v2/publisher.go | 114 - .../k8s.io/test-infra/prow/git/v2/remote.go | 166 - .../k8s.io/test-infra/prow/github/README.md | 10 - .../prow/github/app_auth_roundtripper.go | 283 - .../k8s.io/test-infra/prow/github/client.go | 5178 ----------------- .../k8s.io/test-infra/prow/github/helpers.go | 107 - .../k8s.io/test-infra/prow/github/hmac.go | 128 - .../k8s.io/test-infra/prow/github/links.go | 37 - .../k8s.io/test-infra/prow/github/types.go | 1610 ----- .../k8s.io/test-infra/prow/github/webhooks.go | 79 - .../test-infra/prow/logrusutil/logrusutil.go | 153 - .../test-infra/prow/secretutil/censor.go | 126 - .../k8s.io/test-infra/prow/secretutil/doc.go | 18 - .../test-infra/prow/simplifypath/simplify.go | 123 - .../k8s.io/test-infra/prow/version/doc.go | 59 - .../k8s.io/test-infra/prow/version/metrics.go | 52 - 43 files changed, 12923 deletions(-) delete mode 100644 third_party/k8s.io/test-infra/LICENSE delete mode 100644 third_party/k8s.io/test-infra/ghproxy/ghcache/README.md delete mode 100644 third_party/k8s.io/test-infra/ghproxy/ghcache/coalesce.go delete mode 100644 third_party/k8s.io/test-infra/ghproxy/ghcache/ghcache.go delete mode 100644 third_party/k8s.io/test-infra/ghproxy/ghcache/partitioner.go delete mode 100644 third_party/k8s.io/test-infra/ghproxy/ghmetrics/ghmetrics.go delete mode 100644 third_party/k8s.io/test-infra/ghproxy/ghmetrics/ghpath.go delete mode 100644 third_party/k8s.io/test-infra/ghproxy/ghmetrics/hash.go delete mode 100644 third_party/k8s.io/test-infra/prow/config/org/org.go delete mode 100644 third_party/k8s.io/test-infra/prow/config/secret/agent.go delete mode 100644 third_party/k8s.io/test-infra/prow/config/secret/reloader.go delete mode 100644 third_party/k8s.io/test-infra/prow/config/secret/secret.go delete mode 100644 third_party/k8s.io/test-infra/prow/flagutil/bool.go delete mode 100644 third_party/k8s.io/test-infra/prow/flagutil/doc.go delete mode 100644 third_party/k8s.io/test-infra/prow/flagutil/git.go delete mode 100644 third_party/k8s.io/test-infra/prow/flagutil/github.go delete mode 100644 third_party/k8s.io/test-infra/prow/flagutil/github_enablement.go delete mode 100644 third_party/k8s.io/test-infra/prow/flagutil/instrumentation.go delete mode 100644 third_party/k8s.io/test-infra/prow/flagutil/strings.go delete mode 100644 third_party/k8s.io/test-infra/prow/gerrit/source/source.go delete mode 100644 third_party/k8s.io/test-infra/prow/git/git.go delete mode 100644 third_party/k8s.io/test-infra/prow/git/types/types.go delete mode 100644 third_party/k8s.io/test-infra/prow/git/v2/adapter.go delete mode 100644 third_party/k8s.io/test-infra/prow/git/v2/client_factory.go delete mode 100644 third_party/k8s.io/test-infra/prow/git/v2/executor.go delete mode 100644 third_party/k8s.io/test-infra/prow/git/v2/fakes.go delete mode 100644 third_party/k8s.io/test-infra/prow/git/v2/interactor.go delete mode 100644 third_party/k8s.io/test-infra/prow/git/v2/publisher.go delete mode 100644 third_party/k8s.io/test-infra/prow/git/v2/remote.go delete mode 100644 third_party/k8s.io/test-infra/prow/github/README.md delete mode 100644 third_party/k8s.io/test-infra/prow/github/app_auth_roundtripper.go delete mode 100644 third_party/k8s.io/test-infra/prow/github/client.go delete mode 100644 third_party/k8s.io/test-infra/prow/github/helpers.go delete mode 100644 third_party/k8s.io/test-infra/prow/github/hmac.go delete mode 100644 third_party/k8s.io/test-infra/prow/github/links.go delete mode 100644 third_party/k8s.io/test-infra/prow/github/types.go delete mode 100644 third_party/k8s.io/test-infra/prow/github/webhooks.go delete mode 100644 third_party/k8s.io/test-infra/prow/logrusutil/logrusutil.go delete mode 100644 third_party/k8s.io/test-infra/prow/secretutil/censor.go delete mode 100644 third_party/k8s.io/test-infra/prow/secretutil/doc.go delete mode 100644 third_party/k8s.io/test-infra/prow/simplifypath/simplify.go delete mode 100644 third_party/k8s.io/test-infra/prow/version/doc.go delete mode 100644 third_party/k8s.io/test-infra/prow/version/metrics.go diff --git a/third_party/k8s.io/test-infra/LICENSE b/third_party/k8s.io/test-infra/LICENSE deleted file mode 100644 index deeaa08..0000000 --- a/third_party/k8s.io/test-infra/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2016 The Kubernetes Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/third_party/k8s.io/test-infra/ghproxy/ghcache/README.md b/third_party/k8s.io/test-infra/ghproxy/ghcache/README.md deleted file mode 100644 index f3a0541..0000000 --- a/third_party/k8s.io/test-infra/ghproxy/ghcache/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# ghCache - -## What? - -ghCache is an HTTP cache optimized for caching responses from the GitHub API (https://api.github.com). Specifically, it has the following non-standard caching behavior: -- Every cache hit is revalidated with a conditional HTTP request to GitHub regardless of cache entry freshness (TTL). The 'Cache-Control' header is ignored and overwritten to achieve this. -- Concurrent requests for the same resource are coalesced and share a single request/response from GitHub instead of each request resulting in a corresponding upstream request and response. - -ghCache also provides prometheus instrumentation to expose cache activity, -request duration, and API token usage/savings. - -## Why? - -The most important behavior of ghCache is the mandatory cache entry revalidation. -While this property would cause most API caches to use tokens excessively, in the case of GitHub, we can actually save API tokens. This is because conditional requests for unchanged resources don't cost any API tokens!!! See: https://docs.github.com/en/rest/overview/resources-in-the-rest-api#conditional-requests -Free revalidation allows us to ensure that every request is satisfied with the most up to date resource without actually spending an API token unless the resource has been updated since we last checked it. - -Request coalescing is beneficial for use cases in which the same resource is requested multiple times in rapid succession. Normally these requests would each result in an upstream request to GitHub, potentially costing API tokens, but with request coalescing at most one token is used. This particularly helps when many handlers react to the same event like in Prow's [hook component](/prow/cmd/hook). diff --git a/third_party/k8s.io/test-infra/ghproxy/ghcache/coalesce.go b/third_party/k8s.io/test-infra/ghproxy/ghcache/coalesce.go deleted file mode 100644 index e3882c6..0000000 --- a/third_party/k8s.io/test-infra/ghproxy/ghcache/coalesce.go +++ /dev/null @@ -1,271 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ghcache - -import ( - "bufio" - "bytes" - "net/http" - "net/http/httputil" - "strconv" - "sync" - "time" - - "github.com/sirupsen/logrus" - - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/ghproxy/ghmetrics" -) - -// requestCoalescer allows concurrent requests for the same URI to share a -// single upstream request and response. Once a request comes in for processing -// for the first time, it is processed and a response is received (via -// "requestExecutor"). Meanwhile, if there are any other requests for the same URI, -// those threads Wait(). Then when the first request is done processing (we -// receive a real request), we copy the original request's response into the -// subscribed threads, before letting them all finish. The "cache" map is there -// for our own short-term memory of knowing which request is the "first" one of -// its kind. -type requestCoalescer struct { - sync.Mutex - cache map[string]*firstRequest - - // requestExecutor is anything that can resolve a request by executing a - // single HTTP transaction, returning a Response for the provided Request. - // The coalescer uses this to talk to the actual proxied backend. Using an - // interface here allows us to mock out a fake backend server's response to - // the request. - requestExecutor http.RoundTripper - - hasher ghmetrics.Hasher -} - -// firstRequest is where we store the coalesced requests's actual response. It -// is named firstRequest because only the first one (which also creates the -// entry in the cache) will actually be resolved by being processed over the -// network; all subsequent requests that match the first request's URL will end -// up waiting for this first request to finish. After the first request is -// processed, the "resp" field will be populated, and subsequent requests will -// simply reuse the same "resp" body. Note that if the first request fails, then -// all subsequent requests will fail together. -type firstRequest struct { - *sync.Cond - - // Are there any threads that are "subscribed" to this first request's - // response? - subscribers bool - resp []byte - err error -} - -// RoundTrip coalesces concurrent GET requests for the same URI by blocking -// the later requests until the first request returns and then sharing the -// response between all requests. -// -// Notes: Deadlock shouldn't be possible because the map lock is always -// acquired before firstRequest lock if both locks are to be held and we -// never hold multiple firstRequest locks. -func (coalescer *requestCoalescer) RoundTrip(req *http.Request) (*http.Response, error) { - // Only coalesce GET requests - if req.Method != http.MethodGet { - resp, err := coalescer.requestExecutor.RoundTrip(req) - var tokenBudgetName string - if val := req.Header.Get(TokenBudgetIdentifierHeader); val != "" { - tokenBudgetName = val - } else { - tokenBudgetName = coalescer.hasher.Hash(req) - } - collectMetrics(ModeSkip, req, resp, tokenBudgetName) - return resp, err - } - - var cacheMode = ModeError - resp, err := func() (*http.Response, error) { - key := req.URL.String() - coalescer.Lock() - firstReq, ok := coalescer.cache[key] - // Note that we cannot immediately Unlock() coalescer here just after - // the cache lookup, because that may result in multiple threads - // possibly becoming a "firstReq" creator (main) thread. This is why we - // only Unlock() coalescer __after__ creating the cache entry. - - // Earlier request in flight. Wait for its response, which will be - // received by a different thread (specifically, the original thread - // that created the firstReq object --- let's call this the "main" - // thread for simplicity). - if ok { - // If the request that we're trying to process has a body, don't - // forget to close it. Normally if we're performing the HTTP - // roundtrip ourselves, we won't need to do this because the - // RoundTripper will do it on its own. However we'll never call - // RoundTrip() on this request ourselves because we're going to be - // lazy and just wait for the main thread to do it for us. So we - // need to close the body directly. See - // https://cs.opensource.google/go/go/+/refs/tags/go1.17.1:src/net/http/transport.go;l=510 - // and - // https://cs.opensource.google/go/go/+/refs/tags/go1.17.1:src/net/http/request.go;drc=refs%2Ftags%2Fgo1.17.1;l=1408 - // for an example. - if req.Body != nil { - defer req.Body.Close() // Since we won't pass the request we must close it. - } - - // Let the main thread know that there is at least one subscriber - // (us). We do this by incrementing the firstReq.subscribers - // variable. Note that we first grab the inner firstReq lock before - // unlocking the outer coalescer. This order is important as it - // guarantees that no other threads will delete the cache entry - // (firstReq) before we're done waiting for it. - // - // We need to unlock the coalescer so that other threads can read - // from it (and decide whether to wait or create a new cache entry). - // That is, the coalescer itself should never be blocked by - // subscribed threads. - firstReq.L.Lock() - coalescer.Unlock() - firstReq.subscribers = true - - // The documentation for Wait() says: - // "Because c.L is not locked when Wait first resumes, the caller typically - // cannot assume that the condition is true when Wait returns. Instead, the - // caller should Wait in a loop." - // This does not apply to this use of Wait() because the condition we are - // waiting for remains true once it becomes true. This lets us avoid the - // normal check to see if the condition has switched back to false between - // the signal being sent and this thread acquiring the lock. - - // Unlock firstReq.L variable (so that the thread that __did__ create - // the first request can actually process it). Suspend execution of - // this thread until that is done. - firstReq.Wait() - - // Because firstReq.Wait() will lock firstReq.L before returning, - // release the lock now because we won't be modifying anything - // inside firstRequest. Anyway, if we're here it means that we've - // been woken by a Broadcast() by the main thread. - firstReq.L.Unlock() - - if firstReq.err != nil { - // Don't log the error ourselves, because it will be logged once - // by the main thread. This avoids spamming the logs with the - // same error. - return nil, firstReq.err - } - - // Copy in firstReq's response into our own response. We didn't have - // to process the request ourselves! Wasn't that easy? - resp, err := http.ReadResponse(bufio.NewReader(bytes.NewBuffer(firstReq.resp)), nil) - if err != nil { - logrus.WithField("cache-key", key).WithError(err).Error("Error loading response.") - return nil, err - } - - cacheMode = ModeCoalesced - return resp, nil - } - - // No earlier (first) request in flight yet. Create a new firstRequest - // object and process it ourselves. - firstReq = &firstRequest{Cond: sync.NewCond(&sync.Mutex{})} - coalescer.cache[key] = firstReq - - // Unlock the coalescer so that it doesn't block on this particular - // request. This allows subsequent requests for the same URL to become - // subscribers to this main one. - coalescer.Unlock() - - // Actually process the request and get a response. - resp, err := coalescer.requestExecutor.RoundTrip(req) - // Real response received. Remove this firstRequest from the cache first - // __before__ waking any subscribed threads to let them copy the - // response we got. This order is important. If delete the cache entry - // __after__ waking the subscribed threads, then the following race - // condition can happen: - // - // 1. firstReq creator thread wakes subscribed threads - // 2. subscribed threads begin copying data from firstReq struct - // 3. *NEW* subscribers get created, because the cached key is still there - // 4. cached key is finally deleted - // 5. firstReq creator thread from Step 1 dies - // 6. subscribed threads from Step 3 will wait forever - // (memory leak, not to mention request timeout for all of these) - // - // Deleting the cache key now also allows a new firstRequest{} object to - // be created (and the whole cycle repeated again) by another set of - // requests in flight, if any. - coalescer.Lock() - delete(coalescer.cache, key) - coalescer.Unlock() - - // Write response data into firstReq for all subscribers to see. But - // only bother with writing into firstReq if we have subscribers at all - // (because otherwise no other thread will use it anyway). - firstReq.L.Lock() - if firstReq.subscribers { - if err != nil { - firstReq.resp, firstReq.err = nil, err - } else { - // Copy the response into firstReq.resp before letting - // subscribers know about it. - firstReq.resp, firstReq.err = httputil.DumpResponse(resp, true) - } - - // Wake up all subscribed threads. They will all read firstReq.resp - // to construct their own (identical) HTTP Responses, based on the - // contents of firstReq. - firstReq.Broadcast() - } - firstReq.L.Unlock() - - // The RoundTrip() encountered an error. Log it. - if err != nil { - logrus.WithField("cache-key", key).WithError(err).Warn("Error from cache transport layer.") - return nil, err - } - - // Return a ModeMiss by default (that is, the response was not in the - // cache, so we had to proxy the request and cache the response). This - // is what cacheResponseMode() does, unless there are other modes we can - // glean from the response header, find it with cacheResponseMode. - cacheMode = cacheResponseMode(resp.Header) - - return resp, nil - }() - - var tokenBudgetName string - if val := req.Header.Get(TokenBudgetIdentifierHeader); val != "" { - tokenBudgetName = val - } else { - tokenBudgetName = coalescer.hasher.Hash(req) - } - - collectMetrics(cacheMode, req, resp, tokenBudgetName) - return resp, err -} - -func collectMetrics(cacheMode CacheResponseMode, req *http.Request, resp *http.Response, tokenBudgetName string) { - ghmetrics.CollectCacheRequestMetrics(string(cacheMode), req.URL.Path, req.Header.Get("User-Agent"), tokenBudgetName) - if resp != nil { - resp.Header.Set(CacheModeHeader, string(cacheMode)) - if cacheMode == ModeRevalidated && resp.Header.Get(cacheEntryCreationDateHeader) != "" { - intVal, err := strconv.Atoi(resp.Header.Get(cacheEntryCreationDateHeader)) - if err != nil { - logrus.WithError(err).WithField("header-value", resp.Header.Get(cacheEntryCreationDateHeader)).Warn("Failed to convert cacheEntryCreationDateHeader value to int") - } else { - ghmetrics.CollectCacheEntryAgeMetrics(float64(time.Now().Unix()-int64(intVal)), req.URL.Path, req.Header.Get("User-Agent"), tokenBudgetName) - } - } - } -} diff --git a/third_party/k8s.io/test-infra/ghproxy/ghcache/ghcache.go b/third_party/k8s.io/test-infra/ghproxy/ghcache/ghcache.go deleted file mode 100644 index f76adc7..0000000 --- a/third_party/k8s.io/test-infra/ghproxy/ghcache/ghcache.go +++ /dev/null @@ -1,542 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package ghcache implements an HTTP cache optimized for caching responses -// from the GitHub API (https://api.github.com). -// -// Specifically, it enforces a cache policy that revalidates every cache hit -// with a conditional request to upstream regardless of cache entry freshness -// because conditional requests for unchanged resources don't cost any API -// tokens!!! See: https://developer.github.com/v3/#conditional-requests -// -// It also provides request coalescing and prometheus instrumentation. -package ghcache - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "os" - "path" - "path/filepath" - "strconv" - "strings" - "sync" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - - "github.com/gomodule/redigo/redis" - "github.com/gregjones/httpcache" - "github.com/gregjones/httpcache/diskcache" - rediscache "github.com/gregjones/httpcache/redis" - "github.com/peterbourgon/diskv" - "github.com/prometheus/client_golang/prometheus" - "github.com/sirupsen/logrus" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/ghproxy/ghmetrics" - "golang.org/x/sync/semaphore" -) - -type CacheResponseMode string - -// Cache response modes describe how ghcache fulfilled a request. -const ( - CacheModeHeader = "X-Cache-Mode" - - ModeError CacheResponseMode = "ERROR" // internal error handling request - ModeNoStore CacheResponseMode = "NO-STORE" // response not cacheable - ModeMiss CacheResponseMode = "MISS" // not in cache, request proxied and response cached. - ModeChanged CacheResponseMode = "CHANGED" // cache value invalid: resource changed, cache updated - ModeSkip CacheResponseMode = "SKIP" // cache was skipped, not applicable. e.g. POST request. - // The modes below are the happy cases in which the request is fulfilled for - // free (no API tokens used). - ModeCoalesced CacheResponseMode = "COALESCED" // coalesced request, this is a copied response - ModeRevalidated CacheResponseMode = "REVALIDATED" // cached value revalidated and returned - - // cacheEntryCreationDateHeader contains the creation date of the cache entry - cacheEntryCreationDateHeader = "X-PROW-REQUEST-DATE" - - // TokenBudgetIdentifierHeader is used to identify the token budget for - // which metrics should be recorded if set. If unset, the sha256sum of - // the Authorization header will be used. - TokenBudgetIdentifierHeader = "X-PROW-GHCACHE-TOKEN-BUDGET-IDENTIFIER" - - // TokenExpiryAtHeader includes a date at which the passed token expires and all associated caches - // can be cleaned up. It's value must be in RFC3339 format. - TokenExpiryAtHeader = "X-PROW-TOKEN-EXPIRES-AT" - - apiV3 = "v3" - apiV4 = "v4" -) - -// RequestThrottlingTimes keeps the information about throttling times per API and request methods -type RequestThrottlingTimes struct { - // throttlingTime is applied for all non-GET request methods for apiV3 and apiV4 - throttlingTime uint - // throttlingTimeV4 if different than 0, it's applied for non-GET request methods for apiV4, instead of ThrottlingTime - throttlingTimeV4 uint - // throttlingTimeForGET is applied for all GET request methods for apiV3 and apiV4 - throttlingTimeForGET uint - // maxDelayTime is applied when formed queue is too large, it allows to temporarily set max delay time provided by user instead of calculated value - maxDelayTime uint - // maxDelayTimeV4 is maxDelayTime for APIv4 - maxDelayTimeV4 uint -} - -func (rtt *RequestThrottlingTimes) isEnabled() bool { - return rtt.throttlingTime > 0 && rtt.throttlingTimeForGET > 0 -} - -func (rtt *RequestThrottlingTimes) getThrottlingTimeV4() uint { - if rtt.throttlingTimeV4 > 0 { - return rtt.throttlingTimeV4 - } - return rtt.throttlingTime -} - -// NewRequestThrottlingTimes creates a new RequestThrottlingTimes and returns it -func NewRequestThrottlingTimes(requestThrottlingTime, requestThrottlingTimeV4, requestThrottlingTimeForGET, requestThrottlingMaxDelayTime, requestThrottlingMaxDelayTimeV4 uint) RequestThrottlingTimes { - return RequestThrottlingTimes{ - throttlingTime: requestThrottlingTime, - throttlingTimeV4: requestThrottlingTimeV4, - throttlingTimeForGET: requestThrottlingTimeForGET, - maxDelayTime: requestThrottlingMaxDelayTime, - maxDelayTimeV4: requestThrottlingMaxDelayTimeV4, - } -} - -func CacheModeIsFree(mode CacheResponseMode) bool { - switch mode { - case ModeCoalesced: - return true - case ModeRevalidated: - return true - case ModeError: - // In this case we did not successfully communicate with the GH API, so no - // token is used, but we also don't return a response, so ModeError won't - // ever be returned as a value of CacheModeHeader. - return true - } - return false -} - -// outboundConcurrencyGauge provides the 'concurrent_outbound_requests' gauge that -// is global to the proxy. -var outboundConcurrencyGauge = prometheus.NewGauge(prometheus.GaugeOpts{ - Name: "concurrent_outbound_requests", - Help: "How many concurrent requests are in flight to GitHub servers.", -}) - -// pendingOutboundConnectionsGauge provides the 'pending_outbound_requests' gauge that -// is global to the proxy. -var pendingOutboundConnectionsGauge = prometheus.NewGauge(prometheus.GaugeOpts{ - Name: "pending_outbound_requests", - Help: "How many pending requests are waiting to be sent to GitHub servers.", -}) - -var cachePartitionsCounter = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Name: "ghcache_cache_parititions", - Help: "Which cache partitions exist.", - }, - []string{"token_hash"}, -) - -func init() { - - prometheus.MustRegister(outboundConcurrencyGauge) - prometheus.MustRegister(pendingOutboundConnectionsGauge) - prometheus.MustRegister(cachePartitionsCounter) -} - -func cacheResponseMode(headers http.Header) CacheResponseMode { - if strings.Contains(headers.Get("Cache-Control"), "no-store") { - return ModeNoStore - } - if strings.Contains(headers.Get("Status"), "304 Not Modified") { - return ModeRevalidated - } - if headers.Get("X-Conditional-Request") != "" { - return ModeChanged - } - return ModeMiss -} - -func newThrottlingTransport(maxConcurrency int, roundTripper http.RoundTripper, hasher ghmetrics.Hasher, throttlingTimes RequestThrottlingTimes) http.RoundTripper { - return &throttlingTransport{ - sem: semaphore.NewWeighted(int64(maxConcurrency)), - roundTripper: roundTripper, - timeThrottlingEnabled: throttlingTimes.isEnabled(), - hasher: hasher, - registryApiV3: newTokensRegistry(throttlingTimes.throttlingTime, throttlingTimes.throttlingTimeForGET, throttlingTimes.maxDelayTime), - registryApiV4: newTokensRegistry(throttlingTimes.getThrottlingTimeV4(), throttlingTimes.throttlingTimeForGET, throttlingTimes.maxDelayTimeV4), - } -} - -func newTokensRegistry(requestThrottlingTime, requestThrottlingTimeForGET, maxDelayTime uint) tokensRegistry { - return tokensRegistry{ - lock: sync.Mutex{}, - tokens: map[string]tokenInfo{}, - throttlingTime: time.Millisecond * time.Duration(requestThrottlingTime), - throttlingTimeForGET: time.Millisecond * time.Duration(requestThrottlingTimeForGET), - maxDelayTime: time.Second * time.Duration(maxDelayTime), - } -} - -// tokenInfo keeps the last request timestamp and information whether it was GET request -type tokenInfo struct { - getReq bool - timestamp time.Time -} - -// tokenRegistry keeps the timestamp of last handled request per token budget (appId or hash) -type tokensRegistry struct { - lock sync.Mutex - tokens map[string]tokenInfo - throttlingTime time.Duration - throttlingTimeForGET time.Duration - maxDelayTime time.Duration -} - -func (tr *tokensRegistry) getRequestWaitDuration(tokenBudgetName string, getReq bool) time.Duration { - var duration time.Duration - tr.lock.Lock() - defer tr.lock.Unlock() - toQueue := time.Now() - if t, exists := tr.tokens[tokenBudgetName]; exists { - toQueue, duration = tr.calculateRequestWaitDuration(t, toQueue, getReq) - } - tr.tokens[tokenBudgetName] = tokenInfo{getReq: getReq, timestamp: toQueue} - return duration -} - -func (tr *tokensRegistry) calculateRequestWaitDuration(lastRequest tokenInfo, toQueue time.Time, getReq bool) (time.Time, time.Duration) { - throttlingTime := tr.throttlingTime - // Previous request also was GET => use GET throttling time as a base - if lastRequest.getReq && getReq { - throttlingTime = tr.throttlingTimeForGET - } - duration := toQueue.Sub(lastRequest.timestamp) - - if toQueue.Before(lastRequest.timestamp) || toQueue.Equal(lastRequest.timestamp) { - // There is already queued request, queue next afterwards. - difference := throttlingTime - if getReq { - difference = tr.throttlingTimeForGET - } - future := lastRequest.timestamp.Add(difference) - duration = future.Sub(toQueue) - - // Do not exceed max wait time to avoid creating a huge request backlog if the GitHub api has performance issues - if duration >= tr.maxDelayTime { - duration = tr.maxDelayTime - future = toQueue.Add(tr.maxDelayTime) - } - toQueue = future - } else if duration >= throttlingTime || (getReq && duration >= tr.throttlingTimeForGET) { - // There was no request for some time, no need to wait. - duration = 0 - } else { - // There is a queued request, wait until the next throttling tick. - difference := throttlingTime - duration - if getReq && !lastRequest.getReq { - difference = tr.throttlingTimeForGET - duration - } - duration = difference - toQueue = toQueue.Add(duration) - } - return toQueue, duration -} - -// throttlingTransport throttles outbound concurrency from the proxy and adds QPS limit (1 request per given time) if enabled -type throttlingTransport struct { - sem *semaphore.Weighted - roundTripper http.RoundTripper - hasher ghmetrics.Hasher - timeThrottlingEnabled bool - registryApiV3 tokensRegistry - registryApiV4 tokensRegistry -} - -func (c *throttlingTransport) getTokenBudgetName(req *http.Request) string { - if val := req.Header.Get(TokenBudgetIdentifierHeader); val != "" { - return val - } - return c.hasher.Hash(req) -} - -func (c *throttlingTransport) holdRequest(req *http.Request) { - tokenBudgetName := c.getTokenBudgetName(req) - getReq := req.Method == http.MethodGet - var duration time.Duration - if strings.HasPrefix(req.URL.Path, "graphql") || strings.HasPrefix(req.URL.Path, "/graphql") { - duration = c.registryApiV4.getRequestWaitDuration(tokenBudgetName, getReq) - ghmetrics.CollectGitHubRequestWaitDurationMetrics(tokenBudgetName, req.Method, apiV4, duration) - } else { - duration = c.registryApiV3.getRequestWaitDuration(tokenBudgetName, getReq) - ghmetrics.CollectGitHubRequestWaitDurationMetrics(tokenBudgetName, req.Method, apiV3, duration) - } - if duration > 0 { - time.Sleep(duration) - } -} - -func (c *throttlingTransport) RoundTrip(req *http.Request) (*http.Response, error) { - pendingOutboundConnectionsGauge.Inc() - if c.timeThrottlingEnabled { - c.holdRequest(req) - } - - if err := c.sem.Acquire(context.Background(), 1); err != nil { - logrus.WithField("cache-key", req.URL.String()).WithError(err).Error("Internal error acquiring semaphore.") - return nil, err - } - defer c.sem.Release(1) - pendingOutboundConnectionsGauge.Dec() - outboundConcurrencyGauge.Inc() - defer outboundConcurrencyGauge.Dec() - return c.roundTripper.RoundTrip(req) -} - -// upstreamTransport changes response headers from upstream before they -// reach the cache layer in order to force the caching policy we require. -// -// By default github responds to PR requests with: -// -// Cache-Control: private, max-age=60, s-maxage=60 -// -// Which means the httpcache would not consider anything stale for 60 seconds. -// However, we want to always revalidate cache entries using ETags and last -// modified times so this RoundTripper overrides response headers to: -// -// Cache-Control: no-cache -// -// This instructs the cache to store the response, but always consider it stale. -type upstreamTransport struct { - roundTripper http.RoundTripper - hasher ghmetrics.Hasher -} - -func (u upstreamTransport) RoundTrip(req *http.Request) (*http.Response, error) { - etag := req.Header.Get("if-none-match") - var tokenBudgetName string - if val := req.Header.Get(TokenBudgetIdentifierHeader); val != "" { - tokenBudgetName = val - } else { - tokenBudgetName = u.hasher.Hash(req) - } - - reqStartTime := time.Now() - // Don't modify request, just pass to roundTripper. - resp, err := u.roundTripper.RoundTrip(req) - if err != nil { - ghmetrics.CollectRequestTimeoutMetrics(tokenBudgetName, req.URL.Path, req.Header.Get("User-Agent"), reqStartTime, time.Now()) - logrus.WithField("cache-key", req.URL.String()).WithError(err).Warn("Error from upstream (GitHub).") - return nil, err - } - responseTime := time.Now() - roundTripTime := responseTime.Sub(reqStartTime) - - if resp.StatusCode >= 400 { - // Don't store errors. They can't be revalidated to save API tokens. - resp.Header.Set("Cache-Control", "no-store") - } else { - resp.Header.Set("Cache-Control", "no-cache") - if resp.StatusCode != http.StatusNotModified { - // Used for metrics about the age of cached requests - resp.Header.Set(cacheEntryCreationDateHeader, strconv.Itoa(int(time.Now().Unix()))) - } - } - if etag != "" { - resp.Header.Set("X-Conditional-Request", etag) - } - - apiVersion := apiV3 - if strings.HasPrefix(req.URL.Path, "graphql") || strings.HasPrefix(req.URL.Path, "/graphql") { - resp.Header.Set("Cache-Control", "no-store") - apiVersion = apiV4 - } - - ghmetrics.CollectGitHubTokenMetrics(tokenBudgetName, apiVersion, resp.Header, reqStartTime, responseTime) - ghmetrics.CollectGitHubRequestMetrics(tokenBudgetName, req.URL.Path, strconv.Itoa(resp.StatusCode), req.Header.Get("User-Agent"), roundTripTime.Seconds()) - - return resp, nil -} - -const LogMessageWithDiskPartitionFields = "Not using a partitioned cache because legacyDisablePartitioningByAuthHeader is true" - -// NewDiskCache creates a GitHub cache RoundTripper that is backed by a disk -// cache. -// It supports a partitioned cache. -func NewDiskCache(roundTripper http.RoundTripper, cacheDir string, cacheSizeGB, maxConcurrency int, legacyDisablePartitioningByAuthHeader bool, cachePruneInterval time.Duration, throttlingTimes RequestThrottlingTimes) http.RoundTripper { - if legacyDisablePartitioningByAuthHeader { - diskCache := diskcache.NewWithDiskv( - diskv.New(diskv.Options{ - BasePath: path.Join(cacheDir, "data"), - TempDir: path.Join(cacheDir, "temp"), - CacheSizeMax: uint64(cacheSizeGB) * uint64(1000000000), // convert G to B - })) - return NewFromCache(roundTripper, - func(partitionKey string, _ *time.Time) httpcache.Cache { - logrus.WithField("cache-base-path", path.Join(cacheDir, "data", partitionKey)). - WithField("cache-temp-path", path.Join(cacheDir, "temp", partitionKey)). - Warning(LogMessageWithDiskPartitionFields) - return diskCache - }, - maxConcurrency, - throttlingTimes, - ) - } - - go func() { - for range time.NewTicker(cachePruneInterval).C { - Prune(cacheDir, time.Now) - } - }() - return NewFromCache(roundTripper, - func(partitionKey string, expiresAt *time.Time) httpcache.Cache { - basePath := path.Join(cacheDir, "data", partitionKey) - tempDir := path.Join(cacheDir, "temp", partitionKey) - if err := writecachePartitionMetadata(basePath, tempDir, expiresAt); err != nil { - logrus.WithError(err).Warn("Failed to write cache metadata file, pruning will not work") - } - return diskcache.NewWithDiskv( - diskv.New(diskv.Options{ - BasePath: basePath, - TempDir: tempDir, - CacheSizeMax: uint64(cacheSizeGB) * uint64(1000000000), // convert G to B - })) - }, - maxConcurrency, - throttlingTimes, - ) -} - -func Prune(baseDir string, now func() time.Time) { - // All of this would be easier if the structure was base/partition/{data,temp} - // but because of compatibility we can not change it. - for _, dir := range []string{"data", "temp"} { - base := path.Join(baseDir, dir) - cachePartitionCandidates, err := os.ReadDir(base) - if err != nil { - logrus.WithError(err).Warn("os.ReadDir failed") - // no continue, os.ReadDir returns partial results if it encounters an error - } - for _, cachePartitionCandidate := range cachePartitionCandidates { - if !cachePartitionCandidate.IsDir() { - continue - } - metadataPath := path.Join(base, cachePartitionCandidate.Name(), cachePartitionMetadataFileName) - - // Read optimistically and just ignore errors - raw, err := os.ReadFile(metadataPath) - if err != nil { - continue - } - var metadata cachePartitionMetadata - if err := json.Unmarshal(raw, &metadata); err != nil { - logrus.WithError(err).WithField("filepath", metadataPath).Error("failed to deserialize metadata file") - continue - } - if metadata.ExpiresAt.After(now()) { - continue - } - paritionPath := filepath.Dir(metadataPath) - logrus.WithField("path", paritionPath).WithField("expiresAt", metadata.ExpiresAt.String()).Info("Cleaning up expired cache parition") - if err := os.RemoveAll(paritionPath); err != nil { - logrus.WithError(err).WithField("path", paritionPath).Error("failed to delete expired cache parition") - } - } - } -} - -func writecachePartitionMetadata(basePath, tempDir string, expiresAt *time.Time) error { - // No expiry header for the token was passed, likely it is a PAT which never expires. - if expiresAt == nil { - return nil - } - metadata := cachePartitionMetadata{ExpiresAt: metav1.Time{Time: *expiresAt}} - serialized, err := json.Marshal(metadata) - if err != nil { - return fmt.Errorf("failed to serialize: %w", err) - } - - var errs []error - for _, destBase := range []string{basePath, tempDir} { - if err := os.MkdirAll(destBase, 0755); err != nil { - errs = append(errs, fmt.Errorf("failed to create dir %s: %w", destBase, err)) - } - dest := path.Join(destBase, cachePartitionMetadataFileName) - if err := os.WriteFile(dest, serialized, 0644); err != nil { - errs = append(errs, fmt.Errorf("failed to write %s: %w", dest, err)) - } - } - - return utilerrors.NewAggregate(errs) -} - -const cachePartitionMetadataFileName = ".cache_metadata.json" - -type cachePartitionMetadata struct { - ExpiresAt metav1.Time `json:"expires_at"` -} - -// NewMemCache creates a GitHub cache RoundTripper that is backed by a memory -// cache. -// It supports a partitioned cache. -func NewMemCache(roundTripper http.RoundTripper, maxConcurrency int, throttlingTimes RequestThrottlingTimes) http.RoundTripper { - return NewFromCache(roundTripper, - func(_ string, _ *time.Time) httpcache.Cache { return httpcache.NewMemoryCache() }, - maxConcurrency, - throttlingTimes) -} - -// CachePartitionCreator creates a new cache partition using the given key -type CachePartitionCreator func(partitionKey string, expiresAt *time.Time) httpcache.Cache - -// NewFromCache creates a GitHub cache RoundTripper that is backed by the -// specified httpcache.Cache implementation. -func NewFromCache(roundTripper http.RoundTripper, cache CachePartitionCreator, maxConcurrency int, throttlingTimes RequestThrottlingTimes) http.RoundTripper { - hasher := ghmetrics.NewCachingHasher() - return newPartitioningRoundTripper(func(partitionKey string, expiresAt *time.Time) http.RoundTripper { - cacheTransport := httpcache.NewTransport(cache(partitionKey, expiresAt)) - cacheTransport.Transport = newThrottlingTransport(maxConcurrency, upstreamTransport{roundTripper: roundTripper, hasher: hasher}, hasher, throttlingTimes) - return &requestCoalescer{ - cache: make(map[string]*firstRequest), - requestExecutor: cacheTransport, - hasher: hasher, - } - }) -} - -// NewRedisCache creates a GitHub cache RoundTripper that is backed by a Redis -// cache. -// Important note: The redis implementation does not support partitioning the cache -// which means that requests to the same path from different tokens will invalidate -// each other. -func NewRedisCache(roundTripper http.RoundTripper, redisAddress string, maxConcurrency int, throttlingTimes RequestThrottlingTimes) http.RoundTripper { - conn, err := redis.Dial("tcp", redisAddress) - if err != nil { - logrus.WithError(err).Fatal("Error connecting to Redis") - } - redisCache := rediscache.NewWithClient(conn) - return NewFromCache(roundTripper, - func(_ string, _ *time.Time) httpcache.Cache { return redisCache }, - maxConcurrency, - throttlingTimes) -} diff --git a/third_party/k8s.io/test-infra/ghproxy/ghcache/partitioner.go b/third_party/k8s.io/test-infra/ghproxy/ghcache/partitioner.go deleted file mode 100644 index 2831893..0000000 --- a/third_party/k8s.io/test-infra/ghproxy/ghcache/partitioner.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ghcache - -import ( - "crypto/sha256" - "fmt" - "net/http" - "sync" - "time" - - "github.com/sirupsen/logrus" -) - -type roundTripperCreator func(partitionKey string, expiresAt *time.Time) http.RoundTripper - -// partitioningRoundTripper is a http.RoundTripper -var _ http.RoundTripper = &partitioningRoundTripper{} - -func newPartitioningRoundTripper(rtc roundTripperCreator) *partitioningRoundTripper { - return &partitioningRoundTripper{ - roundTripperCreator: rtc, - lock: &sync.Mutex{}, - roundTrippers: map[string]http.RoundTripper{}, - } -} - -type partitioningRoundTripper struct { - roundTripperCreator roundTripperCreator - lock *sync.Mutex - roundTrippers map[string]http.RoundTripper -} - -func getCachePartition(r *http.Request) string { - // Hash the key to make sure we dont leak it into the directory layout - return fmt.Sprintf("%x", sha256.Sum256([]byte(r.Header.Get("Authorization")))) -} - -func getExpiry(r *http.Request) *time.Time { - raw := r.Header.Get(TokenExpiryAtHeader) - if raw == "" { - return nil - } - parsed, err := time.Parse(time.RFC3339, raw) - if err != nil { - logrus.WithError(err).WithFields(logrus.Fields{ - "path": r.URL.Path, - "raw_value": raw, - "user-agent": r.Header.Get("User-Agent"), - }).Errorf("failed to parse value of %s header as RFC3339 time", TokenExpiryAtHeader) - return nil - } - return &parsed -} - -func (prt *partitioningRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { - cachePartition := getCachePartition(r) - expiresAt := getExpiry(r) - - prt.lock.Lock() - roundTripper, found := prt.roundTrippers[cachePartition] - if !found { - logrus.WithField("cache-parition-key", cachePartition).Info("Creating a new cache for partition") - cachePartitionsCounter.WithLabelValues(cachePartition).Add(1) - prt.roundTrippers[cachePartition] = prt.roundTripperCreator(cachePartition, expiresAt) - roundTripper = prt.roundTrippers[cachePartition] - } - prt.lock.Unlock() - - return roundTripper.RoundTrip(r) -} diff --git a/third_party/k8s.io/test-infra/ghproxy/ghmetrics/ghmetrics.go b/third_party/k8s.io/test-infra/ghproxy/ghmetrics/ghmetrics.go deleted file mode 100644 index 2dc253f..0000000 --- a/third_party/k8s.io/test-infra/ghproxy/ghmetrics/ghmetrics.go +++ /dev/null @@ -1,196 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ghmetrics - -import ( - "net/http" - "strconv" - "strings" - "sync" - "time" - - "github.com/prometheus/client_golang/prometheus" - "github.com/sirupsen/logrus" -) - -// ghTokenUntilResetGaugeVec provides the 'github_token_reset' gauge that -// enables keeping track of GitHub reset times. -var ghTokenUntilResetGaugeVec = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Name: "github_token_reset", - Help: "Last reported GitHub token reset time.", - }, - []string{"token_hash", "api_version", "ratelimit_resource"}, -) - -// ghTokenUsageGaugeVec provides the 'github_token_usage' gauge that -// enables keeping track of GitHub calls and quotas. -var ghTokenUsageGaugeVec = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Name: "github_token_usage", - Help: "How many GitHub token requets are remaining for the current hour.", - }, - []string{"token_hash", "api_version", "ratelimit_resource"}, -) - -// ghRequestDurationHistVec provides the 'github_request_duration' histogram that keeps track -// of the duration of GitHub requests by API path. -var ghRequestDurationHistVec = prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Name: "github_request_duration", - Help: "GitHub request duration by API path.", - Buckets: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10}, - }, - []string{"token_hash", "path", "status", "user_agent"}, -) - -// ghRequestDurationHistVec provides the 'github_request_duration' histogram that keeps track -// of the duration of GitHub requests by API path. -var ghRequestWaitDurationHistVec = prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Name: "github_request_wait_duration_seconds", - Help: "GitHub request wait duration before sending to API in seconds", - Buckets: []float64{0.1, 0.25, 0.5, 1, 2.5, 5, 7.5, 10, 15, 20, 25, 30, 45, 60, 90, 120, 150, 180}, - }, - []string{"token_hash", "request_type", "api"}, -) - -// cacheCounter provides the 'ghcache_responses' counter vec that is indexed -// by the cache response mode. -var cacheCounter = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Name: "ghcache_responses", - Help: "How many cache responses of each cache response mode there are.", - }, - []string{"mode", "path", "user_agent", "token_hash"}, -) - -// timeoutDuration provides the 'github_request_timeouts' histogram that keeps -// track of the timeouts of GitHub requests by API path. -var timeoutDuration = prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Name: "github_request_timeouts", - Help: "GitHub request timeout by API path.", - Buckets: []float64{45, 60, 90, 120, 300}, - }, - []string{"token_hash", "path", "user_agent"}, -) - -// cacheEntryAge tells us about the age of responses -// that came from the cache. -var cacheEntryAge = prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Name: "ghcache_cache_entry_age_seconds", - Help: "The age of cache entries by API path.", - Buckets: []float64{5, 900, 1800, 3600, 7200, 14400}, - }, - []string{"token_hash", "path", "user_agent"}, -) - -var muxTokenUsage sync.Mutex -var lastGitHubResponse time.Time - -func init() { - prometheus.MustRegister(ghTokenUntilResetGaugeVec) - prometheus.MustRegister(ghTokenUsageGaugeVec) - prometheus.MustRegister(ghRequestDurationHistVec) - prometheus.MustRegister(ghRequestWaitDurationHistVec) - prometheus.MustRegister(cacheCounter) - prometheus.MustRegister(timeoutDuration) - prometheus.MustRegister(cacheEntryAge) -} - -// CollectGitHubTokenMetrics publishes the rate limits of the github api to -// `github_token_usage` as well as `github_token_reset` on prometheus. -func CollectGitHubTokenMetrics(tokenHash, apiVersion string, headers http.Header, reqStartTime, responseTime time.Time) { - remaining := headers.Get("X-RateLimit-Remaining") - if remaining == "" { - return - } - resource := headers.Get("X-RateLimit-Resource") - timeUntilReset := timestampStringToTime(headers.Get("X-RateLimit-Reset")) - durationUntilReset := timeUntilReset.Sub(reqStartTime) - - remainingFloat, err := strconv.ParseFloat(remaining, 64) - if err != nil { - logrus.WithError(err).Infof("Couldn't convert number of remaining token requests into gauge value (float)") - } - if remainingFloat == 0 { - logrus.WithFields(logrus.Fields{ - "header": remaining, - "user-agent": headers.Get("User-Agent"), - }).Debug("Parsed GitHub header as indicating no remaining rate-limit.") - } - - muxTokenUsage.Lock() - isAfter := lastGitHubResponse.After(responseTime) - if !isAfter { - lastGitHubResponse = responseTime - } - muxTokenUsage.Unlock() - if isAfter { - logrus.WithField("last-github-response", lastGitHubResponse).WithField("response-time", responseTime).Debug("Previously pushed metrics of a newer response, skipping old metrics") - } else { - ghTokenUntilResetGaugeVec.With(prometheus.Labels{"token_hash": tokenHash, "api_version": apiVersion, "ratelimit_resource": resource}).Set(float64(durationUntilReset.Nanoseconds())) - ghTokenUsageGaugeVec.With(prometheus.Labels{"token_hash": tokenHash, "api_version": apiVersion, "ratelimit_resource": resource}).Set(remainingFloat) - } -} - -// CollectGitHubRequestMetrics publishes the number of requests by API path to -// `github_requests` on prometheus. -func CollectGitHubRequestMetrics(tokenHash, path, statusCode, userAgent string, roundTripTime float64) { - ghRequestDurationHistVec.With(prometheus.Labels{"token_hash": tokenHash, "path": simplifier.Simplify(path), "status": statusCode, "user_agent": userAgentWithoutVersion(userAgent)}).Observe(roundTripTime) -} - -// timestampStringToTime takes a unix timestamp and returns a `time.Time` -// from the given time. -func timestampStringToTime(tstamp string) time.Time { - timestamp, err := strconv.ParseInt(tstamp, 10, 64) - if err != nil { - logrus.WithField("timestamp", tstamp).Info("Couldn't convert unix timestamp") - } - return time.Unix(timestamp, 0) -} - -// userAgentWithouVersion formats a user agent without the version to reduce label cardinality -func userAgentWithoutVersion(userAgent string) string { - if !strings.Contains(userAgent, "/") { - return userAgent - } - return strings.SplitN(userAgent, "/", 2)[0] -} - -// CollectCacheRequestMetrics records a cache outcome for a specific path -func CollectCacheRequestMetrics(mode, path, userAgent, tokenHash string) { - cacheCounter.With(prometheus.Labels{"mode": mode, "path": simplifier.Simplify(path), "user_agent": userAgentWithoutVersion(userAgent), "token_hash": tokenHash}).Inc() -} - -func CollectCacheEntryAgeMetrics(age float64, path, userAgent, tokenHash string) { - cacheEntryAge.With(prometheus.Labels{"path": simplifier.Simplify(path), "user_agent": userAgentWithoutVersion(userAgent), "token_hash": tokenHash}).Observe(age) -} - -// CollectRequestTimeoutMetrics publishes the duration of timed-out requests by -// API path to 'github_request_timeouts' on prometheus. -func CollectRequestTimeoutMetrics(tokenHash, path, userAgent string, reqStartTime, responseTime time.Time) { - timeoutDuration.With(prometheus.Labels{"token_hash": tokenHash, "path": simplifier.Simplify(path), "user_agent": userAgentWithoutVersion(userAgent)}).Observe(float64(responseTime.Sub(reqStartTime).Seconds())) -} - -// CollectGitHubRequestWaitDurationMetrics publishes the wait duration of requests -// before sending to respective GitHub API on prometheus. -func CollectGitHubRequestWaitDurationMetrics(tokenHash, requestType, api string, duration time.Duration) { - ghRequestWaitDurationHistVec.With(prometheus.Labels{"token_hash": tokenHash, "request_type": requestType, "api": api}).Observe(duration.Seconds()) -} diff --git a/third_party/k8s.io/test-infra/ghproxy/ghmetrics/ghpath.go b/third_party/k8s.io/test-infra/ghproxy/ghmetrics/ghpath.go deleted file mode 100644 index f06084e..0000000 --- a/third_party/k8s.io/test-infra/ghproxy/ghmetrics/ghpath.go +++ /dev/null @@ -1,181 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ghmetrics - -import ( - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/simplifypath" -) - -func repositoryTree() []simplifypath.Node { - return []simplifypath.Node{ - l("branches", v("branch", l("protection", - l("restrictions", l("users"), l("teams")), - l("required_status_checks", l("contexts")), - l("required_pull_request_reviews"), - l("required_signatures"), - l("enforce_admins")))), - l("issues", - l("comments", v("commentId")), - l("events", v("eventId")), - v("issueId", - l("lock"), - l("comments"), - l("events"), - l("assignees"), - l("reactions"), - l("labels", simplifypath.VGreedy("labelId")))), - l("keys", v("keyId")), - l("labels", v("labelId")), - l("milestones", v("milestone")), - l("pulls", - v("pullId", - l("commits"), - l("files"), - l("comments"), - l("reviews"), - l("requested_reviewers"), - l("merge"))), - l("releases", v("releaseId")), - l("statuses", v("statusId")), - l("subscribers", v("subscriberId")), - l("assignees", v("assigneeId")), - l("archive", v("zip")), - l("collaborators", v("collaboratorId", l("permission"))), - l("comments", v("commentId")), - l("compare", v("sha")), - l("contents", v("contentId")), - l("commits", - v("sha", - l("check-runs"), - l("status")), - ), - l("git", - l("commits", v("sha")), - l("ref", v("refId")), - l("tags", v("tagId")), - l("trees", v("sha")), - l("refs", l("heads", v("ref")))), - l("stars"), - l("merges"), - l("stargazers"), - l("notifications"), - l("hooks"), - l("deployments"), - l("downloads"), - l("events"), - l("forks"), - l("topics"), - l("vulnerability-alerts"), - l("automated-security-fixes"), - l("contributors"), - l("languages"), - l("teams"), - l("tags"), - l("transfer"), - } -} - -func organizationTree() []simplifypath.Node { - return []simplifypath.Node{ - l("credential-authorizations", v("credentialId")), - l("repos"), - l("issues"), - l("invitations"), - l("members", v("login")), - l("memberships", v("login")), - l("teams"), - l("team", v("teamId", - l("repos"), - l("members"))), - } -} - -var simplifier = simplifypath.NewSimplifier(l("", // shadow element mimicing the root - l(""), - l("app", l("installations", v("id", l("access_tokens")))), - l("repos", - v("owner", - v("repo", - repositoryTree()...))), - l("repositories", - v("repoId", - repositoryTree()...)), - l("user", - l("following", v("userId")), - l("keys", v("keyId")), - l("email", l("visibility")), - l("emails"), - l("public_emails"), - l("followers"), - l("starred"), - l("issues"), - v("id", l("repos")), - ), - l("users", - v("username", - l("followers", v("username")), - l("repos"), - l("hovercard"), - l("following"))), - l("orgs", - v("orgname", - organizationTree()...)), - l("organizations", - v("orgId", - organizationTree()...)), - l("organizations", - v("orgId", - l("members"), - l("repos"), - l("teams"))), - l("issues", v("issueId")), - l("search", - l("repositories"), - l("commits"), - l("code"), - l("issues"), - l("users"), - l("topics"), - l("labels")), - l("gists", - l("public"), - l("starred")), - l("notifications", l("threads", v("threadId", l("subscription")))), - l("emojis"), - l("events"), - l("feeds"), - l("hub"), - l("rate_limit"), - l("teams", v("id", - l("members"), - l("memberships", v("user")), - l("repos", v("org", v("repo"))), - l("invitations"), - )), - // end point for gh api v4 - l("graphql"), - l("licenses"))) - -// l and v keep the tree legible - -func l(fragment string, children ...simplifypath.Node) simplifypath.Node { - return simplifypath.L(fragment, children...) -} - -func v(fragment string, children ...simplifypath.Node) simplifypath.Node { - return simplifypath.V(fragment, children...) -} diff --git a/third_party/k8s.io/test-infra/ghproxy/ghmetrics/hash.go b/third_party/k8s.io/test-infra/ghproxy/ghmetrics/hash.go deleted file mode 100644 index 028c314..0000000 --- a/third_party/k8s.io/test-infra/ghproxy/ghmetrics/hash.go +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ghmetrics - -import ( - "crypto/sha256" - "fmt" - "net/http" - "sync" - - "github.com/sirupsen/logrus" -) - -// Hasher knows how to hash an authorization header from a request -type Hasher interface { - Hash(req *http.Request) string -} - -func NewCachingHasher() Hasher { - return &cachingHasher{ - lock: sync.RWMutex{}, - hashes: map[string]string{}, - } -} - -type cachingHasher struct { - lock sync.RWMutex - hashes map[string]string -} - -func (h *cachingHasher) Hash(req *http.Request) string { - // get authorization header to convert to sha256 - authHeader := req.Header.Get("Authorization") - if authHeader == "" { - logrus.Warn("Couldn't retrieve 'Authorization' header, adding to unknown bucket") - authHeader = "unknown" - } - h.lock.RLock() - hash, cached := h.hashes[authHeader] - h.lock.RUnlock() - if cached { - return hash - } - - h.lock.Lock() - hash = fmt.Sprintf("%x", sha256.Sum256([]byte(authHeader))) // use %x to make this a utf-8 string for use as a label - h.hashes[authHeader] = hash - h.lock.Unlock() - return hash -} diff --git a/third_party/k8s.io/test-infra/prow/config/org/org.go b/third_party/k8s.io/test-infra/prow/config/org/org.go deleted file mode 100644 index bf14dfe..0000000 --- a/third_party/k8s.io/test-infra/prow/config/org/org.go +++ /dev/null @@ -1,176 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package org - -import ( - "fmt" - - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/github" -) - -// FullConfig stores the full configuration to be used by the tool, mapping -// orgs to their configuration at the top level under an `orgs` key. -type FullConfig struct { - Orgs map[string]Config `json:"orgs,omitempty"` -} - -// Metadata declares metadata about the GitHub org. -// -// See https://developer.github.com/v3/orgs/#edit-an-organization -type Metadata struct { - BillingEmail *string `json:"billing_email,omitempty"` - Company *string `json:"company,omitempty"` - Email *string `json:"email,omitempty"` - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - Location *string `json:"location,omitempty"` - HasOrganizationProjects *bool `json:"has_organization_projects,omitempty"` - HasRepositoryProjects *bool `json:"has_repository_projects,omitempty"` - DefaultRepositoryPermission *github.RepoPermissionLevel `json:"default_repository_permission,omitempty"` - MembersCanCreateRepositories *bool `json:"members_can_create_repositories,omitempty"` -} - -// RepoCreateOptions declares options for creating new repos -// See https://developer.github.com/v3/repos/#create -type RepoCreateOptions struct { - AutoInit *bool `json:"auto_init,omitempty"` - GitignoreTemplate *string `json:"gitignore_template,omitempty"` - LicenseTemplate *string `json:"license_template,omitempty"` -} - -// Repo declares metadata about the GitHub repository -// -// See https://developer.github.com/v3/repos/#edit -type Repo struct { - Description *string `json:"description,omitempty"` - HomePage *string `json:"homepage,omitempty"` - Private *bool `json:"private,omitempty"` - HasIssues *bool `json:"has_issues,omitempty"` - HasProjects *bool `json:"has_projects,omitempty"` - HasWiki *bool `json:"has_wiki,omitempty"` - AllowSquashMerge *bool `json:"allow_squash_merge,omitempty"` - AllowMergeCommit *bool `json:"allow_merge_commit,omitempty"` - AllowRebaseMerge *bool `json:"allow_rebase_merge,omitempty"` - SquashMergeCommitTitle *string `json:"squash_merge_commit_title,omitempty"` - SquashMergeCommitMessage *string `json:"squash_merge_commit_message,omitempty"` - - DefaultBranch *string `json:"default_branch,omitempty"` - Archived *bool `json:"archived,omitempty"` - - Previously []string `json:"previously,omitempty"` - - OnCreate *RepoCreateOptions `json:"on_create,omitempty"` -} - -// Config declares org metadata as well as its people and teams. -type Config struct { - Metadata - Teams map[string]Team `json:"teams,omitempty"` - Members []string `json:"members,omitempty"` - Admins []string `json:"admins,omitempty"` - Repos map[string]Repo `json:"repos,omitempty"` -} - -// TeamMetadata declares metadata about the github team. -// -// See https://developer.github.com/v3/teams/#edit-team -type TeamMetadata struct { - Description *string `json:"description,omitempty"` - Privacy *Privacy `json:"privacy,omitempty"` -} - -// Team declares metadata as well as its poeple. -type Team struct { - TeamMetadata - Members []string `json:"members,omitempty"` - Maintainers []string `json:"maintainers,omitempty"` - Children map[string]Team `json:"teams,omitempty"` - - Previously []string `json:"previously,omitempty"` - - // This is injected to the Team structure by listing privilege - // levels on dump and if set by users will cause privileges to - // be added on sync. - // https://developer.github.com/v3/teams/#list-team-repos - // https://developer.github.com/v3/teams/#add-or-update-team-repository - Repos map[string]github.RepoPermissionLevel `json:"repos,omitempty"` -} - -// Privacy is secret or closed. -// -// See https://developer.github.com/v3/teams/#edit-team -type Privacy string - -const ( - // Closed means it is only visible to org members - Closed Privacy = "closed" - // Secret means it is only visible to team members. - Secret Privacy = "secret" -) - -var privacySettings = map[Privacy]bool{ - Closed: true, - Secret: true, -} - -// MarshalText returns bytes that equal secret or closed -func (p Privacy) MarshalText() ([]byte, error) { - return []byte(p), nil -} - -// UnmarshalText returns an error if text != secret or closed -func (p *Privacy) UnmarshalText(text []byte) error { - v := Privacy(text) - if _, ok := privacySettings[v]; !ok { - return fmt.Errorf("bad privacy setting: %s", v) - } - *p = v - return nil -} - -// PruneRepoDefaults finds values in org.Repo config that matches the default -// values replaces them with nil pointer. This reduces the size of an org dump -// by omitting the fields that would be set to the same value when not set at all. -// See https://developer.github.com/v3/repos/#edit -func PruneRepoDefaults(repo Repo) Repo { - pruneString := func(p **string, def string) { - if *p != nil && **p == def { - *p = nil - } - } - pruneBool := func(p **bool, def bool) { - if *p != nil && **p == def { - *p = nil - } - } - - pruneString(&repo.Description, "") - pruneString(&repo.HomePage, "") - - pruneBool(&repo.Private, false) - pruneBool(&repo.HasIssues, true) - // Projects' defaults depend on org setting, do not prune - pruneBool(&repo.HasWiki, true) - pruneBool(&repo.AllowRebaseMerge, true) - pruneBool(&repo.AllowSquashMerge, true) - pruneBool(&repo.AllowMergeCommit, true) - - pruneBool(&repo.Archived, false) - pruneString(&repo.DefaultBranch, "master") - - return repo -} diff --git a/third_party/k8s.io/test-infra/prow/config/secret/agent.go b/third_party/k8s.io/test-infra/prow/config/secret/agent.go deleted file mode 100644 index 43e80e3..0000000 --- a/third_party/k8s.io/test-infra/prow/config/secret/agent.go +++ /dev/null @@ -1,183 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package secret implements an agent to read and reload the secrets. -package secret - -import ( - "fmt" - "sync" - - "github.com/sirupsen/logrus" - "k8s.io/apimachinery/pkg/util/sets" - - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/logrusutil" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/secretutil" -) - -// secretAgent is the singleton that loads secrets for us -var secretAgent *agent - -func init() { - secretAgent = &agent{ - secretsMap: map[string]secretReloader{}, - ReloadingCensorer: secretutil.NewCensorer(), - } - logrus.SetFormatter(logrusutil.NewFormatterWithCensor(logrus.StandardLogger().Formatter, secretAgent.ReloadingCensorer)) -} - -// Start creates goroutines to monitor the files that contain the secret value. -// Additionally, Start wraps the current standard logger formatter with a -// censoring formatter that removes secret occurrences from the logs. -func (a *agent) Start(paths []string) error { - a.secretsMap = make(map[string]secretReloader, len(paths)) - a.ReloadingCensorer = secretutil.NewCensorer() - - for _, path := range paths { - if err := a.Add(path); err != nil { - return fmt.Errorf("failed to load secret at %s: %w", path, err) - } - } - - logrus.SetFormatter(logrusutil.NewFormatterWithCensor(logrus.StandardLogger().Formatter, a.ReloadingCensorer)) - - return nil -} - -// Add registers a new path to the agent. -func Add(paths ...string) error { - for _, path := range paths { - if err := secretAgent.Add(path); err != nil { - return err - } - } - return nil -} - -// AddWithParser registers a new path to the agent. The secret will only be updated if it can -// be successfully parsed. The returned getter must be kept, as it is the only way of accessing -// the typed secret. -func AddWithParser[T any](path string, parsingFN func([]byte) (T, error)) (func() T, error) { - loader := &parsingSecretReloader[T]{ - path: path, - parsingFN: parsingFN, - } - return loader.get, secretAgent.add(path, loader) -} - -// GetSecret returns the value of a secret stored in a map. -func GetSecret(secretPath string) []byte { - return secretAgent.GetSecret(secretPath) -} - -// GetTokenGenerator returns a function that gets the value of a given secret. -func GetTokenGenerator(secretPath string) func() []byte { - return func() []byte { - return GetSecret(secretPath) - } -} - -func Censor(content []byte) []byte { - return secretAgent.Censor(content) -} - -// agent watches a path and automatically loads the secrets stored. -type agent struct { - sync.RWMutex - secretsMap map[string]secretReloader - *secretutil.ReloadingCensorer -} - -type secretReloader interface { - getRaw() []byte - start(reloadCensor func()) error -} - -// Add registers a new path to the agent. -func (a *agent) Add(path string) error { - return a.add(path, &parsingSecretReloader[[]byte]{ - path: path, - parsingFN: func(b []byte) ([]byte, error) { return b, nil }, - }) -} - -func (a *agent) add(path string, loader secretReloader) error { - if err := loader.start(a.refreshCensorer); err != nil { - return err - } - - a.setSecret(path, loader) - - return nil -} - -// GetSecret returns the value of a secret stored in a map. -func (a *agent) GetSecret(secretPath string) []byte { - a.RLock() - defer a.RUnlock() - if val, set := a.secretsMap[secretPath]; set { - return val.getRaw() - } - return nil -} - -// setSecret sets a value in a map of secrets. -func (a *agent) setSecret(secretPath string, secretValue secretReloader) { - a.Lock() - a.secretsMap[secretPath] = secretValue - a.Unlock() - a.refreshCensorer() -} - -// refreshCensorer should be called when the secrets map changes -func (a *agent) refreshCensorer() { - var secrets [][]byte - a.RLock() - for _, value := range a.secretsMap { - secrets = append(secrets, value.getRaw()) - } - a.RUnlock() - a.ReloadingCensorer.RefreshBytes(secrets...) -} - -// GetTokenGenerator returns a function that gets the value of a given secret. -func (a *agent) GetTokenGenerator(secretPath string) func() []byte { - return func() []byte { - return a.GetSecret(secretPath) - } -} - -// Censor replaces sensitive parts of the content with a placeholder. -func (a *agent) Censor(content []byte) []byte { - a.RLock() - defer a.RUnlock() - if a.ReloadingCensorer == nil { - // there's no constructor for an agent so we can't ensure that everyone is - // trying to censor *after* actually loading a secret ... - return content - } - return secretutil.AdaptCensorer(a.ReloadingCensorer)(content) -} - -func (a *agent) getSecrets() sets.Set[string] { - a.RLock() - defer a.RUnlock() - secrets := sets.New[string]() - for _, v := range a.secretsMap { - secrets.Insert(string(v.getRaw())) - } - return secrets -} diff --git a/third_party/k8s.io/test-infra/prow/config/secret/reloader.go b/third_party/k8s.io/test-infra/prow/config/secret/reloader.go deleted file mode 100644 index 6734191..0000000 --- a/third_party/k8s.io/test-infra/prow/config/secret/reloader.go +++ /dev/null @@ -1,99 +0,0 @@ -/* -Copyright 2022 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package secret - -import ( - "os" - "sync" - "time" - - "github.com/sirupsen/logrus" -) - -type parsingSecretReloader[T any] struct { - lock sync.RWMutex - path string - rawValue []byte - parsed T - parsingFN func([]byte) (T, error) -} - -func (p *parsingSecretReloader[T]) start(reloadCensor func()) error { - raw, parsed, err := loadSingleSecretWithParser(p.path, p.parsingFN) - if err != nil { - return err - } - p.lock.Lock() - p.rawValue = raw - p.parsed = parsed - p.lock.Unlock() - reloadCensor() - - go p.reloadSecret(reloadCensor) - return nil -} - -func (p *parsingSecretReloader[T]) reloadSecret(reloadCensor func()) { - var lastModTime time.Time - logger := logrus.NewEntry(logrus.StandardLogger()) - - skips := 0 - for range time.Tick(1 * time.Second) { - if skips < 600 { - // Check if the file changed to see if it needs to be re-read. - secretStat, err := os.Stat(p.path) - if err != nil { - logger.WithField("secret-path", p.path).WithError(err).Error("Error loading secret file.") - continue - } - - recentModTime := secretStat.ModTime() - if !recentModTime.After(lastModTime) { - skips++ - continue // file hasn't been modified - } - lastModTime = recentModTime - } - - raw, parsed, err := loadSingleSecretWithParser(p.path, p.parsingFN) - if err != nil { - logger.WithField("secret-path", p.path).WithError(err).Error("Error loading secret.") - continue - } - - p.lock.Lock() - p.rawValue = raw - p.parsed = parsed - p.lock.Unlock() - reloadCensor() - - skips = 0 - } - -} - -func (p *parsingSecretReloader[T]) getRaw() []byte { - p.lock.RLock() - defer p.lock.RUnlock() - return p.rawValue -} - -func (p *parsingSecretReloader[T]) get() T { - p.lock.RLock() - defer p.lock.RUnlock() - return p.parsed -} diff --git a/third_party/k8s.io/test-infra/prow/config/secret/secret.go b/third_party/k8s.io/test-infra/prow/config/secret/secret.go deleted file mode 100644 index bd01e85..0000000 --- a/third_party/k8s.io/test-infra/prow/config/secret/secret.go +++ /dev/null @@ -1,41 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package secret - -import ( - "bytes" - "fmt" - "os" -) - -// loadSingleSecret reads and returns the value of a single file. -func loadSingleSecret(path string) ([]byte, error) { - b, err := os.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("error reading %s: %w", path, err) - } - return bytes.TrimSpace(b), nil -} - -func loadSingleSecretWithParser[T any](path string, parsingFN func([]byte) (T, error)) ([]byte, T, error) { - raw, err := loadSingleSecret(path) - if err != nil { - return nil, *(new(T)), err - } - parsed, err := parsingFN(raw) - return raw, parsed, err -} diff --git a/third_party/k8s.io/test-infra/prow/flagutil/bool.go b/third_party/k8s.io/test-infra/prow/flagutil/bool.go deleted file mode 100644 index b97e0ce..0000000 --- a/third_party/k8s.io/test-infra/prow/flagutil/bool.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package flagutil - -import ( - "strconv" -) - -// Bool holds a boolean flag value, tracking whether it was set explicitly. -type Bool struct { - Value bool - Explicit bool -} - -// IsBoolFlag causes golang to consider --foo to mean --foo=true -func (b *Bool) IsBoolFlag() bool { - return true -} - -// String value of the string. -func (b *Bool) String() string { - return strconv.FormatBool(b.Value) -} - -// Set the bool according to the string. -func (b *Bool) Set(s string) error { - v, err := strconv.ParseBool(s) - if err != nil { - return err - } - b.Explicit = true - b.Value = v - return nil -} diff --git a/third_party/k8s.io/test-infra/prow/flagutil/doc.go b/third_party/k8s.io/test-infra/prow/flagutil/doc.go deleted file mode 100644 index e9e08a8..0000000 --- a/third_party/k8s.io/test-infra/prow/flagutil/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package flagutil contains utilities and interfaces shared between -// several Prow commands. -package flagutil diff --git a/third_party/k8s.io/test-infra/prow/flagutil/git.go b/third_party/k8s.io/test-infra/prow/flagutil/git.go deleted file mode 100644 index b1ce385..0000000 --- a/third_party/k8s.io/test-infra/prow/flagutil/git.go +++ /dev/null @@ -1,99 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package flagutil - -import ( - "errors" - "flag" - - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/git/v2" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/github" - utilpointer "k8s.io/utils/pointer" -) - -// GitOptions holds options for interacting with git. -type GitOptions struct { - host string - user string - email string - tokenPath string - useSSH bool - useGitHubUser bool -} - -// AddFlags injects Git options into the given FlagSet. -func (o *GitOptions) AddFlags(fs *flag.FlagSet) { - fs.StringVar(&o.host, "git-host", "github.com", "host to contact for git operations.") - fs.StringVar(&o.user, "git-user", "", "User for git commits, optional. Can be derived from GitHub credentials.") - fs.StringVar(&o.email, "git-email", "", "Email for git commits, optional. Can be derived from GitHub credentials.") - fs.StringVar(&o.tokenPath, "git-token-path", "", "Path to the file containing the git token for HTTPS operations, optional. Can be derived from GitHub credentials.") - fs.BoolVar(&o.useSSH, "git-over-ssh", false, "Use SSH when pushing and pulling instead of HTTPS. SSH credentials should be present at ~/.ssh") - fs.BoolVar(&o.useGitHubUser, "git-user-from-github", true, "Use GitHub credentials and user identity for git operations.") -} - -// Validate validates Git options. -func (o *GitOptions) Validate(dryRun bool) error { - if o.host == "" { - return errors.New("--git-host is required") - } - - if !o.useGitHubUser { - switch { - case o.user == "": - return errors.New("--git-user is required, or may be loaded by setting --git-user-from-github") - case o.email == "": - return errors.New("--git-email is required, or may be loaded by setting --git-user-from-github") - } - } - - if !o.useSSH && !o.useGitHubUser && o.tokenPath == "" { - return errors.New("--git-token-path must be provided or defaulted by setting --git-user-from-github or --git-over-ssh") - } - return nil -} - -// GitClient creates a new git client. -func (o *GitOptions) GitClient(userClient github.UserClient, token func() []byte, censor func(content []byte) []byte, dryRun bool) (git.ClientFactory, error) { - gitUser := func() (name, email string, err error) { - name, email = o.user, o.email - if o.useGitHubUser { - user, err := userClient.BotUser() - if err != nil { - return "", "", err - } - name = user.Name - email = user.Email - } - return name, email, nil - } - username := func() (login string, err error) { - user, err := userClient.BotUser() - if err != nil { - return "", err - } - return user.Login, nil - } - opts := git.ClientFactoryOpts{ - Host: o.host, - UseSSH: utilpointer.BoolPtr(o.useSSH), - Username: username, - Token: token, - GitUser: gitUser, - Censor: censor, - } - return git.NewClientFactory(opts.Apply) -} diff --git a/third_party/k8s.io/test-infra/prow/flagutil/github.go b/third_party/k8s.io/test-infra/prow/flagutil/github.go deleted file mode 100644 index 62ab459..0000000 --- a/third_party/k8s.io/test-infra/prow/flagutil/github.go +++ /dev/null @@ -1,420 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package flagutil - -import ( - "crypto/rsa" - "errors" - "flag" - "fmt" - "net/url" - "strconv" - "strings" - "time" - - "github.com/dgrijalva/jwt-go/v4" - "github.com/sirupsen/logrus" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/config/secret" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/git" - gitv2 "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/git/v2" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/github" -) - -// GitHubOptions holds options for interacting with GitHub. -// -// Set AllowAnonymous to be true if you want to allow anonymous github access. -// Set AllowDirectAccess to be true if you want to suppress warnings on direct github access (without ghproxy). -type GitHubOptions struct { - Host string - endpoint Strings - graphqlEndpoint string - TokenPath string - AllowAnonymous bool - AllowDirectAccess bool - AppID string - AppPrivateKeyPath string - - ThrottleHourlyTokens int - ThrottleAllowBurst int - - OrgThrottlers Strings - parsedOrgThrottlers map[string]throttlerSettings - - // These will only be set after a github client was retrieved for the first time - tokenGenerator github.TokenGenerator - userGenerator github.UserGenerator - - // the following options determine how the client behaves around retries - maxRequestTime time.Duration - maxRetries int - max404Retries int - initialDelay time.Duration - maxSleepTime time.Duration -} - -type throttlerSettings struct { - hourlyTokens int - burst int -} - -// flagParams struct is used indirectly by users of this package to customize -// the common flags behavior, such as providing their own default values -// or suppressing presence of certain flags. -type flagParams struct { - defaults GitHubOptions - - disableThrottlerOptions bool -} - -type FlagParameter func(options *flagParams) - -// ThrottlerDefaults allows to customize the default values of flags -// that control the throttler behavior. Setting `hourlyTokens` to zero -// disables throttling by default. -func ThrottlerDefaults(hourlyTokens, allowedBursts int) FlagParameter { - return func(o *flagParams) { - o.defaults.ThrottleHourlyTokens = hourlyTokens - o.defaults.ThrottleAllowBurst = allowedBursts - } -} - -// DisableThrottlerOptions suppresses the presence of throttler-related flags, -// effectively disallowing external users to parametrize default throttling -// behavior. This is useful mostly when a program creates multiple GH clients -// with different behavior. -func DisableThrottlerOptions() FlagParameter { - return func(o *flagParams) { - o.disableThrottlerOptions = true - } -} - -// AddCustomizedFlags injects GitHub options into the given FlagSet. Behavior can be customized -// via the functional options. -func (o *GitHubOptions) AddCustomizedFlags(fs *flag.FlagSet, paramFuncs ...FlagParameter) { - o.addFlags(fs, paramFuncs...) -} - -// AddFlags injects GitHub options into the given FlagSet -func (o *GitHubOptions) AddFlags(fs *flag.FlagSet) { - o.addFlags(fs) -} - -func (o *GitHubOptions) addFlags(fs *flag.FlagSet, paramFuncs ...FlagParameter) { - params := flagParams{ - defaults: GitHubOptions{ - Host: github.DefaultHost, - endpoint: NewStrings(github.DefaultAPIEndpoint), - graphqlEndpoint: github.DefaultGraphQLEndpoint, - }, - } - - for _, parametrize := range paramFuncs { - parametrize(¶ms) - } - - defaults := params.defaults - fs.StringVar(&o.Host, "github-host", defaults.Host, "GitHub's default host (may differ for enterprise)") - o.endpoint = NewStrings(defaults.endpoint.Strings()...) - fs.Var(&o.endpoint, "github-endpoint", "GitHub's API endpoint (may differ for enterprise).") - fs.StringVar(&o.graphqlEndpoint, "github-graphql-endpoint", defaults.graphqlEndpoint, "GitHub GraphQL API endpoint (may differ for enterprise).") - fs.StringVar(&o.TokenPath, "github-token-path", defaults.TokenPath, "Path to the file containing the GitHub OAuth secret.") - fs.StringVar(&o.AppID, "github-app-id", defaults.AppID, "ID of the GitHub app. If set, requires --github-app-private-key-path to be set and --github-token-path to be unset.") - fs.StringVar(&o.AppPrivateKeyPath, "github-app-private-key-path", defaults.AppPrivateKeyPath, "Path to the private key of the github app. If set, requires --github-app-id to bet set and --github-token-path to be unset") - - if !params.disableThrottlerOptions { - fs.IntVar(&o.ThrottleHourlyTokens, "github-hourly-tokens", defaults.ThrottleHourlyTokens, "If set to a value larger than zero, enable client-side throttling to limit hourly token consumption. If set, --github-allowed-burst must be positive too.") - fs.IntVar(&o.ThrottleAllowBurst, "github-allowed-burst", defaults.ThrottleAllowBurst, "Size of token consumption bursts. If set, --github-hourly-tokens must be positive too and set to a higher or equal number.") - fs.Var(&o.OrgThrottlers, "github-throttle-org", "Throttler settings for a specific org in org:hourlyTokens:burst format. Can be passed multiple times. Only valid when using github apps auth.") - } - - fs.DurationVar(&o.maxRequestTime, "github-client.request-timeout", github.DefaultMaxSleepTime, "Timeout for any single request to the GitHub API.") - fs.IntVar(&o.maxRetries, "github-client.max-retries", github.DefaultMaxRetries, "Maximum number of retries that will be used for a failing request to the GitHub API.") - fs.IntVar(&o.max404Retries, "github-client.max-404-retries", github.DefaultMax404Retries, "Maximum number of retries that will be used for a 404-ing request to the GitHub API.") - fs.DurationVar(&o.maxSleepTime, "github-client.backoff-timeout", github.DefaultMaxSleepTime, "Largest allowable Retry-After time for requests to the GitHub API.") - fs.DurationVar(&o.initialDelay, "github-client.initial-delay", github.DefaultInitialDelay, "Initial delay before retries begin for requests to the GitHub API.") -} - -func (o *GitHubOptions) parseOrgThrottlers() error { - if len(o.OrgThrottlers.vals) == 0 { - return nil - } - - if o.AppID == "" { - return errors.New("--github-throttle-org was passed, but client doesn't use apps auth") - } - - o.parsedOrgThrottlers = make(map[string]throttlerSettings, len(o.OrgThrottlers.vals)) - var errs []error - for _, orgThrottler := range o.OrgThrottlers.vals { - colonSplit := strings.Split(orgThrottler, ":") - if len(colonSplit) != 3 { - errs = append(errs, fmt.Errorf("-github-throttle-org=%s is not in org:hourlyTokens:burst format", orgThrottler)) - continue - } - org, hourlyTokensString, burstString := colonSplit[0], colonSplit[1], colonSplit[2] - hourlyTokens, err := strconv.ParseInt(hourlyTokensString, 10, 32) - if err != nil { - errs = append(errs, fmt.Errorf("-github-throttle-org=%s is not in org:hourlyTokens:burst format: hourlyTokens is not an int", orgThrottler)) - continue - } - burst, err := strconv.ParseInt(burstString, 10, 32) - if err != nil { - errs = append(errs, fmt.Errorf("-github-throttle-org=%s is not in org:hourlyTokens:burst format: burst is not an int", orgThrottler)) - continue - } - if hourlyTokens < 1 { - errs = append(errs, fmt.Errorf("-github-throttle-org=%s: hourlyTokens must be > 0", orgThrottler)) - continue - } - if burst < 1 { - errs = append(errs, fmt.Errorf("-github-throttle-org=%s: burst must be > 0", orgThrottler)) - continue - } - if burst > hourlyTokens { - errs = append(errs, fmt.Errorf("-github-throttle-org=%s: burst must not be greater than hourlyTokens", orgThrottler)) - continue - } - if _, alreadyExists := o.parsedOrgThrottlers[org]; alreadyExists { - errs = append(errs, fmt.Errorf("got multiple -github-throttle-org for the %s org", org)) - continue - } - o.parsedOrgThrottlers[org] = throttlerSettings{hourlyTokens: int(hourlyTokens), burst: int(burst)} - } - - return utilerrors.NewAggregate(errs) -} - -// Validate validates GitHub options. Note that validate updates the GitHubOptions -// to add default values for TokenPath and graphqlEndpoint. -func (o *GitHubOptions) Validate(bool) error { - endpoints := o.endpoint.Strings() - for i, uri := range endpoints { - if uri == "" { - endpoints[i] = github.DefaultAPIEndpoint - } else if _, err := url.ParseRequestURI(uri); err != nil { - return fmt.Errorf("invalid -github-endpoint URI: %q", uri) - } - } - - if o.TokenPath != "" && (o.AppID != "" || o.AppPrivateKeyPath != "") { - return fmt.Errorf("--token-path is mutually exclusive with --app-id and --app-private-key-path") - } - if o.AppID == "" != (o.AppPrivateKeyPath == "") { - return errors.New("--app-id and --app-private-key-path must be set together") - } - - if o.TokenPath != "" && len(endpoints) == 1 && endpoints[0] == github.DefaultAPIEndpoint && !o.AllowDirectAccess { - logrus.Warn("It doesn't look like you are using ghproxy to cache API calls to GitHub! This has become a required component of Prow and other components will soon be allowed to add features that may rapidly consume API ratelimit without caching. Starting May 1, 2020 use Prow components without ghproxy at your own risk! https://github.com/kubernetes/test-infra/tree/master/ghproxy#ghproxy") - } - - if o.graphqlEndpoint == "" { - o.graphqlEndpoint = github.DefaultGraphQLEndpoint - } else if _, err := url.Parse(o.graphqlEndpoint); err != nil { - return fmt.Errorf("invalid -github-graphql-endpoint URI: %q", o.graphqlEndpoint) - } - - if (o.ThrottleHourlyTokens > 0) != (o.ThrottleAllowBurst > 0) { - if o.ThrottleHourlyTokens == 0 { - // Tolerate `--github-hourly-tokens=0` alone to disable throttling - o.ThrottleAllowBurst = 0 - } else { - return errors.New("--github-hourly-tokens and --github-allowed-burst must be either both higher than zero or both equal to zero") - } - } - if o.ThrottleAllowBurst > o.ThrottleHourlyTokens { - return errors.New("--github-allowed-burst must not be larger than --github-hourly-tokens") - } - - return o.parseOrgThrottlers() -} - -// GitHubClientWithLogFields returns a GitHub client with extra logging fields -func (o *GitHubOptions) GitHubClientWithLogFields(dryRun bool, fields logrus.Fields) (github.Client, error) { - client, err := o.githubClient(dryRun) - if err != nil { - return nil, err - } - return client.WithFields(fields), nil -} - -func (o *GitHubOptions) githubClient(dryRun bool) (github.Client, error) { - fields := logrus.Fields{} - options := o.baseClientOptions() - options.DryRun = dryRun - - if o.TokenPath == "" && o.AppPrivateKeyPath == "" { - logrus.Warn("empty -github-token-path, will use anonymous github client") - } - - if o.TokenPath == "" { - options.GetToken = func() []byte { - return []byte{} - } - } else { - if err := secret.Add(o.TokenPath); err != nil { - return nil, fmt.Errorf("failed to add GitHub token to secret agent: %w", err) - } - options.GetToken = secret.GetTokenGenerator(o.TokenPath) - } - - if o.AppPrivateKeyPath != "" { - apk, err := o.appPrivateKeyGenerator() - if err != nil { - return nil, err - } - options.AppPrivateKey = apk - } - - optionallyThrottled := func(c github.Client) (github.Client, error) { - // Throttle handles zeros as "disable throttling" so we do not need to call it conditionally - if err := c.Throttle(o.ThrottleHourlyTokens, o.ThrottleAllowBurst); err != nil { - return nil, fmt.Errorf("failed to throttle: %w", err) - } - for org, settings := range o.parsedOrgThrottlers { - if err := c.Throttle(settings.hourlyTokens, settings.burst, org); err != nil { - return nil, fmt.Errorf("failed to set up throttling for org %s: %w", org, err) - } - } - return c, nil - } - - tokenGenerator, userGenerator, client, err := github.NewClientFromOptions(fields, options) - if err != nil { - return nil, fmt.Errorf("failed to construct github client: %w", err) - } - o.tokenGenerator = tokenGenerator - o.userGenerator = userGenerator - return optionallyThrottled(client) -} - -// baseClientOptions populates client options that are derived from flags without processing -func (o *GitHubOptions) baseClientOptions() github.ClientOptions { - return github.ClientOptions{ - Censor: secret.Censor, - AppID: o.AppID, - GraphqlEndpoint: o.graphqlEndpoint, - Bases: o.endpoint.Strings(), - MaxRequestTime: o.maxRequestTime, - InitialDelay: o.initialDelay, - MaxSleepTime: o.maxSleepTime, - MaxRetries: o.maxRetries, - Max404Retries: o.max404Retries, - } -} - -// GitHubClient returns a GitHub client. -func (o *GitHubOptions) GitHubClient(dryRun bool) (github.Client, error) { - return o.GitHubClientWithLogFields(dryRun, logrus.Fields{}) -} - -// GitHubClientWithAccessToken creates a GitHub client from an access token. -func (o *GitHubOptions) GitHubClientWithAccessToken(token string) (github.Client, error) { - options := o.baseClientOptions() - options.GetToken = func() []byte { return []byte(token) } - options.AppID = "" // Since we are using a token, we should not use the app auth - _, _, client, err := github.NewClientFromOptions(logrus.Fields{}, options) - return client, err -} - -// GitClientFactory returns git.ClientFactory. Passing non-empty cookieFilePath -// will result in git ClientFactory to work with Gerrit. -// TODO(chaodaiG): move this logic to somewhere more appropriate instead of in -// github.go. -func (o *GitHubOptions) GitClientFactory(cookieFilePath string, cacheDir *string, dryRun bool) (gitv2.ClientFactory, error) { - var gitClientFactory gitv2.ClientFactory - if cookieFilePath != "" && o.TokenPath == "" && o.AppPrivateKeyPath == "" { - opts := gitv2.ClientFactoryOpts{ - CookieFilePath: cookieFilePath, - } - if cacheDir != nil && *cacheDir != "" { - opts.CacheDirBase = cacheDir - } - var err error - gitClientFactory, err = gitv2.NewClientFactory(opts.Apply) - if err != nil { - return nil, fmt.Errorf("failed to create git client from cookieFile: %v\n(cookieFile is only for Gerrit)", err) - } - } else { - gitClient, err := o.GitClient(dryRun) - if err != nil { - return nil, fmt.Errorf("Error getting git client: %w", err) - } - gitClientFactory = gitv2.ClientFactoryFrom(gitClient) - } - - return gitClientFactory, nil -} - -// GitClient returns a Git client. -func (o *GitHubOptions) GitClient(dryRun bool) (client *git.Client, err error) { - client, err = git.NewClientWithHost(o.Host) - if err != nil { - return nil, err - } - - // We must capture the value of client here to prevent issues related - // to the use of named return values when an error is encountered. - // Without this, we risk a nil pointer dereference. - defer func(client *git.Client) { - if err != nil { - client.Clean() - } - }(client) - - user, generator, err := o.getGitAuthentication(dryRun) - if err != nil { - return nil, fmt.Errorf("failed to get git authentication: %w", err) - } - client.SetCredentials(user, generator) - - return client, nil -} - -func (o *GitHubOptions) getGitAuthentication(dryRun bool) (string, git.GitTokenGenerator, error) { - // the client must have been created at least once for us to have generators - if o.userGenerator == nil { - if _, err := o.GitHubClient(dryRun); err != nil { - return "", nil, fmt.Errorf("error getting GitHub client: %w", err) - } - } - - login, err := o.userGenerator() - if err != nil { - return "", nil, fmt.Errorf("error getting bot name: %w", err) - } - return login, git.GitTokenGenerator(o.tokenGenerator), nil -} - -func (o *GitHubOptions) appPrivateKeyGenerator() (func() *rsa.PrivateKey, error) { - generator, err := secret.AddWithParser( - o.AppPrivateKeyPath, - func(raw []byte) (*rsa.PrivateKey, error) { - privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(raw) - if err != nil { - return nil, fmt.Errorf("failed to parse rsa key from pem: %w", err) - } - return privateKey, nil - }, - ) - if err != nil { - return nil, fmt.Errorf("failed to add the key from --app-private-key-path to secret agent: %w", err) - } - - return generator, nil -} diff --git a/third_party/k8s.io/test-infra/prow/flagutil/github_enablement.go b/third_party/k8s.io/test-infra/prow/flagutil/github_enablement.go deleted file mode 100644 index ed5a886..0000000 --- a/third_party/k8s.io/test-infra/prow/flagutil/github_enablement.go +++ /dev/null @@ -1,94 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package flagutil - -import ( - "flag" - "fmt" - "strings" - - utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/apimachinery/pkg/util/sets" -) - -// GitHubEnablementOptions allows enable/disable functionality on a github org or -// org/repo level. If either EnabledOrgs or EnabledRepos is set, only org/repos in -// those are allowed, otherwise everything that is not in DisabledOrgs or DisabledRepos -// is allowed. -type GitHubEnablementOptions struct { - enabledOrgs Strings - enabledRepos Strings - disabledOrgs Strings - disabledRepos Strings -} - -func (o *GitHubEnablementOptions) AddFlags(fs *flag.FlagSet) { - fs.Var(&o.enabledOrgs, "github-enabled-org", "Enabled github org. Can be passed multiple times. If set, all orgs or repos that are not allowed via --gitbub-enabled-orgs or --github-enabled-repos will be ignored") - fs.Var(&o.enabledRepos, "github-enabled-repo", "Enabled github repo in org/repo format. Can be passed multiple times. If set, all orgs or repos that are not allowed via --gitbub-enabled-orgs or --github-enabled-repos will be ignored") - fs.Var(&o.disabledOrgs, "github-disabled-org", "Disabled github org. Can be passed multiple times. Orgs that are in this list will be ignored.") - fs.Var(&o.disabledRepos, "github-disabled-repo", "Disabled github repo in org/repo format. Can be passed multiple times. Repos that are in this list will be ignored.") -} - -func (o *GitHubEnablementOptions) Validate(_ bool) error { - var errs []error - - for _, enabledRepo := range o.enabledRepos.vals { - if err := validateOrgRepoFormat(enabledRepo); err != nil { - errs = append(errs, fmt.Errorf("--github-enabled-repo=%s is invalid: %w", enabledRepo, err)) - } - } - for _, disabledRepo := range o.disabledRepos.vals { - if err := validateOrgRepoFormat(disabledRepo); err != nil { - errs = append(errs, fmt.Errorf("--github-disabled-repo=%s is invalid: %w", disabledRepo, err)) - } - } - - if intersection := o.enabledOrgs.StringSet().Intersection(o.disabledOrgs.StringSet()); len(intersection) != 0 { - errs = append(errs, fmt.Errorf("%v is in both --github-enabled-org and --github-disabled-org", sets.List(intersection))) - } - - if intersection := o.enabledRepos.StringSet().Intersection(o.disabledRepos.StringSet()); len(intersection) != 0 { - errs = append(errs, fmt.Errorf("%v is in both --github-enabled-repo and --github-disabled-repo", sets.List(intersection))) - } - - return utilerrors.NewAggregate(errs) -} - -func validateOrgRepoFormat(orgRepo string) error { - components := strings.Split(orgRepo, "/") - if n := len(components); n != 2 || components[0] == "" || components[1] == "" { - return fmt.Errorf("%q is not in org/repo format", orgRepo) - } - - return nil -} - -func (o *GitHubEnablementOptions) EnablementChecker() func(org, repo string) bool { - enabledOrgs := o.enabledOrgs.StringSet() - enabledRepos := o.enabledRepos.StringSet() - disabledOrgs := o.disabledOrgs.StringSet() - diabledRepos := o.disabledRepos.StringSet() - return func(org, repo string) bool { - if len(enabledOrgs) > 0 || len(enabledRepos) > 0 { - if !enabledOrgs.Has(org) && !enabledRepos.Has(org+"/"+repo) { - return false - } - } - - return !disabledOrgs.Has(org) && !diabledRepos.Has(org+"/"+repo) - } -} diff --git a/third_party/k8s.io/test-infra/prow/flagutil/instrumentation.go b/third_party/k8s.io/test-infra/prow/flagutil/instrumentation.go deleted file mode 100644 index a482f6c..0000000 --- a/third_party/k8s.io/test-infra/prow/flagutil/instrumentation.go +++ /dev/null @@ -1,69 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package flagutil - -import ( - "flag" - "time" -) - -const ( - DefaultMetricsPort = 9090 - DefaultPProfPort = 6060 - DefaultHealthPort = 8081 - - DefaultMemoryProfileInterval = 30 * time.Second -) - -// InstrumentationOptions holds common options which are used across Prow components -type InstrumentationOptions struct { - // MetricsPort is the port which is used to serve metrics - MetricsPort int - // PProfPort is the port which is used to serve pprof - PProfPort int - // HealthPort is the port which is used to serve liveness and readiness - HealthPort int - - // ProfileMemory determines if the process should profile memory - ProfileMemory bool - // MemoryProfileInterval is the interval at which memory profiles should be dumped - MemoryProfileInterval time.Duration -} - -// DefaultInstrumentationOptions returns an initialized options struct, mostly for use in tests. -func DefaultInstrumentationOptions() InstrumentationOptions { - return InstrumentationOptions{ - MetricsPort: DefaultMetricsPort, - PProfPort: DefaultPProfPort, - HealthPort: DefaultHealthPort, - ProfileMemory: false, - MemoryProfileInterval: DefaultMemoryProfileInterval, - } -} - -// AddFlags injects common options into the given FlagSet. -func (o *InstrumentationOptions) AddFlags(fs *flag.FlagSet) { - fs.IntVar(&o.MetricsPort, "metrics-port", DefaultMetricsPort, "port to serve metrics") - fs.IntVar(&o.PProfPort, "pprof-port", DefaultPProfPort, "port to serve pprof") - fs.IntVar(&o.HealthPort, "health-port", DefaultHealthPort, "port to serve liveness and readiness") - fs.BoolVar(&o.ProfileMemory, "profile-memory-usage", false, "profile memory usage for analysis") - fs.DurationVar(&o.MemoryProfileInterval, "memory-profile-interval", DefaultMemoryProfileInterval, "duration at which memory profiles should be dumped") -} - -func (o *InstrumentationOptions) Validate(_ bool) error { - return nil -} diff --git a/third_party/k8s.io/test-infra/prow/flagutil/strings.go b/third_party/k8s.io/test-infra/prow/flagutil/strings.go deleted file mode 100644 index b9ef203..0000000 --- a/third_party/k8s.io/test-infra/prow/flagutil/strings.go +++ /dev/null @@ -1,76 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package flagutil - -import ( - "strings" - - "k8s.io/apimachinery/pkg/util/sets" -) - -// Strings represents the value of a flag that accept multiple strings. -type Strings struct { - vals []string - beenSet bool -} - -// NewStrings returns a Strings struct that defaults to the value of def if left unset. -func NewStrings(def ...string) Strings { - return Strings{ - vals: def, - beenSet: false, - } -} - -// NewStringsBeenSet returns a Strings struct with beenSet: true -func NewStringsBeenSet(def ...string) Strings { - return Strings{ - vals: def, - beenSet: true, - } -} - -// Strings returns the slice of strings set for this value instance. -func (s *Strings) Strings() []string { - return s.vals -} - -// StringSet returns a sets.Set[string] of strings set for this value instance. -func (s *Strings) StringSet() sets.Set[string] { - return sets.New[string](s.Strings()...) -} - -// String returns a concatenated string of all the values joined by commas. -func (s *Strings) String() string { - return strings.Join(s.vals, ",") -} - -// Set records the value passed, overwriting the defaults (if any) -func (s *Strings) Set(value string) error { - if !s.beenSet { - s.beenSet = true - // Value is being set, don't use default. - s.vals = nil - } - s.vals = append(s.vals, value) - return nil -} - -// Add records the value passes, adding to the defaults (if any) -func (s *Strings) Add(value string) { - s.vals = append(s.vals, value) -} diff --git a/third_party/k8s.io/test-infra/prow/gerrit/source/source.go b/third_party/k8s.io/test-infra/prow/gerrit/source/source.go deleted file mode 100644 index fc17734..0000000 --- a/third_party/k8s.io/test-infra/prow/gerrit/source/source.go +++ /dev/null @@ -1,106 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package source contains functions that help with Gerrit source control -// specific logics. -package source - -import ( - "fmt" - "strings" -) - -// IsGerritOrg tells whether the org is a Gerrit org or not. It returns true -// when the org string starts with https://. -func IsGerritOrg(org string) bool { - return strings.HasPrefix(org, "https://") || strings.HasPrefix(org, "http://") -} - -// CloneURIFromOrgRepo returns normalized cloneURI from org and repo. The -// returns cloneURI will always have https:// or http:// prefix, and there is no -// trailing slash at the end. -func CloneURIFromOrgRepo(org, repo string) string { - return NormalizeCloneURI(orgRepo(org, repo)) -} - -// NormalizeOrg returns normalized org. It ensures that org always has https:// -// or http:// prefix, and there is no trailing slash at the end. This function -// should be used everywhere that Gerrit org is referenced. -func NormalizeOrg(org string) string { - return strings.TrimRight(ensuresHTTPSPrefix(org), "/") -} - -// NormalizeCloneURI returns normalized cloneURI. It ensures that cloneURI -// always has https:// or http:// prefix, and there is no trailing slash at the -// end. This function should be used everywhere that Gerrit cloneURI is -// referenced. -func NormalizeCloneURI(cloneURI string) string { - return strings.TrimRight(ensuresHTTPSPrefix(cloneURI), "/") -} - -// OrgRepoFromCloneURI returns org and repo from cloneURI. The returned org -// always has https:// or http:// prefix even if cloneURI doesn't have it. -func OrgRepoFromCloneURI(cloneURI string) (string, string, error) { - scheme := "https://" - if strings.HasPrefix(cloneURI, "http://") { - scheme = "http://" - } - cloneURIWithoutPrefix := TrimHTTPSPrefix(cloneURI) - var org, repo string - parts := strings.SplitN(cloneURIWithoutPrefix, "/", 2) - if len(parts) != 2 { - return org, repo, fmt.Errorf("should have 2 parts: %v", parts) - } - return NormalizeOrg(scheme + parts[0]), strings.TrimRight(parts[1], "/"), nil -} - -func ensuresHTTPSPrefix(in string) string { - scheme := "https://" - if strings.HasPrefix(in, "http://") { - scheme = "http://" - } - return fmt.Sprintf("%s%s", scheme, strings.Trim(TrimHTTPSPrefix(in), "/")) -} - -// TrimHTTPSPrefix trims https:// and http:// from input, also remvoes all -// trailing slashes from the end. -func TrimHTTPSPrefix(in string) string { - in = strings.TrimPrefix(in, "https://") - in = strings.TrimPrefix(in, "http://") - return strings.TrimRight(in, "/") -} - -// orgRepo returns /, removes all extra slashs. -func orgRepo(org, repo string) string { - org = strings.Trim(org, "/") - repo = strings.Trim(repo, "/") - return org + "/" + repo -} - -// CodeRootURL converts code review URL into source code URL, simply -// trimming the `-review` suffix from the name of the org. -// -// Gerrit URL for sourcecode looks like -// https://android.googlesource.com, and the code review URL looks like -// https://android-review.googlesource.com/c/platform/frameworks/support/+/2260382. -func CodeRootURL(reviewURL string) (string, error) { - orgParts := strings.Split(reviewURL, ".") - if !strings.HasSuffix(orgParts[0], "-review") { - return "", fmt.Errorf("cannot find '-review' suffix from the first part of url %v", orgParts) - } - orgParts[0] = strings.TrimSuffix(orgParts[0], "-review") - return strings.Join(orgParts, "."), nil -} diff --git a/third_party/k8s.io/test-infra/prow/git/git.go b/third_party/k8s.io/test-infra/prow/git/git.go deleted file mode 100644 index 3d28200..0000000 --- a/third_party/k8s.io/test-infra/prow/git/git.go +++ /dev/null @@ -1,639 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package git provides a client to plugins that can do git operations. -package git - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - "sync" - "time" - - "github.com/sirupsen/logrus" - - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/git/types" -) - -const github = "github.com" - -// Client can clone repos. It keeps a local cache, so successive clones of the -// same repo should be quick. Create with NewClient. Be sure to clean it up. -type Client struct { - // logger will be used to log git operations and must be set. - logger *logrus.Entry - - credLock sync.RWMutex - // user is used when pushing or pulling code if specified. - user string - - // needed to generate the token. - tokenGenerator GitTokenGenerator - - // dir is the location of the git cache. - dir string - // git is the path to the git binary. - git string - // base is the base path for git clone calls. For users it will be set to - // GitHub, but for tests set it to a directory with git repos. - base string - // host is the git host. - // TODO: use either base or host. the redundancy here is to help landing - // #14609 easier. - host string - - // The mutex protects repoLocks which protect individual repos. This is - // necessary because Clone calls for the same repo are racy. Rather than - // one lock for all repos, use a lock per repo. - // Lock with Client.lockRepo, unlock with Client.unlockRepo. - rlm sync.Mutex - repoLocks map[string]*sync.Mutex -} - -// Clean removes the local repo cache. The Client is unusable after calling. -func (c *Client) Clean() error { - return os.RemoveAll(c.dir) -} - -// NewClient returns a client that talks to GitHub. It will fail if git is not -// in the PATH. -func NewClient() (*Client, error) { - return NewClientWithHost(github) -} - -// NewClientWithHost creates a client with specified host. -func NewClientWithHost(host string) (*Client, error) { - g, err := exec.LookPath("git") - if err != nil { - return nil, err - } - t, err := os.MkdirTemp("", "git") - if err != nil { - return nil, err - } - return &Client{ - logger: logrus.WithField("client", "git"), - tokenGenerator: func(_ string) (string, error) { return "", nil }, - dir: t, - git: g, - base: fmt.Sprintf("https://%s", host), - host: host, - repoLocks: make(map[string]*sync.Mutex), - }, nil -} - -// SetRemote sets the remote for the client. This is not thread-safe, and is -// useful for testing. The client will clone from remote/org/repo, and Repo -// objects spun out of the client will also hit that path. -// TODO: c.host field needs to be updated accordingly. -func (c *Client) SetRemote(remote string) { - c.base = remote -} - -type GitTokenGenerator func(org string) (string, error) - -// SetCredentials sets credentials in the client to be used for pushing to -// or pulling from remote repositories. -func (c *Client) SetCredentials(user string, tokenGenerator GitTokenGenerator) { - c.credLock.Lock() - defer c.credLock.Unlock() - c.user = user - c.tokenGenerator = tokenGenerator -} - -func (c *Client) getCredentials(org string) (string, string, error) { - c.credLock.RLock() - defer c.credLock.RUnlock() - token, err := c.tokenGenerator(org) - return c.user, token, err -} - -func (c *Client) lockRepo(repo string) { - c.rlm.Lock() - if _, ok := c.repoLocks[repo]; !ok { - c.repoLocks[repo] = &sync.Mutex{} - } - m := c.repoLocks[repo] - c.rlm.Unlock() - m.Lock() -} - -func (c *Client) unlockRepo(repo string) { - c.rlm.Lock() - defer c.rlm.Unlock() - c.repoLocks[repo].Unlock() -} - -func remoteFromBase(base, user, pass, host, org, repo string) string { - baseWithAuth := base - if user != "" && pass != "" { - baseWithAuth = fmt.Sprintf("https://%s:%s@%s", user, pass, host) - } - return fmt.Sprintf("%s/%s/%s", baseWithAuth, org, repo) -} - -// Clone clones a repository. Pass the full repository name, such as -// "kubernetes/test-infra" as the repo. -// This function may take a long time if it is the first time cloning the repo. -// In that case, it must do a full git mirror clone. For large repos, this can -// take a while. Once that is done, it will do a git fetch instead of a clone, -// which will usually take at most a few seconds. -func (c *Client) Clone(organization, repository string) (*Repo, error) { - orgRepo := organization + "/" + repository - c.lockRepo(orgRepo) - defer c.unlockRepo(orgRepo) - - user, pass, err := c.getCredentials(organization) - if err != nil { - return nil, fmt.Errorf("failed to get token: %w", err) - } - cache := filepath.Join(c.dir, orgRepo) + ".git" - remote := remoteFromBase(c.base, user, pass, c.host, organization, repository) - if _, err := os.Stat(cache); os.IsNotExist(err) { - // Cache miss, clone it now. - c.logger.WithField("repo", orgRepo).Info("Cloning for the first time.") - if err := os.MkdirAll(filepath.Dir(cache), os.ModePerm); err != nil && !os.IsExist(err) { - return nil, err - } - if b, err := retryCmd(c.logger, "", c.git, "clone", "--mirror", remote, cache); err != nil { - return nil, fmt.Errorf("git cache clone error: %v. output: %s", err, string(b)) - } - } else if err != nil { - return nil, err - } else { - // Cache hit. Do a git fetch to keep updated. - // Update remote url, if we use apps auth the token changes every hour - if b, err := retryCmd(c.logger, cache, c.git, "remote", "set-url", "origin", remote); err != nil { - return nil, fmt.Errorf("updating remote url failed: %w. output: %s", err, string(b)) - } - c.logger.WithField("repo", orgRepo).Info("Fetching.") - if b, err := retryCmd(c.logger, cache, c.git, "fetch", "--prune"); err != nil { - return nil, fmt.Errorf("git fetch error: %v. output: %s", err, string(b)) - } - } - t, err := os.MkdirTemp("", "git") - if err != nil { - return nil, err - } - if b, err := exec.Command(c.git, "clone", cache, t).CombinedOutput(); err != nil { - return nil, fmt.Errorf("git repo clone error: %v. output: %s", err, string(b)) - } - // Updating remote url to true remote like `git@github.com:kubernetes/test-infra.git`, - // instead of something like `/tmp/12345/test-infra`, so that `git fetch` in this clone makes more sense. - cmd := exec.Command(c.git, "remote", "set-url", "origin", remote) - cmd.Dir = t - if b, err := cmd.CombinedOutput(); err != nil { - return nil, fmt.Errorf("updating remote url failed: %w. output: %s", err, string(b)) - } - r := &Repo{ - dir: t, - logger: c.logger, - git: c.git, - host: c.host, - base: c.base, - org: organization, - repo: repository, - user: user, - pass: pass, - tokenGenerator: c.tokenGenerator, - } - // disable git GC - if err := r.Config("gc.auto", "0"); err != nil { - return nil, err - } - return r, nil -} - -// Repo is a clone of a git repository. Create with Client.Clone, and don't -// forget to clean it up after. -type Repo struct { - // dir is the location of the git repo. - dir string - - // git is the path to the git binary. - git string - // host is the git host. - host string - // base is the base path for remote git fetch calls. - base string - // org is the organization name: "org" in "org/repo". - org string - // repo is the repository name: "repo" in "org/repo". - repo string - // user is used for pushing to the remote repo. - user string - // pass is used for pushing to the remote repo. - pass string - - // needed to generate the token. - tokenGenerator GitTokenGenerator - - credLock sync.RWMutex - - logger *logrus.Entry -} - -// Directory exposes the location of the git repo -func (r *Repo) Directory() string { - return r.dir -} - -// SetLogger sets logger: Do not use except in unit tests -func (r *Repo) SetLogger(logger *logrus.Entry) { - r.logger = logger -} - -// SetGit sets git: Do not use except in unit tests -func (r *Repo) SetGit(git string) { - r.git = git -} - -// Clean deletes the repo. It is unusable after calling. -func (r *Repo) Clean() error { - return os.RemoveAll(r.dir) -} - -// refreshRepoAuth updates Repo client token when current token is going to expire. -// Git client authenticating with PAT(personal access token) doesn't have this problem as it's a single token. -// GitHub app auth will need this for rotating token every hour. -func (r *Repo) refreshRepoAuth() error { - // Lock because we'll update r.pass here - r.credLock.Lock() - defer r.credLock.Unlock() - pass, err := r.tokenGenerator(r.org) - if err != nil { - return fmt.Errorf("failed to get token: %w", err) - } - if pass == r.pass { // Token unchanged, no need to do anything - return nil - } - - r.pass = pass - remote := remoteFromBase(r.base, r.user, r.pass, r.host, r.org, r.repo) - if b, err := r.gitCommand("remote", "set-url", "origin", remote).CombinedOutput(); err != nil { - return fmt.Errorf("updating remote url failed: %w. output: %s", err, string(b)) - } - return nil -} - -// ResetHard runs `git reset --hard` -func (r *Repo) ResetHard(commitlike string) error { - // `git reset --hard` doesn't cleanup untracked file - r.logger.Info("Clean untracked files and dirs.") - if b, err := r.gitCommand("clean", "-df").CombinedOutput(); err != nil { - return fmt.Errorf("error clean -df: %v. output: %s", err, string(b)) - } - r.logger.WithField("commitlike", commitlike).Info("Reset hard.") - co := r.gitCommand("reset", "--hard", commitlike) - if b, err := co.CombinedOutput(); err != nil { - return fmt.Errorf("error reset hard %s: %v. output: %s", commitlike, err, string(b)) - } - return nil -} - -// IsDirty checks whether the repo is dirty or not -func (r *Repo) IsDirty() (bool, error) { - r.logger.Info("Checking is dirty.") - b, err := r.gitCommand("status", "--porcelain").CombinedOutput() - if err != nil { - return false, fmt.Errorf("error add -A: %v. output: %s", err, string(b)) - } - return len(b) > 0, nil -} - -func (r *Repo) gitCommand(arg ...string) *exec.Cmd { - cmd := exec.Command(r.git, arg...) - cmd.Dir = r.dir - r.logger.WithField("args", cmd.Args).WithField("dir", cmd.Dir).Debug("Constructed git command") - return cmd -} - -// Checkout runs git checkout. -func (r *Repo) Checkout(commitlike string) error { - r.logger.WithField("commitlike", commitlike).Info("Checkout.") - co := r.gitCommand("checkout", commitlike) - if b, err := co.CombinedOutput(); err != nil { - return fmt.Errorf("error checking out %s: %v. output: %s", commitlike, err, string(b)) - } - return nil -} - -// RevParse runs git rev-parse. -func (r *Repo) RevParse(commitlike string) (string, error) { - r.logger.WithField("commitlike", commitlike).Info("RevParse.") - b, err := r.gitCommand("rev-parse", commitlike).CombinedOutput() - if err != nil { - return "", fmt.Errorf("error rev-parsing %s: %v. output: %s", commitlike, err, string(b)) - } - return string(b), nil -} - -// BranchExists returns true if branch exists in heads. -func (r *Repo) BranchExists(branch string) bool { - heads := "origin" - r.logger.WithFields(logrus.Fields{"branch": branch, "heads": heads}).Info("Checking if branch exists.") - co := r.gitCommand("ls-remote", "--exit-code", "--heads", heads, branch) - return co.Run() == nil -} - -// CheckoutNewBranch creates a new branch and checks it out. -func (r *Repo) CheckoutNewBranch(branch string) error { - r.logger.WithField("branch", branch).Info("Create and checkout.") - co := r.gitCommand("checkout", "-b", branch) - if b, err := co.CombinedOutput(); err != nil { - return fmt.Errorf("error checking out %s: %v. output: %s", branch, err, string(b)) - } - return nil -} - -// Merge attempts to merge commitlike into the current branch. It returns true -// if the merge completes. It returns an error if the abort fails. -func (r *Repo) Merge(commitlike string) (bool, error) { - return r.MergeWithStrategy(commitlike, types.MergeMerge) -} - -// MergeWithStrategy attempts to merge commitlike into the current branch given the merge strategy. -// It returns true if the merge completes. It returns an error if the abort fails. -func (r *Repo) MergeWithStrategy(commitlike string, mergeStrategy types.PullRequestMergeType) (bool, error) { - r.logger.WithField("commitlike", commitlike).Info("Merging.") - switch mergeStrategy { - case types.MergeMerge: - return r.mergeWithMergeStrategyMerge(commitlike) - case types.MergeSquash: - return r.mergeWithMergeStrategySquash(commitlike) - case types.MergeRebase: - return r.mergeWithMergeStrategyRebase(commitlike) - default: - return false, fmt.Errorf("merge strategy %q is not supported", mergeStrategy) - } -} - -func (r *Repo) mergeWithMergeStrategyMerge(commitlike string) (bool, error) { - co := r.gitCommand("merge", "--no-ff", "--no-stat", "-m merge", commitlike) - - b, err := co.CombinedOutput() - if err == nil { - return true, nil - } - r.logger.WithField("out", string(b)).WithError(err).Infof("Merge failed.") - - if b, err := r.gitCommand("merge", "--abort").CombinedOutput(); err != nil { - return false, fmt.Errorf("error aborting merge for commitlike %s: %v. output: %s", commitlike, err, string(b)) - } - - return false, nil -} - -func (r *Repo) mergeWithMergeStrategySquash(commitlike string) (bool, error) { - co := r.gitCommand("merge", "--squash", "--no-stat", commitlike) - - b, err := co.CombinedOutput() - if err != nil { - r.logger.WithField("out", string(b)).WithError(err).Infof("Merge failed.") - if b, err := r.gitCommand("reset", "--hard", "HEAD").CombinedOutput(); err != nil { - return false, fmt.Errorf("error resetting after failed squash for commitlike %s: %v. output: %s", commitlike, err, string(b)) - } - return false, nil - } - - b, err = r.gitCommand("commit", "--no-stat", "-m", "merge").CombinedOutput() - if err != nil { - r.logger.WithField("out", string(b)).WithError(err).Infof("Commit after squash failed.") - return false, err - } - - return true, nil -} - -func (r *Repo) mergeWithMergeStrategyRebase(commitlike string) (bool, error) { - if commitlike == "" { - return false, errors.New("branch must be set") - } - - headRev, err := r.revParse("HEAD") - if err != nil { - r.logger.WithError(err).Infof("Failed to parse HEAD revision") - return false, err - } - headRev = strings.TrimSuffix(headRev, "\n") - - co := r.gitCommand("rebase", "--no-stat", headRev, commitlike) - b, err := co.CombinedOutput() - if err != nil { - r.logger.WithField("out", string(b)).WithError(err).Infof("Rebase failed.") - if b, err := r.gitCommand("rebase", "--abort").CombinedOutput(); err != nil { - return false, fmt.Errorf("error aborting after failed rebase for commitlike %s: %v. output: %s", commitlike, err, string(b)) - } - return false, nil - } - - return true, nil -} - -func (r *Repo) revParse(args ...string) (string, error) { - fullArgs := append([]string{"rev-parse"}, args...) - co := r.gitCommand(fullArgs...) - b, err := co.CombinedOutput() - if err != nil { - return "", errors.New(string(b)) - } - return string(b), nil -} - -// MergeAndCheckout merges the provided headSHAs in order onto baseSHA using the provided strategy. -// If no headSHAs are provided, it will only checkout the baseSHA and return. -// Only the `merge` and `squash` strategies are supported. -func (r *Repo) MergeAndCheckout(baseSHA string, mergeStrategy types.PullRequestMergeType, headSHAs ...string) error { - if baseSHA == "" { - return errors.New("baseSHA must be set") - } - if err := r.Checkout(baseSHA); err != nil { - return err - } - if len(headSHAs) == 0 { - return nil - } - r.logger.WithFields(logrus.Fields{"headSHAs": headSHAs, "baseSHA": baseSHA, "strategy": mergeStrategy}).Info("Merging.") - for _, headSHA := range headSHAs { - ok, err := r.MergeWithStrategy(headSHA, mergeStrategy) - if err != nil { - return err - } else if !ok { - return fmt.Errorf("failed to merge %q", headSHA) - } - } - return nil -} - -// Am tries to apply the patch in the given path into the current branch -// by performing a three-way merge (similar to git cherry-pick). It returns -// an error if the patch cannot be applied. -func (r *Repo) Am(path string) error { - r.logger.WithField("path", path).Info("Applying.") - co := r.gitCommand("am", "--3way", path) - b, err := co.CombinedOutput() - if err == nil { - return nil - } - output := string(b) - r.logger.WithField("out", output).WithError(err).Infof("Patch apply failed.") - if b, abortErr := r.gitCommand("am", "--abort").CombinedOutput(); abortErr != nil { - r.logger.WithField("out", string(b)).WithError(abortErr).Warning("Aborting patch apply failed.") - } - applyMsg := "The copy of the patch that failed is found in: .git/rebase-apply/patch" - msg := "" - if strings.Contains(output, applyMsg) { - i := strings.Index(output, applyMsg) - msg = string(output[:i]) - } else { - msg = string(output) - } - return errors.New(msg) -} - -// Push pushes over https to the provided owner/repo#branch using a password -// for basic auth. -func (r *Repo) Push(branch string, force bool) error { - return r.PushToNamedFork(r.user, branch, force) -} - -func (r *Repo) PushToNamedFork(forkName, branch string, force bool) error { - if err := r.refreshRepoAuth(); err != nil { - return err - } - if r.user == "" || r.pass == "" { - return errors.New("cannot push without credentials - configure your git client") - } - r.logger.WithFields(logrus.Fields{"user": r.user, "repo": r.repo, "branch": branch}).Info("Pushing.") - remote := remoteFromBase(r.base, r.user, r.pass, r.host, r.user, forkName) - var co *exec.Cmd - if !force { - co = r.gitCommand("push", remote, branch) - } else { - co = r.gitCommand("push", "--force", remote, branch) - } - out, err := co.CombinedOutput() - if err != nil { - r.logger.WithField("out", string(out)).WithError(err).Error("Pushing failed.") - return fmt.Errorf("pushing failed, output: %q, error: %w", string(out), err) - } - return nil -} - -// CheckoutPullRequest does exactly that. -func (r *Repo) CheckoutPullRequest(number int) error { - if err := r.refreshRepoAuth(); err != nil { - return err - } - r.logger.WithFields(logrus.Fields{"org": r.org, "repo": r.repo, "number": number}).Info("Fetching and checking out.") - remote := remoteFromBase(r.base, r.user, r.pass, r.host, r.org, r.repo) - if b, err := retryCmd(r.logger, r.dir, r.git, "fetch", remote, fmt.Sprintf("pull/%d/head:pull%d", number, number)); err != nil { - return fmt.Errorf("git fetch failed for PR %d: %v. output: %s", number, err, string(b)) - } - co := r.gitCommand("checkout", fmt.Sprintf("pull%d", number)) - if b, err := co.CombinedOutput(); err != nil { - return fmt.Errorf("git checkout failed for PR %d: %v. output: %s", number, err, string(b)) - } - return nil -} - -// Config runs git config. -func (r *Repo) Config(args ...string) error { - r.logger.WithField("args", args).Info("Running git config.") - if b, err := r.gitCommand(append([]string{"config"}, args...)...).CombinedOutput(); err != nil { - return fmt.Errorf("git config %v failed: %v. output: %s", args, err, string(b)) - } - return nil -} - -// retryCmd will retry the command a few times with backoff. Use this for any -// commands that will be talking to GitHub, such as clones or fetches. -func retryCmd(l *logrus.Entry, dir, cmd string, arg ...string) ([]byte, error) { - var b []byte - var err error - sleepyTime := time.Second - for i := 0; i < 3; i++ { - c := exec.Command(cmd, arg...) - c.Dir = dir - b, err = c.CombinedOutput() - if err != nil { - err = fmt.Errorf("running %q %v returned error %w with output %q", cmd, arg, err, string(b)) - l.WithField("count", i+1).WithError(err).Debug("Retrying, if this is not the 3rd try then this will be retried.") - time.Sleep(sleepyTime) - sleepyTime *= 2 - continue - } - break - } - return b, err -} - -// Diff runs 'git diff HEAD --name-only' and returns a list -// of file names with upcoming changes -func (r *Repo) Diff(head, sha string) (changes []string, err error) { - r.logger.WithField("sha", sha).Info("Diff head.") - output, err := r.gitCommand("diff", head, sha, "--name-only").CombinedOutput() - if err != nil { - return nil, err - } - scan := bufio.NewScanner(bytes.NewReader(output)) - scan.Split(bufio.ScanLines) - for scan.Scan() { - changes = append(changes, scan.Text()) - } - return -} - -// MergeCommitsExistBetween runs 'git log .. --merged' to verify -// if merge commits exist between "target" and "head". -func (r *Repo) MergeCommitsExistBetween(target, head string) (bool, error) { - r.logger.WithFields(logrus.Fields{"target": target, "head": head}).Info("Verifying if merge commits exist.") - b, err := r.gitCommand("log", fmt.Sprintf("%s..%s", target, head), "--oneline", "--merges").CombinedOutput() - if err != nil { - return false, fmt.Errorf("error verifying if merge commits exist between %s and %s: %v. output: %s", target, head, err, string(b)) - } - return len(b) != 0, nil -} - -// ShowRef returns the commit for a commitlike. Unlike rev-parse it does not require a checkout. -func (r *Repo) ShowRef(commitlike string) (string, error) { - r.logger.WithField("commitlike", commitlike).Info("Getting the commit sha.") - out, err := r.gitCommand("show-ref", "-s", commitlike).CombinedOutput() - if err != nil { - return "", fmt.Errorf("failed to get commit sha for commitlike %s: %w", commitlike, err) - } - return strings.TrimSpace(string(out)), nil -} - -// Fetch fetches from remote -func (r *Repo) Fetch(arg ...string) error { - arg = append([]string{"fetch"}, arg...) - if err := r.refreshRepoAuth(); err != nil { - return err - } - r.logger.Infof("Fetching from remote.") - out, err := r.gitCommand(arg...).CombinedOutput() - if err != nil { - return fmt.Errorf("failed to fetch: %v.\nOutput: %s", err, string(out)) - } - return nil -} diff --git a/third_party/k8s.io/test-infra/prow/git/types/types.go b/third_party/k8s.io/test-infra/prow/git/types/types.go deleted file mode 100644 index 93b74e2..0000000 --- a/third_party/k8s.io/test-infra/prow/git/types/types.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2022 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package types stores types used by all git clients -package types - -// PullRequestMergeType enumerates the types of merges used by Prow for either GitHub or Gerrit API -type PullRequestMergeType string - -// Possible types of merges for the GitHub merge API -const ( - MergeMerge PullRequestMergeType = "merge" - MergeRebase PullRequestMergeType = "rebase" - MergeSquash PullRequestMergeType = "squash" - MergeIfNecessary PullRequestMergeType = "ifNecessary" -) diff --git a/third_party/k8s.io/test-infra/prow/git/v2/adapter.go b/third_party/k8s.io/test-infra/prow/git/v2/adapter.go deleted file mode 100644 index f723795..0000000 --- a/third_party/k8s.io/test-infra/prow/git/v2/adapter.go +++ /dev/null @@ -1,118 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package git - -import ( - "errors" - "fmt" - "strings" - - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/git" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/git/types" -) - -func OrgRepo(full string) (string, string, error) { - if strings.Count(full, "/") != 1 { - return "", "", fmt.Errorf("full repo name %s does not follow the org/repo format", full) - } - parts := strings.Split(full, "/") - return parts[0], parts[1], nil -} - -// ClientFactoryFrom adapts the v1 client to a v2 client -func ClientFactoryFrom(c *git.Client) ClientFactory { - return &clientFactoryAdapter{Client: c} -} - -type clientFactoryAdapter struct { - *git.Client -} - -// ClientFromDir creates a client that operates on a repo that has already -// been cloned to the given directory. -// -// CloneURI is the third arg that's ignored here, it's currently only used for -// cloning Gerrit repos. This client is not used for cloning Gerrit repos yet, -// so leave it unimplemented. -// (TODO: chaodaiG) Either implement or remove this struct. -func (a *clientFactoryAdapter) ClientFromDir(org, repo, dir string) (RepoClient, error) { - return nil, errors.New("no ClientFromDir implementation exists in the v1 git client") -} - -// Repo creates a client that operates on a new clone of the repo. -func (a *clientFactoryAdapter) ClientFor(org, repo string) (RepoClient, error) { - r, err := a.Client.Clone(org, repo) - return &repoClientAdapter{Repo: r}, err -} - -type repoClientAdapter struct { - *git.Repo -} - -func (a *repoClientAdapter) MergeAndCheckout(baseSHA string, mergeStrategy string, headSHAs ...string) error { - return a.Repo.MergeAndCheckout(baseSHA, types.PullRequestMergeType(mergeStrategy), headSHAs...) -} - -func (a *repoClientAdapter) MergeWithStrategy(commitlike, mergeStrategy string, opts ...MergeOpt) (bool, error) { - return a.Repo.MergeWithStrategy(commitlike, types.PullRequestMergeType(mergeStrategy)) -} - -func (a *repoClientAdapter) Clone(from string) error { - return errors.New("no Clone implementation exists in the v1 repo client") -} - -func (a *repoClientAdapter) Commit(title, body string) error { - return errors.New("no Commit implementation exists in the v1 repo client") -} - -func (a *repoClientAdapter) PushToFork(branch string, force bool) error { - return a.Repo.Push(branch, force) -} - -func (a *repoClientAdapter) PushToNamedFork(forkName, branch string, force bool) error { - return a.Repo.PushToNamedFork(forkName, branch, force) -} - -func (a *repoClientAdapter) CommitExists(sha string) (bool, error) { - return false, errors.New("no CommitExists implementation exists in the v1 repo client") -} - -func (a *repoClientAdapter) PushToCentral(branch string, force bool) error { - return errors.New("no PushToCentral implementation exists in the v1 repo client") -} - -func (a *repoClientAdapter) MirrorClone() error { - return errors.New("no MirrorClone implementation exists in the v1 repo client") -} - -func (a *repoClientAdapter) Fetch(arg ...string) error { - // TODO(mpherman): Bring adapter Fetch in line with gitv2 fetch without hard-coding origin as remote. - args := append([]string{"origin"}, arg...) - return a.Repo.Fetch(args...) -} - -func (a *repoClientAdapter) FetchFromRemote(resolver RemoteResolver, branch string) error { - return errors.New("no FetchFromRemote implementation exists in the v1 repo client") -} - -func (a *repoClientAdapter) RemoteUpdate() error { - return errors.New("no RemoteUpdate implementation exists in the v1 repo client") -} - -func (a *repoClientAdapter) FetchRef(refspec string) error { - return errors.New("no FetchRef implementation exists in the v1 repo client") -} diff --git a/third_party/k8s.io/test-infra/prow/git/v2/client_factory.go b/third_party/k8s.io/test-infra/prow/git/v2/client_factory.go deleted file mode 100644 index e2a8d60..0000000 --- a/third_party/k8s.io/test-infra/prow/git/v2/client_factory.go +++ /dev/null @@ -1,314 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package git - -import ( - "fmt" - "os" - "os/exec" - "path" - "runtime" - "sync" - - "github.com/sirupsen/logrus" - utilpointer "k8s.io/utils/pointer" -) - -// ClientFactory knows how to create clientFactory for repos -type ClientFactory interface { - // ClientFromDir creates a client that operates on a repo that has already - // been cloned to the given directory. - ClientFromDir(org, repo, dir string) (RepoClient, error) - // ClientFor creates a client that operates on a new clone of the repo. - ClientFor(org, repo string) (RepoClient, error) - - // Clean removes the caches used to generate clients - Clean() error -} - -// RepoClient exposes interactions with a git repo -type RepoClient interface { - Publisher - Interactor -} - -type repoClient struct { - publisher - interactor -} - -type ClientFactoryOpts struct { - // Host, defaults to "github.com" if unset - Host string - // UseSSH, defaults to false - UseSSH *bool - // The directory in which the cache should be - // created. Defaults to the "/var/tmp" on - // Linux and os.TempDir otherwise - CacheDirBase *string - // If unset, publishing action will error - Username LoginGetter - // If unset, publishing action will error - Token TokenGetter - // The git user to use. - GitUser GitUserGetter - // The censor to use. Not needed for anonymous - // actions. - Censor Censor - // Path to the httpCookieFile that will be used to authenticate client - CookieFilePath string -} - -// Apply allows to use a ClientFactoryOpts as Opt -func (cfo *ClientFactoryOpts) Apply(target *ClientFactoryOpts) { - if cfo.Host != "" { - target.Host = cfo.Host - } - if cfo.UseSSH != nil { - target.UseSSH = cfo.UseSSH - } - if cfo.CacheDirBase != nil { - target.CacheDirBase = cfo.CacheDirBase - } - if cfo.Token != nil { - target.Token = cfo.Token - } - if cfo.GitUser != nil { - target.GitUser = cfo.GitUser - } - if cfo.Censor != nil { - target.Censor = cfo.Censor - } - if cfo.Username != nil { - target.Username = cfo.Username - } - if cfo.CookieFilePath != "" { - target.CookieFilePath = cfo.CookieFilePath - } -} - -// ClientFactoryOpts allows to manipulate the options for a ClientFactory -type ClientFactoryOpt func(*ClientFactoryOpts) - -func defaultClientFactoryOpts(cfo *ClientFactoryOpts) { - if cfo.Host == "" { - cfo.Host = "github.com" - } - if cfo.CacheDirBase == nil { - switch runtime.GOOS { - case "linux": - cfo.CacheDirBase = utilpointer.StringPtr("/var/tmp") - default: - cfo.CacheDirBase = utilpointer.StringPtr("") - } - } - if cfo.Censor == nil { - cfo.Censor = func(in []byte) []byte { return in } - } -} - -// NewClientFactory allows for the creation of repository clients. It uses github.com -// without authentication by default, if UseSSH then returns -// sshRemoteResolverFactory, and if CookieFilePath is provided then returns -// gerritResolverFactory(Assuming that git http.cookiefile is used only by -// Gerrit, this function needs to be updated if it turned out that this -// assumtpion is not correct.) -func NewClientFactory(opts ...ClientFactoryOpt) (ClientFactory, error) { - o := ClientFactoryOpts{} - defaultClientFactoryOpts(&o) - for _, opt := range opts { - opt(&o) - } - - if o.CookieFilePath != "" { - if output, err := exec.Command("git", "config", "--global", "http.cookiefile", o.CookieFilePath).CombinedOutput(); err != nil { - return nil, fmt.Errorf("unable to configure http.cookiefile.\nOutput: %s\nError: %w", string(output), err) - } - } - - cacheDir, err := os.MkdirTemp(*o.CacheDirBase, "gitcache") - if err != nil { - return nil, err - } - var remote RemoteResolverFactory - if o.UseSSH != nil && *o.UseSSH { - remote = &sshRemoteResolverFactory{ - host: o.Host, - username: o.Username, - } - } else if o.CookieFilePath != "" { - remote = &gerritResolverFactory{} - } else { - remote = &httpResolverFactory{ - host: o.Host, - username: o.Username, - token: o.Token, - } - } - return &clientFactory{ - cacheDir: cacheDir, - cacheDirBase: *o.CacheDirBase, - remote: remote, - gitUser: o.GitUser, - censor: o.Censor, - masterLock: &sync.Mutex{}, - repoLocks: map[string]*sync.Mutex{}, - logger: logrus.WithField("client", "git"), - cookieFilePath: o.CookieFilePath, - }, nil -} - -// NewLocalClientFactory allows for the creation of repository clients -// based on a local filepath remote for testing -func NewLocalClientFactory(baseDir string, gitUser GitUserGetter, censor Censor) (ClientFactory, error) { - cacheDir, err := os.MkdirTemp("", "gitcache") - if err != nil { - return nil, err - } - return &clientFactory{ - cacheDir: cacheDir, - remote: &pathResolverFactory{baseDir: baseDir}, - gitUser: gitUser, - censor: censor, - masterLock: &sync.Mutex{}, - repoLocks: map[string]*sync.Mutex{}, - logger: logrus.WithField("client", "git"), - }, nil -} - -type clientFactory struct { - remote RemoteResolverFactory - gitUser GitUserGetter - censor Censor - logger *logrus.Entry - cookieFilePath string - - // cacheDir is the root under which cached clones of repos are created - cacheDir string - // cacheDirBase is the basedir under which create tempdirs - cacheDirBase string - // masterLock guards mutations to the repoLocks records - masterLock *sync.Mutex - // repoLocks guard mutating access to subdirectories under the cacheDir - repoLocks map[string]*sync.Mutex -} - -// bootstrapClients returns a repository client and cloner for a dir. -func (c *clientFactory) bootstrapClients(org, repo, dir string) (cacher, cloner, RepoClient, error) { - if dir == "" { - workdir, err := os.Getwd() - if err != nil { - return nil, nil, nil, err - } - dir = workdir - } - logger := c.logger.WithFields(logrus.Fields{"org": org, "repo": repo}) - logger.WithField("dir", dir).Debug("Creating a pre-initialized client.") - executor, err := NewCensoringExecutor(dir, c.censor, logger) - if err != nil { - return nil, nil, nil, err - } - var remote RemoteResolverFactory - remote = c.remote - client := &repoClient{ - publisher: publisher{ - remotes: remotes{ - publishRemote: remote.PublishRemote(org, repo), - centralRemote: remote.CentralRemote(org, repo), - }, - executor: executor, - info: c.gitUser, - logger: logger, - }, - interactor: interactor{ - dir: dir, - remote: remote.CentralRemote(org, repo), - executor: executor, - logger: logger, - }, - } - return client, client, client, nil -} - -// ClientFromDir returns a repository client for a directory that's already initialized with content. -// If the directory isn't specified, the current working directory is used. -func (c *clientFactory) ClientFromDir(org, repo, dir string) (RepoClient, error) { - _, _, client, err := c.bootstrapClients(org, repo, dir) - return client, err -} - -// ClientFor returns a repository client for the specified repository. -// This function may take a long time if it is the first time cloning the repo. -// In that case, it must do a full git mirror clone. For large repos, this can -// take a while. Once that is done, it will do a git fetch instead of a clone, -// which will usually take at most a few seconds. -// -// org and repo are used for determining where the repo is cloned, cloneURI -// overrides org/repo for cloning. -func (c *clientFactory) ClientFor(org, repo string) (RepoClient, error) { - cacheDir := path.Join(c.cacheDir, org, repo) - c.logger.WithFields(logrus.Fields{"org": org, "repo": repo, "dir": cacheDir}).Debug("Creating a client from the cache.") - cacheClientCacher, _, _, err := c.bootstrapClients(org, repo, cacheDir) - if err != nil { - return nil, err - } - - repoDir, err := os.MkdirTemp(c.cacheDirBase, "gitrepo") - if err != nil { - return nil, err - } - _, repoClientCloner, repoClient, err := c.bootstrapClients(org, repo, repoDir) - if err != nil { - return nil, err - } - c.masterLock.Lock() - if _, exists := c.repoLocks[cacheDir]; !exists { - c.repoLocks[cacheDir] = &sync.Mutex{} - } - c.masterLock.Unlock() - c.repoLocks[cacheDir].Lock() - defer c.repoLocks[cacheDir].Unlock() - if _, err := os.Stat(path.Join(cacheDir, "HEAD")); os.IsNotExist(err) { - // we have not yet cloned this repo, we need to do a full clone - if err := os.MkdirAll(cacheDir, os.ModePerm); err != nil && !os.IsExist(err) { - return nil, err - } - if err := cacheClientCacher.MirrorClone(); err != nil { - return nil, err - } - } else if err != nil { - // something unexpected happened - return nil, err - } else { - // we have cloned the repo previously, but will refresh it - if err := cacheClientCacher.RemoteUpdate(); err != nil { - return nil, err - } - } - - // initialize the new derivative repo from the cache - if err := repoClientCloner.Clone(cacheDir); err != nil { - return nil, err - } - - return repoClient, nil -} - -// Clean removes the caches used to generate clients -func (c *clientFactory) Clean() error { - return os.RemoveAll(c.cacheDir) -} diff --git a/third_party/k8s.io/test-infra/prow/git/v2/executor.go b/third_party/k8s.io/test-infra/prow/git/v2/executor.go deleted file mode 100644 index 9f31c11..0000000 --- a/third_party/k8s.io/test-infra/prow/git/v2/executor.go +++ /dev/null @@ -1,78 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package git - -import ( - "os/exec" - "strings" - "time" - - "github.com/sirupsen/logrus" -) - -// executor knows how to execute Git commands -type executor interface { - Run(args ...string) ([]byte, error) -} - -// Censor censors content to remove secrets -type Censor func(content []byte) []byte - -func NewCensoringExecutor(dir string, censor Censor, logger *logrus.Entry) (executor, error) { - g, err := exec.LookPath("git") - if err != nil { - return nil, err - } - return &censoringExecutor{ - logger: logger.WithField("client", "git"), - dir: dir, - git: g, - censor: censor, - execute: func(dir, command string, args ...string) ([]byte, error) { - c := exec.Command(command, args...) - c.Dir = dir - return c.CombinedOutput() - }, - }, nil -} - -type censoringExecutor struct { - // logger will be used to log git operations - logger *logrus.Entry - // dir is the location of this repo. - dir string - // git is the path to the git binary. - git string - // censor removes sensitive data from output - censor Censor - // execute executes a command - execute func(dir, command string, args ...string) ([]byte, error) -} - -func (e *censoringExecutor) Run(args ...string) ([]byte, error) { - logger := e.logger.WithField("args", strings.Join(args, " ")) - now := time.Now() - b, err := e.execute(e.dir, e.git, args...) - b = e.censor(b) - if err != nil { - logger.WithError(err).WithField("output", string(b)).Debug("Running command failed.") - } else { - logger.Debug("Running command succeeded.") - } - logger.WithFields(logrus.Fields{"duration": time.Since(now), "dir": e.dir}).Info("Time taken to execute command.") - return b, err -} diff --git a/third_party/k8s.io/test-infra/prow/git/v2/fakes.go b/third_party/k8s.io/test-infra/prow/git/v2/fakes.go deleted file mode 100644 index c4abbf2..0000000 --- a/third_party/k8s.io/test-infra/prow/git/v2/fakes.go +++ /dev/null @@ -1,52 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package git - -import ( - "fmt" - "strings" -) - -// fakeResolver allows for simple injections in tests -type fakeResolver struct { - out string - err error -} - -func (r *fakeResolver) Resolve() (string, error) { - return r.out, r.err -} - -type execResponse struct { - out []byte - err error -} - -// fakeExecutor is useful in testing for mocking an Executor -type fakeExecutor struct { - records [][]string - responses map[string]execResponse -} - -func (e *fakeExecutor) Run(args ...string) ([]byte, error) { - e.records = append(e.records, args) - key := strings.Join(args, " ") - if response, ok := e.responses[key]; ok { - return response.out, response.err - } - return []byte{}, fmt.Errorf("no response configured for %s", key) -} diff --git a/third_party/k8s.io/test-infra/prow/git/v2/interactor.go b/third_party/k8s.io/test-infra/prow/git/v2/interactor.go deleted file mode 100644 index 1908268..0000000 --- a/third_party/k8s.io/test-infra/prow/git/v2/interactor.go +++ /dev/null @@ -1,461 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package git - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "os" - "strings" - - "github.com/sirupsen/logrus" -) - -// Interactor knows how to operate on a git repository cloned from GitHub -// using a local cache. -type Interactor interface { - // Directory exposes the directory in which the repository has been cloned - Directory() string - // Clean removes the repository. It is up to the user to call this once they are done - Clean() error - // ResetHard runs `git reset --hard` - ResetHard(commitlike string) error - // IsDirty checks whether the repo is dirty or not - IsDirty() (bool, error) - // Checkout runs `git checkout` - Checkout(commitlike string) error - // RevParse runs `git rev-parse` - RevParse(commitlike string) (string, error) - // BranchExists determines if a branch with the name exists - BranchExists(branch string) bool - // CommitExists determines if the commit SHA exists locally - CommitExists(sha string) (bool, error) - // CheckoutNewBranch creates a new branch from HEAD and checks it out - CheckoutNewBranch(branch string) error - // Merge merges the commitlike into the current HEAD - Merge(commitlike string) (bool, error) - // MergeWithStrategy merges the commitlike into the current HEAD with the strategy - MergeWithStrategy(commitlike, mergeStrategy string, opts ...MergeOpt) (bool, error) - // MergeAndCheckout merges all commitlikes into the current HEAD with the appropriate strategy - MergeAndCheckout(baseSHA string, mergeStrategy string, headSHAs ...string) error - // Am calls `git am` - Am(path string) error - // Fetch calls `git fetch arg...` - Fetch(arg ...string) error - // FetchRef fetches the refspec - FetchRef(refspec string) error - // FetchFromRemote fetches the branch of the given remote - FetchFromRemote(remote RemoteResolver, branch string) error - // CheckoutPullRequest fetches and checks out the synthetic refspec from GitHub for a pull request HEAD - CheckoutPullRequest(number int) error - // Config runs `git config` - Config(args ...string) error - // Diff runs `git diff` - Diff(head, sha string) (changes []string, err error) - // MergeCommitsExistBetween determines if merge commits exist between target and HEAD - MergeCommitsExistBetween(target, head string) (bool, error) - // ShowRef returns the commit for a commitlike. Unlike rev-parse it does not require a checkout. - ShowRef(commitlike string) (string, error) -} - -// cacher knows how to cache and update repositories in a central cache -type cacher interface { - // MirrorClone sets up a mirror of the source repository. - MirrorClone() error - // RemoteUpdate fetches all updates from the remote. - RemoteUpdate() error -} - -// cloner knows how to clone repositories from a central cache -type cloner interface { - // Clone clones the repository from a local path. - Clone(from string) error -} - -// MergeOpt holds options for git merge operations. -// Currently only commit message option is supported. -type MergeOpt struct { - CommitMessage string -} - -type interactor struct { - executor executor - remote RemoteResolver - dir string - logger *logrus.Entry -} - -// Directory exposes the directory in which this repository has been cloned -func (i *interactor) Directory() string { - return i.dir -} - -// Clean cleans up the repository from the on-disk cache -func (i *interactor) Clean() error { - return os.RemoveAll(i.dir) -} - -// ResetHard runs `git reset --hard` -func (i *interactor) ResetHard(commitlike string) error { - // `git reset --hard` doesn't cleanup untracked file - i.logger.Info("Clean untracked files and dirs.") - if out, err := i.executor.Run("clean", "-df"); err != nil { - return fmt.Errorf("error clean -df: %v. output: %s", err, string(out)) - } - i.logger.WithField("commitlike", commitlike).Info("Reset hard.") - if out, err := i.executor.Run("reset", "--hard", commitlike); err != nil { - return fmt.Errorf("error reset hard %s: %v. output: %s", commitlike, err, string(out)) - } - return nil -} - -// IsDirty checks whether the repo is dirty or not -func (i *interactor) IsDirty() (bool, error) { - i.logger.Info("Checking is dirty.") - b, err := i.executor.Run("status", "--porcelain") - if err != nil { - return false, fmt.Errorf("error add -A: %v. output: %s", err, string(b)) - } - return len(b) > 0, nil -} - -// Clone clones the repository from a local path. -func (i *interactor) Clone(from string) error { - i.logger.Infof("Creating a clone of the repo at %s from %s", i.dir, from) - if out, err := i.executor.Run("clone", from, i.dir); err != nil { - return fmt.Errorf("error creating a clone: %w %v", err, string(out)) - } - return nil -} - -// MirrorClone sets up a mirror of the source repository. -func (i *interactor) MirrorClone() error { - i.logger.Infof("Creating a mirror of the repo at %s", i.dir) - remote, err := i.remote() - if err != nil { - return fmt.Errorf("could not resolve remote for cloning: %w", err) - } - if out, err := i.executor.Run("clone", "--mirror", remote, i.dir); err != nil { - return fmt.Errorf("error creating a mirror clone: %w %v", err, string(out)) - } - return nil -} - -// Checkout runs git checkout. -func (i *interactor) Checkout(commitlike string) error { - i.logger.Infof("Checking out %q", commitlike) - if out, err := i.executor.Run("checkout", commitlike); err != nil { - return fmt.Errorf("error checking out %q: %w %v", commitlike, err, string(out)) - } - return nil -} - -// RevParse runs git rev-parse. -func (i *interactor) RevParse(commitlike string) (string, error) { - i.logger.Infof("Parsing revision %q", commitlike) - out, err := i.executor.Run("rev-parse", commitlike) - if err != nil { - return "", fmt.Errorf("error parsing %q: %w %v", commitlike, err, string(out)) - } - return string(out), nil -} - -// BranchExists returns true if branch exists in heads. -func (i *interactor) BranchExists(branch string) bool { - i.logger.Infof("Checking if branch %q exists", branch) - _, err := i.executor.Run("ls-remote", "--exit-code", "--heads", "origin", branch) - return err == nil -} - -func (i *interactor) CommitExists(sha string) (bool, error) { - i.logger.WithField("SHA", sha).Info("Checking if SHA exists") - _, err := i.executor.Run("branch", "--contains", sha) - if err != nil && strings.Contains(err.Error(), "no such commit") { - return false, nil - } else if err != nil { - return false, fmt.Errorf("Unable to check if commit exists: %v", err) - } - return true, nil - -} - -// CheckoutNewBranch creates a new branch and checks it out. -func (i *interactor) CheckoutNewBranch(branch string) error { - i.logger.Infof("Checking out new branch %q", branch) - if out, err := i.executor.Run("checkout", "-b", branch); err != nil { - return fmt.Errorf("error checking out new branch %q: %w %v", branch, err, string(out)) - } - return nil -} - -// Merge attempts to merge commitlike into the current branch. It returns true -// if the merge completes. It returns an error if the abort fails. -func (i *interactor) Merge(commitlike string) (bool, error) { - return i.MergeWithStrategy(commitlike, "merge") -} - -// MergeWithStrategy attempts to merge commitlike into the current branch given the merge strategy. -// It returns true if the merge completes. if the merge does not complete successfully, we try to -// abort it and return an error if the abort fails. -func (i *interactor) MergeWithStrategy(commitlike, mergeStrategy string, opts ...MergeOpt) (bool, error) { - i.logger.Infof("Merging %q using the %q strategy", commitlike, mergeStrategy) - switch mergeStrategy { - case "merge": - return i.mergeMerge(commitlike, opts...) - case "squash": - return i.squashMerge(commitlike) - case "rebase": - return i.mergeRebase(commitlike) - case "ifNecessary": - return i.mergeIfNecessary(commitlike, opts...) - default: - return false, fmt.Errorf("merge strategy %q is not supported", mergeStrategy) - } -} - -func (i *interactor) mergeHelper(args []string, commitlike string, opts ...MergeOpt) (bool, error) { - if len(opts) == 0 { - args = append(args, []string{"-m", "merge"}...) - } else { - for _, opt := range opts { - args = append(args, []string{"-m", opt.CommitMessage}...) - } - } - - args = append(args, commitlike) - - out, err := i.executor.Run(args...) - if err == nil { - return true, nil - } - i.logger.WithError(err).Warnf("Error merging %q: %s", commitlike, string(out)) - if out, err := i.executor.Run("merge", "--abort"); err != nil { - return false, fmt.Errorf("error aborting merge of %q: %w %v", commitlike, err, string(out)) - } - return false, nil -} - -func (i *interactor) mergeMerge(commitlike string, opts ...MergeOpt) (bool, error) { - args := []string{"merge", "--no-ff", "--no-stat"} - return i.mergeHelper(args, commitlike, opts...) -} - -func (i *interactor) mergeIfNecessary(commitlike string, opts ...MergeOpt) (bool, error) { - args := []string{"merge", "--ff", "--no-stat"} - return i.mergeHelper(args, commitlike, opts...) -} - -func (i *interactor) squashMerge(commitlike string) (bool, error) { - out, err := i.executor.Run("merge", "--squash", "--no-stat", commitlike) - if err != nil { - i.logger.WithError(err).Warnf("Error staging merge for %q: %s", commitlike, string(out)) - if out, err := i.executor.Run("reset", "--hard", "HEAD"); err != nil { - return false, fmt.Errorf("error aborting merge of %q: %w %v", commitlike, err, string(out)) - } - return false, nil - } - out, err = i.executor.Run("commit", "--no-stat", "-m", "merge") - if err != nil { - i.logger.WithError(err).Warnf("Error committing merge for %q: %s", commitlike, string(out)) - if out, err := i.executor.Run("reset", "--hard", "HEAD"); err != nil { - return false, fmt.Errorf("error aborting merge of %q: %w %v", commitlike, err, string(out)) - } - return false, nil - } - return true, nil -} - -func (i *interactor) mergeRebase(commitlike string) (bool, error) { - if commitlike == "" { - return false, errors.New("branch must be set") - } - - headRev, err := i.revParse("HEAD") - if err != nil { - i.logger.WithError(err).Infof("Failed to parse HEAD revision") - return false, err - } - headRev = strings.TrimSuffix(headRev, "\n") - - b, err := i.executor.Run("rebase", "--no-stat", headRev, commitlike) - if err != nil { - i.logger.WithField("out", string(b)).WithError(err).Infof("Rebase failed.") - if b, err := i.executor.Run("rebase", "--abort"); err != nil { - return false, fmt.Errorf("error aborting after failed rebase for commitlike %s: %v. output: %s", commitlike, err, string(b)) - } - return false, nil - } - return true, nil -} - -func (i *interactor) revParse(args ...string) (string, error) { - fullArgs := append([]string{"rev-parse"}, args...) - b, err := i.executor.Run(fullArgs...) - if err != nil { - return "", errors.New(string(b)) - } - return string(b), nil -} - -// Only the `merge` and `squash` strategies are supported. -func (i *interactor) MergeAndCheckout(baseSHA string, mergeStrategy string, headSHAs ...string) error { - if baseSHA == "" { - return errors.New("baseSHA must be set") - } - if err := i.Checkout(baseSHA); err != nil { - return err - } - for _, headSHA := range headSHAs { - ok, err := i.MergeWithStrategy(headSHA, mergeStrategy) - if err != nil { - return err - } else if !ok { - return fmt.Errorf("failed to merge %q", headSHA) - } - } - return nil -} - -// Am tries to apply the patch in the given path into the current branch -// by performing a three-way merge (similar to git cherry-pick). It returns -// an error if the patch cannot be applied. -func (i *interactor) Am(path string) error { - i.logger.Infof("Applying patch at %s", path) - out, err := i.executor.Run("am", "--3way", path) - if err == nil { - return nil - } - i.logger.WithError(err).Infof("Patch apply failed with output: %s", string(out)) - if abortOut, abortErr := i.executor.Run("am", "--abort"); err != nil { - i.logger.WithError(abortErr).Warningf("Aborting patch apply failed with output: %s", string(abortOut)) - } - return errors.New(string(bytes.TrimPrefix(out, []byte("The copy of the patch that failed is found in: .git/rebase-apply/patch")))) -} - -// RemoteUpdate fetches all updates from the remote. -func (i *interactor) RemoteUpdate() error { - i.logger.Info("Updating from remote") - if out, err := i.executor.Run("remote", "update", "--prune"); err != nil { - return fmt.Errorf("error updating: %w %v", err, string(out)) - } - return nil -} - -// Fetch fetches all updates from the remote. -func (i *interactor) Fetch(arg ...string) error { - remote, err := i.remote() - if err != nil { - return fmt.Errorf("could not resolve remote for fetching: %w", err) - } - arg = append([]string{"fetch", remote}, arg...) - i.logger.Infof("Fetching from %s", remote) - if out, err := i.executor.Run(arg...); err != nil { - return fmt.Errorf("error fetching: %w %v", err, string(out)) - } - return nil -} - -// FetchRef fetches a refspec from the remote and leaves it as FETCH_HEAD. -func (i *interactor) FetchRef(refspec string) error { - remote, err := i.remote() - if err != nil { - return fmt.Errorf("could not resolve remote for fetching: %w", err) - } - i.logger.Infof("Fetching %q from %s", refspec, remote) - if out, err := i.executor.Run("fetch", remote, refspec); err != nil { - return fmt.Errorf("error fetching %q: %w %v", refspec, err, string(out)) - } - return nil -} - -// FetchFromRemote fetches all update from a specific remote and branch and leaves it as FETCH_HEAD. -func (i *interactor) FetchFromRemote(remote RemoteResolver, branch string) error { - r, err := remote() - if err != nil { - return fmt.Errorf("couldn't get remote: %w", err) - } - - i.logger.Infof("Fetching %s from %s", branch, r) - if out, err := i.executor.Run("fetch", r, branch); err != nil { - return fmt.Errorf("error fetching %s from %s: %w %v", branch, r, err, string(out)) - } - return nil -} - -// CheckoutPullRequest fetches the HEAD of a pull request using a synthetic refspec -// available on GitHub remotes and creates a branch at that commit. -func (i *interactor) CheckoutPullRequest(number int) error { - i.logger.Infof("Checking out pull request %d", number) - if err := i.FetchRef(fmt.Sprintf("pull/%d/head", number)); err != nil { - return err - } - if err := i.Checkout("FETCH_HEAD"); err != nil { - return err - } - if err := i.CheckoutNewBranch(fmt.Sprintf("pull%d", number)); err != nil { - return err - } - return nil -} - -// Config runs git config. -func (i *interactor) Config(args ...string) error { - i.logger.WithField("args", args).Info("Configuring.") - if out, err := i.executor.Run(append([]string{"config"}, args...)...); err != nil { - return fmt.Errorf("error configuring %v: %w %v", args, err, string(out)) - } - return nil -} - -// Diff lists the difference between the two references, returning the output -// line by line. -func (i *interactor) Diff(head, sha string) ([]string, error) { - i.logger.Infof("Finding the differences between %q and %q", head, sha) - out, err := i.executor.Run("diff", head, sha, "--name-only") - if err != nil { - return nil, err - } - var changes []string - scan := bufio.NewScanner(bytes.NewReader(out)) - scan.Split(bufio.ScanLines) - for scan.Scan() { - changes = append(changes, scan.Text()) - } - return changes, nil -} - -// MergeCommitsExistBetween runs 'git log .. --merged' to verify -// if merge commits exist between "target" and "head". -func (i *interactor) MergeCommitsExistBetween(target, head string) (bool, error) { - i.logger.Infof("Determining if merge commits exist between %q and %q", target, head) - out, err := i.executor.Run("log", fmt.Sprintf("%s..%s", target, head), "--oneline", "--merges") - if err != nil { - return false, fmt.Errorf("error verifying if merge commits exist between %q and %q: %v %s", target, head, err, string(out)) - } - return len(out) != 0, nil -} - -func (i *interactor) ShowRef(commitlike string) (string, error) { - i.logger.Infof("Getting the commit sha for commitlike %s", commitlike) - out, err := i.executor.Run("show-ref", "-s", commitlike) - if err != nil { - return "", fmt.Errorf("failed to get commit sha for commitlike %s: %w", commitlike, err) - } - return strings.TrimSpace(string(out)), nil -} diff --git a/third_party/k8s.io/test-infra/prow/git/v2/publisher.go b/third_party/k8s.io/test-infra/prow/git/v2/publisher.go deleted file mode 100644 index 72752fd..0000000 --- a/third_party/k8s.io/test-infra/prow/git/v2/publisher.go +++ /dev/null @@ -1,114 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package git - -import ( - "errors" - "fmt" - - "github.com/sirupsen/logrus" -) - -// Publisher knows how to publish local work to a remote -type Publisher interface { - // Commit stages all changes and commits them with the message - Commit(title, body string) error - // PushToFork pushes the local state to the fork remote - PushToFork(branch string, force bool) error - // PushToNamedFork is used for when the fork has a different name than the original repp - PushToNamedFork(forkName, branch string, force bool) error - // PushToCentral pushes the local state to the central remote - PushToCentral(branch string, force bool) error -} - -// GitUserGetter fetches a name and email for us in git commits on-demand -type GitUserGetter func() (name, email string, err error) - -type remotes struct { - publishRemote RemoteResolver - centralRemote RemoteResolver -} - -type publisher struct { - executor executor - remotes remotes - info GitUserGetter - logger *logrus.Entry -} - -// Commit adds all of the current content to the index and creates a commit -func (p *publisher) Commit(title, body string) error { - p.logger.Infof("Committing changes with title %q", title) - name, email, err := p.info() - if err != nil { - return err - } - commands := [][]string{ - {"add", "--all"}, - {"commit", "--message", title, "--message", body, "--author", fmt.Sprintf("%s <%s>", name, email)}, - } - for _, command := range commands { - if out, err := p.executor.Run(command...); err != nil { - return fmt.Errorf("error committing %q: %w %v", title, err, string(out)) - } - } - return nil -} - -func (p *publisher) PushToNamedFork(forkName, branch string, force bool) error { - return errors.New("pushToNamedFork is not implemented in the v2 client") -} - -// PublishPush pushes the local state to the publish remote -func (p *publisher) PushToFork(branch string, force bool) error { - remote, err := p.remotes.publishRemote() - if err != nil { - return err - } - - args := []string{"push"} - if force { - args = append(args, "--force") - } - args = append(args, []string{remote, branch}...) - - p.logger.Infof("Pushing branch %q to %q", branch, remote) - if out, err := p.executor.Run(args...); err != nil { - return fmt.Errorf("error pushing %q: %w %v", branch, err, string(out)) - } - return nil -} - -// CentralPush pushes the local state to the central remote -func (p *publisher) PushToCentral(branch string, force bool) error { - remote, err := p.remotes.centralRemote() - if err != nil { - return err - } - - args := []string{"push"} - if force { - args = append(args, "--force") - } - args = append(args, []string{remote, branch}...) - - p.logger.Infof("Pushing branch %q to %q", branch, remote) - if out, err := p.executor.Run(args...); err != nil { - return fmt.Errorf("error pushing %q: %w %v", branch, err, string(out)) - } - return nil -} diff --git a/third_party/k8s.io/test-infra/prow/git/v2/remote.go b/third_party/k8s.io/test-infra/prow/git/v2/remote.go deleted file mode 100644 index a62e052..0000000 --- a/third_party/k8s.io/test-infra/prow/git/v2/remote.go +++ /dev/null @@ -1,166 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package git - -import ( - "errors" - "fmt" - "net/url" - "path" - - gerritsource "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/gerrit/source" -) - -// RemoteResolverFactory knows how to construct remote resolvers for -// authoritative central remotes (to pull from) and publish remotes -// (to push to) for a repository. These resolvers are called at run-time -// to determine remotes for git commands. -type RemoteResolverFactory interface { - // CentralRemote returns a resolver for a remote server with an - // authoritative version of the repository. This type of remote - // is useful for fetching refs and cloning. - CentralRemote(org, repo string) RemoteResolver - // PublishRemote returns a resolver for a remote server with a - // personal fork of the repository. This type of remote is most - // useful for publishing local changes. - PublishRemote(org, repo string) RemoteResolver -} - -// RemoteResolver knows how to construct a remote URL for git calls -type RemoteResolver func() (string, error) - -// LoginGetter fetches a GitHub login on-demand -type LoginGetter func() (login string, err error) - -// TokenGetter fetches a GitHub OAuth token on-demand -type TokenGetter func() []byte - -type sshRemoteResolverFactory struct { - host string - username LoginGetter -} - -// CentralRemote creates a remote resolver that refers to an authoritative remote -// for the repository. -func (f *sshRemoteResolverFactory) CentralRemote(org, repo string) RemoteResolver { - remote := fmt.Sprintf("git@%s:%s/%s.git", f.host, org, repo) - return func() (string, error) { - return remote, nil - } -} - -// PublishRemote creates a remote resolver that refers to a user's remote -// for the repository that can be published to. -func (f *sshRemoteResolverFactory) PublishRemote(_, repo string) RemoteResolver { - return func() (string, error) { - org, err := f.username() - if err != nil { - return "", err - } - return fmt.Sprintf("git@%s:%s/%s.git", f.host, org, repo), nil - } -} - -type httpResolverFactory struct { - host string - // Optional, either both or none must be set - username LoginGetter - token TokenGetter -} - -// CentralRemote creates a remote resolver that refers to an authoritative remote -// for the repository. -func (f *httpResolverFactory) CentralRemote(org, repo string) RemoteResolver { - return HttpResolver(func() (*url.URL, error) { - return &url.URL{Scheme: "https", Host: f.host, Path: fmt.Sprintf("%s/%s", org, repo)}, nil - }, f.username, f.token) -} - -// PublishRemote creates a remote resolver that refers to a user's remote -// for the repository that can be published to. -func (f *httpResolverFactory) PublishRemote(_, repo string) RemoteResolver { - return HttpResolver(func() (*url.URL, error) { - if f.username == nil { - return nil, errors.New("username not configured, no publish repo available") - } - o, err := f.username() - if err != nil { - return nil, err - } - return &url.URL{Scheme: "https", Host: f.host, Path: fmt.Sprintf("%s/%s", o, repo)}, nil - }, f.username, f.token) -} - -// HttpResolver builds http URLs that may optionally contain simple auth credentials, resolved dynamically. -func HttpResolver(remote func() (*url.URL, error), username LoginGetter, token TokenGetter) RemoteResolver { - return func() (string, error) { - remote, err := remote() - if err != nil { - return "", fmt.Errorf("could not resolve remote: %w", err) - } - - if username != nil { - name, err := username() - if err != nil { - return "", fmt.Errorf("could not resolve username: %w", err) - } - remote.User = url.UserPassword(name, string(token())) - } - - return remote.String(), nil - } -} - -// pathResolverFactory generates resolvers for local path-based repositories, -// used in local integration testing only -type pathResolverFactory struct { - baseDir string -} - -// CentralRemote creates a remote resolver that refers to an authoritative remote -// for the repository. -func (f *pathResolverFactory) CentralRemote(org, repo string) RemoteResolver { - return func() (string, error) { - return path.Join(f.baseDir, org, repo), nil - } -} - -// PublishRemote creates a remote resolver that refers to a user's remote -// for the repository that can be published to. -func (f *pathResolverFactory) PublishRemote(org, repo string) RemoteResolver { - return func() (string, error) { - return path.Join(f.baseDir, org, repo), nil - } -} - -// gerritResolverFactory is meant to be used by Gerrit only. It's so different -// from GitHub that there is no way any of the remotes logic can be shared -// between these two providers. The resulting CentralRemote and PublishRemote -// are both the clone URI. -type gerritResolverFactory struct{} - -func (f *gerritResolverFactory) CentralRemote(org, repo string) RemoteResolver { - return func() (string, error) { - return gerritsource.CloneURIFromOrgRepo(org, repo), nil - } -} - -func (f *gerritResolverFactory) PublishRemote(org, repo string) RemoteResolver { - return func() (string, error) { - return gerritsource.CloneURIFromOrgRepo(org, repo), nil - } -} diff --git a/third_party/k8s.io/test-infra/prow/github/README.md b/third_party/k8s.io/test-infra/prow/github/README.md deleted file mode 100644 index 3a03047..0000000 --- a/third_party/k8s.io/test-infra/prow/github/README.md +++ /dev/null @@ -1,10 +0,0 @@ -DOCUMENTATION DEPRECATION NOTICE: This file is deprecated. Please refer to the -[new migrated -location](https://docs.prow.k8s.io/docs/github/). -Please do not edit this file; instead, make changes to the new location! - -The new location is served on the web at -https://docs.prow.k8s.io/docs/. - -This file will be deleted on 2023-02-28. - diff --git a/third_party/k8s.io/test-infra/prow/github/app_auth_roundtripper.go b/third_party/k8s.io/test-infra/prow/github/app_auth_roundtripper.go deleted file mode 100644 index 8b9af3c..0000000 --- a/third_party/k8s.io/test-infra/prow/github/app_auth_roundtripper.go +++ /dev/null @@ -1,283 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package github - -import ( - "context" - "crypto/rsa" - "fmt" - "net/http" - "net/url" - "reflect" - "regexp" - "runtime/debug" - "strings" - "sync" - "time" - - jwt "github.com/dgrijalva/jwt-go/v4" - - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/ghproxy/ghcache" -) - -const ( - githubOrgHeaderKey = "X-PROW-GITHUB-ORG" -) - -type appGitHubClient interface { - ListAppInstallations() ([]AppInstallation, error) - getAppInstallationToken(installationId int64) (*AppInstallationToken, error) - GetApp() (*App, error) -} - -func newAppsRoundTripper(appID string, privateKey func() *rsa.PrivateKey, upstream http.RoundTripper, githubClient appGitHubClient, v3BaseURLs []string) (*appsRoundTripper, error) { - roundTripper := &appsRoundTripper{ - appID: appID, - privateKey: privateKey, - upstream: upstream, - githubClient: githubClient, - hostPrefixMapping: make(map[string]string, len(v3BaseURLs)), - } - for _, baseURL := range v3BaseURLs { - url, err := url.Parse(baseURL) - if err != nil { - return nil, fmt.Errorf("failed to parse github-endpoint %s as URL: %w", baseURL, err) - } - roundTripper.hostPrefixMapping[url.Host] = url.Path - } - - return roundTripper, nil -} - -type appsRoundTripper struct { - appID string - appSlug string - appSlugLock sync.Mutex - privateKey func() *rsa.PrivateKey - installationLock sync.RWMutex - installations map[string]AppInstallation - tokenLock sync.RWMutex - tokens map[int64]*AppInstallationToken - upstream http.RoundTripper - githubClient appGitHubClient - hostPrefixMapping map[string]string -} - -// appsAuthError is returned by the appsRoundTripper if any issues were encountered -// trying to authorize the request. It signals the client to not retry. -type appsAuthError struct { - error -} - -func (*appsAuthError) Is(target error) bool { - _, ok := target.(*appsAuthError) - return ok -} - -func (arr *appsRoundTripper) canonicalizedPath(url *url.URL) string { - return strings.TrimPrefix(url.Path, arr.hostPrefixMapping[url.Host]) -} - -var installationPath = regexp.MustCompile(`^/repos/[^/]+/[^/]+/installation$`) - -func (arr *appsRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { - path := arr.canonicalizedPath(r.URL) - // We need to use a JWT when we are getting /app/* endpoints or installation information for a particular repo - if strings.HasPrefix(path, "/app") || installationPath.MatchString(path) { - if err := arr.addAppAuth(r); err != nil { - return nil, err - } - } else if err := arr.addAppInstallationAuth(r); err != nil { - return nil, err - } - - return arr.upstream.RoundTrip(r) -} - -// TimeNow is exposed so that it can be mocked by unit test, to ensure that -// addAppAuth always return consistent token when needed. -// DO NOT use it in prod -var TimeNow = func() time.Time { - return time.Now().UTC() -} - -func (arr *appsRoundTripper) addAppAuth(r *http.Request) *appsAuthError { - now := TimeNow() - expiresAt := now.Add(10 * time.Minute) - token, err := jwt.NewWithClaims(jwt.SigningMethodRS256, &jwt.StandardClaims{ - IssuedAt: jwt.NewTime(float64(now.Unix())), - ExpiresAt: jwt.NewTime(float64(expiresAt.Unix())), - Issuer: arr.appID, - }).SignedString(arr.privateKey()) - if err != nil { - return &appsAuthError{fmt.Errorf("failed to generate jwt: %w", err)} - } - - r.Header.Set("Authorization", "Bearer "+token) - r.Header.Set(ghcache.TokenExpiryAtHeader, expiresAt.Format(time.RFC3339)) - - // We call the /app endpoint to resolve the slug, so we can't set it there - if arr.canonicalizedPath(r.URL) == "/app" { - r.Header.Set(ghcache.TokenBudgetIdentifierHeader, arr.appID) - } else { - slug, err := arr.getSlug() - if err != nil { - return &appsAuthError{err} - } - r.Header.Set(ghcache.TokenBudgetIdentifierHeader, slug) - } - return nil -} - -func extractOrgFromContext(ctx context.Context) string { - var org string - if v := ctx.Value(githubOrgHeaderKey); v != nil { - org = v.(string) - } - return org -} - -func (arr *appsRoundTripper) addAppInstallationAuth(r *http.Request) *appsAuthError { - org := extractOrgFromContext(r.Context()) - if org == "" { - return &appsAuthError{fmt.Errorf("BUG apps auth requested but empty org, please report this to the test-infra repo. Stack: %s", string(debug.Stack()))} - } - - token, expiresAt, err := arr.installationTokenFor(org) - if err != nil { - return &appsAuthError{err} - } - - r.Header.Set("Authorization", "Bearer "+token) - r.Header.Set(ghcache.TokenExpiryAtHeader, expiresAt.Format(time.RFC3339)) - slug, err := arr.getSlug() - if err != nil { - return &appsAuthError{err} - } - - // Token budgets are set on organization level, so include it in the identifier - // to not mess up metrics. - r.Header.Set(ghcache.TokenBudgetIdentifierHeader, slug+" - "+org) - - return nil -} - -func (arr *appsRoundTripper) installationTokenFor(org string) (string, time.Time, error) { - installationID, err := arr.installationIDFor(org) - if err != nil { - return "", time.Time{}, fmt.Errorf("failed to get installation id for org %s: %w", org, err) - } - - token, expiresAt, err := arr.getTokenForInstallation(installationID) - if err != nil { - return "", time.Time{}, fmt.Errorf("failed to get an installation token for org %s: %w", org, err) - } - - return token, expiresAt, nil -} - -// installationIDFor returns the installation id for the given org. Unfortunately, -// GitHub does not expose what repos in that org the app is installed in, it -// only tells us if its all repos or a subset via the repository_selection -// property. -// Ref: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps#list-installations-for-the-authenticated-app -func (arr *appsRoundTripper) installationIDFor(org string) (int64, error) { - arr.installationLock.RLock() - id, found := arr.installations[org] - arr.installationLock.RUnlock() - if found { - return id.ID, nil - } - - arr.installationLock.Lock() - defer arr.installationLock.Unlock() - - // Check again in case a concurrent routine updated it while we waited for the lock - id, found = arr.installations[org] - if found { - return id.ID, nil - } - - installations, err := arr.githubClient.ListAppInstallations() - if err != nil { - return 0, fmt.Errorf("failed to list app installations: %w", err) - } - - installationsMap := make(map[string]AppInstallation, len(installations)) - for _, installation := range installations { - installationsMap[installation.Account.Login] = installation - } - - if equal := reflect.DeepEqual(arr.installations, installationsMap); equal { - return 0, fmt.Errorf("the github app is not installed in organization %s", org) - } - arr.installations = installationsMap - - id, found = installationsMap[org] - if !found { - return 0, fmt.Errorf("the github app is not installed in organization %s", org) - } - - return id.ID, nil -} - -func (arr *appsRoundTripper) getTokenForInstallation(installation int64) (string, time.Time, error) { - arr.tokenLock.RLock() - token, found := arr.tokens[installation] - arr.tokenLock.RUnlock() - - if found && token.ExpiresAt.Add(-time.Minute).After(time.Now()) { - return token.Token, token.ExpiresAt, nil - } - - arr.tokenLock.Lock() - defer arr.tokenLock.Unlock() - - // Check again in case a concurrent routine got a token while we waited for the lock - token, found = arr.tokens[installation] - if found && token.ExpiresAt.Add(-time.Minute).After(time.Now()) { - return token.Token, token.ExpiresAt, nil - } - - token, err := arr.githubClient.getAppInstallationToken(installation) - if err != nil { - return "", time.Time{}, fmt.Errorf("failed to get installation token from GitHub: %w", err) - } - - if arr.tokens == nil { - arr.tokens = map[int64]*AppInstallationToken{} - } - arr.tokens[installation] = token - - return token.Token, token.ExpiresAt, nil -} - -func (arr *appsRoundTripper) getSlug() (string, error) { - arr.appSlugLock.Lock() - defer arr.appSlugLock.Unlock() - - if arr.appSlug != "" { - return arr.appSlug, nil - } - response, err := arr.githubClient.GetApp() - if err != nil { - return "", err - } - - arr.appSlug = response.Slug - return arr.appSlug, nil -} diff --git a/third_party/k8s.io/test-infra/prow/github/client.go b/third_party/k8s.io/test-infra/prow/github/client.go deleted file mode 100644 index 44348a0..0000000 --- a/third_party/k8s.io/test-infra/prow/github/client.go +++ /dev/null @@ -1,5178 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package github - -import ( - "bytes" - "context" - "crypto/rsa" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "regexp" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/prometheus/client_golang/prometheus" - githubql "github.com/shurcooL/githubv4" - "github.com/sirupsen/logrus" - "golang.org/x/oauth2" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/apimachinery/pkg/util/sets" - - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/ghproxy/ghcache" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/version" -) - -type timeClient interface { - Sleep(time.Duration) - Until(time.Time) time.Duration -} - -type standardTime struct{} - -func (s *standardTime) Sleep(d time.Duration) { - time.Sleep(d) -} -func (s *standardTime) Until(t time.Time) time.Duration { - return time.Until(t) -} - -// OrganizationClient interface for organisation related API actions -type OrganizationClient interface { - IsMember(org, user string) (bool, error) - GetOrg(name string) (*Organization, error) - EditOrg(name string, config Organization) (*Organization, error) - ListOrgInvitations(org string) ([]OrgInvitation, error) - ListOrgMembers(org, role string) ([]TeamMember, error) - HasPermission(org, repo, user string, roles ...string) (bool, error) - GetUserPermission(org, repo, user string) (string, error) - UpdateOrgMembership(org, user string, admin bool) (*OrgMembership, error) - RemoveOrgMembership(org, user string) error -} - -// HookClient interface for hook related API actions -type HookClient interface { - ListOrgHooks(org string) ([]Hook, error) - ListRepoHooks(org, repo string) ([]Hook, error) - EditRepoHook(org, repo string, id int, req HookRequest) error - EditOrgHook(org string, id int, req HookRequest) error - CreateOrgHook(org string, req HookRequest) (int, error) - CreateRepoHook(org, repo string, req HookRequest) (int, error) - DeleteOrgHook(org string, id int, req HookRequest) error - DeleteRepoHook(org, repo string, id int, req HookRequest) error - ListCurrentUserRepoInvitations() ([]UserRepoInvitation, error) - AcceptUserRepoInvitation(invitationID int) error - ListCurrentUserOrgInvitations() ([]UserOrgInvitation, error) - AcceptUserOrgInvitation(org string) error -} - -// CommentClient interface for comment related API actions -type CommentClient interface { - CreateComment(org, repo string, number int, comment string) error - CreateCommentWithContext(ctx context.Context, org, repo string, number int, comment string) error - DeleteComment(org, repo string, id int) error - DeleteCommentWithContext(ctx context.Context, org, repo string, id int) error - EditComment(org, repo string, id int, comment string) error - EditCommentWithContext(ctx context.Context, org, repo string, id int, comment string) error - CreateCommentReaction(org, repo string, id int, reaction string) error - DeleteStaleComments(org, repo string, number int, comments []IssueComment, isStale func(IssueComment) bool) error - DeleteStaleCommentsWithContext(ctx context.Context, org, repo string, number int, comments []IssueComment, isStale func(IssueComment) bool) error -} - -// IssueClient interface for issue related API actions -type IssueClient interface { - CreateIssue(org, repo, title, body string, milestone int, labels, assignees []string) (int, error) - CreateIssueReaction(org, repo string, id int, reaction string) error - ListIssueComments(org, repo string, number int) ([]IssueComment, error) - ListIssueCommentsWithContext(ctx context.Context, org, repo string, number int) ([]IssueComment, error) - GetIssueLabels(org, repo string, number int) ([]Label, error) - ListIssueEvents(org, repo string, num int) ([]ListedIssueEvent, error) - AssignIssue(org, repo string, number int, logins []string) error - UnassignIssue(org, repo string, number int, logins []string) error - CloseIssue(org, repo string, number int) error - CloseIssueAsNotPlanned(org, repo string, number int) error - ReopenIssue(org, repo string, number int) error - FindIssues(query, sort string, asc bool) ([]Issue, error) - FindIssuesWithOrg(org, query, sort string, asc bool) ([]Issue, error) - ListOpenIssues(org, repo string) ([]Issue, error) - GetIssue(org, repo string, number int) (*Issue, error) - EditIssue(org, repo string, number int, issue *Issue) (*Issue, error) -} - -// PullRequestClient interface for pull request related API actions -type PullRequestClient interface { - GetPullRequests(org, repo string) ([]PullRequest, error) - GetPullRequest(org, repo string, number int) (*PullRequest, error) - EditPullRequest(org, repo string, number int, pr *PullRequest) (*PullRequest, error) - GetPullRequestPatch(org, repo string, number int) ([]byte, error) - CreatePullRequest(org, repo, title, body, head, base string, canModify bool) (int, error) - UpdatePullRequest(org, repo string, number int, title, body *string, open *bool, branch *string, canModify *bool) error - GetPullRequestChanges(org, repo string, number int) ([]PullRequestChange, error) - ListPullRequestComments(org, repo string, number int) ([]ReviewComment, error) - CreatePullRequestReviewComment(org, repo string, number int, rc ReviewComment) error - ListReviews(org, repo string, number int) ([]Review, error) - ClosePR(org, repo string, number int) error - ReopenPR(org, repo string, number int) error - CreateReview(org, repo string, number int, r DraftReview) error - RequestReview(org, repo string, number int, logins []string) error - UnrequestReview(org, repo string, number int, logins []string) error - Merge(org, repo string, pr int, details MergeDetails) error - IsMergeable(org, repo string, number int, SHA string) (bool, error) - ListPRCommits(org, repo string, number int) ([]RepositoryCommit, error) - UpdatePullRequestBranch(org, repo string, number int, expectedHeadSha *string) error -} - -// CommitClient interface for commit related API actions -type CommitClient interface { - CreateStatus(org, repo, SHA string, s Status) error - CreateStatusWithContext(ctx context.Context, org, repo, SHA string, s Status) error - ListStatuses(org, repo, ref string) ([]Status, error) - GetSingleCommit(org, repo, SHA string) (RepositoryCommit, error) - GetCombinedStatus(org, repo, ref string) (*CombinedStatus, error) - ListCheckRuns(org, repo, ref string) (*CheckRunList, error) - GetRef(org, repo, ref string) (string, error) - DeleteRef(org, repo, ref string) error - ListFileCommits(org, repo, path string) ([]RepositoryCommit, error) - CreateCheckRun(org, repo string, checkRun CheckRun) error -} - -// RepositoryClient interface for repository related API actions -type RepositoryClient interface { - GetRepo(owner, name string) (FullRepo, error) - GetRepos(org string, isUser bool) ([]Repo, error) - GetBranches(org, repo string, onlyProtected bool) ([]Branch, error) - GetBranchProtection(org, repo, branch string) (*BranchProtection, error) - RemoveBranchProtection(org, repo, branch string) error - UpdateBranchProtection(org, repo, branch string, config BranchProtectionRequest) error - AddRepoLabel(org, repo, label, description, color string) error - UpdateRepoLabel(org, repo, label, newName, description, color string) error - DeleteRepoLabel(org, repo, label string) error - GetRepoLabels(org, repo string) ([]Label, error) - AddLabel(org, repo string, number int, label string) error - AddLabelWithContext(ctx context.Context, org, repo string, number int, label string) error - AddLabels(org, repo string, number int, labels ...string) error - AddLabelsWithContext(ctx context.Context, org, repo string, number int, labels ...string) error - RemoveLabel(org, repo string, number int, label string) error - RemoveLabelWithContext(ctx context.Context, org, repo string, number int, label string) error - WasLabelAddedByHuman(org, repo string, number int, label string) (bool, error) - GetFile(org, repo, filepath, commit string) ([]byte, error) - GetDirectory(org, repo, dirpath, commit string) ([]DirectoryContent, error) - IsCollaborator(org, repo, user string) (bool, error) - ListCollaborators(org, repo string) ([]User, error) - CreateFork(owner, repo string) (string, error) - EnsureFork(forkingUser, org, repo string) (string, error) - ListRepoTeams(org, repo string) ([]Team, error) - CreateRepo(owner string, isUser bool, repo RepoCreateRequest) (*FullRepo, error) - UpdateRepo(owner, name string, repo RepoUpdateRequest) (*FullRepo, error) -} - -// TeamClient interface for team related API actions -type TeamClient interface { - CreateTeam(org string, team Team) (*Team, error) - EditTeam(org string, t Team) (*Team, error) - DeleteTeam(org string, id int) error - DeleteTeamBySlug(org, teamSlug string) error - ListTeams(org string) ([]Team, error) - UpdateTeamMembership(org string, id int, user string, maintainer bool) (*TeamMembership, error) - UpdateTeamMembershipBySlug(org, teamSlug, user string, maintainer bool) (*TeamMembership, error) - RemoveTeamMembership(org string, id int, user string) error - RemoveTeamMembershipBySlug(org, teamSlug, user string) error - ListTeamMembers(org string, id int, role string) ([]TeamMember, error) - ListTeamMembersBySlug(org, teamSlug, role string) ([]TeamMember, error) - ListTeamRepos(org string, id int) ([]Repo, error) - ListTeamReposBySlug(org, teamSlug string) ([]Repo, error) - UpdateTeamRepo(id int, org, repo string, permission TeamPermission) error - UpdateTeamRepoBySlug(org, teamSlug, repo string, permission TeamPermission) error - RemoveTeamRepo(id int, org, repo string) error - RemoveTeamRepoBySlug(org, teamSlug, repo string) error - ListTeamInvitations(org string, id int) ([]OrgInvitation, error) - ListTeamInvitationsBySlug(org, teamSlug string) ([]OrgInvitation, error) - TeamHasMember(org string, teamID int, memberLogin string) (bool, error) - TeamBySlugHasMember(org string, teamSlug string, memberLogin string) (bool, error) - GetTeamBySlug(slug string, org string) (*Team, error) -} - -// UserClient interface for user related API actions -type UserClient interface { - // BotUser will return details about the user the client runs as. Use BotUserChecker() - // instead when checking for comment authorship, as the Username in comments might have - // a [bot] suffix when using github apps authentication. - BotUser() (*UserData, error) - // BotUserChecker can be used to check if a comment was authored by the bot user. - BotUserChecker() (func(candidate string) bool, error) - BotUserCheckerWithContext(ctx context.Context) (func(candidate string) bool, error) - Email() (string, error) -} - -// ProjectClient interface for project related API actions -type ProjectClient interface { - GetRepoProjects(owner, repo string) ([]Project, error) - GetOrgProjects(org string) ([]Project, error) - GetProjectColumns(org string, projectID int) ([]ProjectColumn, error) - CreateProjectCard(org string, columnID int, projectCard ProjectCard) (*ProjectCard, error) - GetColumnProjectCards(org string, columnID int) ([]ProjectCard, error) - GetColumnProjectCard(org string, columnID int, issueURL string) (*ProjectCard, error) - MoveProjectCard(org string, projectCardID int, newColumnID int) error - DeleteProjectCard(org string, projectCardID int) error -} - -// MilestoneClient interface for milestone related API actions -type MilestoneClient interface { - ClearMilestone(org, repo string, num int) error - SetMilestone(org, repo string, issueNum, milestoneNum int) error - ListMilestones(org, repo string) ([]Milestone, error) -} - -// RerunClient interface for job rerun access check related API actions -type RerunClient interface { - TeamBySlugHasMember(org string, teamSlug string, memberLogin string) (bool, error) - TeamHasMember(org string, teamID int, memberLogin string) (bool, error) - IsCollaborator(org, repo, user string) (bool, error) - IsMember(org, user string) (bool, error) - GetIssueLabels(org, repo string, number int) ([]Label, error) -} - -// Client interface for GitHub API -type Client interface { - PullRequestClient - RepositoryClient - CommitClient - IssueClient - CommentClient - OrganizationClient - TeamClient - ProjectClient - MilestoneClient - UserClient - HookClient - ListAppInstallations() ([]AppInstallation, error) - IsAppInstalled(org, repo string) (bool, error) - UsesAppAuth() bool - ListAppInstallationsForOrg(org string) ([]AppInstallation, error) - GetApp() (*App, error) - GetAppWithContext(ctx context.Context) (*App, error) - GetFailedActionRunsByHeadBranch(org, repo, branchName, headSHA string) ([]WorkflowRun, error) - - Throttle(hourlyTokens, burst int, org ...string) error - QueryWithGitHubAppsSupport(ctx context.Context, q interface{}, vars map[string]interface{}, org string) error - MutateWithGitHubAppsSupport(ctx context.Context, m interface{}, input githubql.Input, vars map[string]interface{}, org string) error - - SetMax404Retries(int) - - WithFields(fields logrus.Fields) Client - ForPlugin(plugin string) Client - ForSubcomponent(subcomponent string) Client - Used() bool - TriggerGitHubWorkflow(org, repo string, id int) error -} - -// client interacts with the github api. It is reconstructed whenever -// ForPlugin/ForSubcomment is called to change the Logger and User-Agent -// header, whereas delegate will stay the same. -type client struct { - // If logger is non-nil, log all method calls with it. - logger *logrus.Entry - // identifier is used to add more identification to the user-agent header - identifier string - gqlc gqlClient - used bool - *delegate -} - -// delegate actually does the work to talk to GitHub -type delegate struct { - time timeClient - - maxRetries int - max404Retries int - maxSleepTime time.Duration - initialDelay time.Duration - - client httpClient - bases []string - dry bool - fake bool - usesAppsAuth bool - throttle throttler - getToken func() []byte - censor func([]byte) []byte - - mut sync.Mutex // protects botName and email - userData *UserData -} - -type UserData struct { - Name string - Login string - Email string -} - -// Used determines whether the client has been used -func (c *client) Used() bool { - return c.used -} - -// ForPlugin clones the client, keeping the underlying delegate the same but adding -// a plugin identifier and log field -func (c *client) ForPlugin(plugin string) Client { - return c.forKeyValue("plugin", plugin) -} - -// ForSubcomponent clones the client, keeping the underlying delegate the same but adding -// an identifier and log field -func (c *client) ForSubcomponent(subcomponent string) Client { - return c.forKeyValue("subcomponent", subcomponent) -} - -func (c *client) forKeyValue(key, value string) Client { - newClient := &client{ - identifier: value, - logger: c.logger.WithField(key, value), - delegate: c.delegate, - } - newClient.gqlc = c.gqlc.forUserAgent(newClient.userAgent()) - return newClient -} - -func (c *client) userAgent() string { - if c.identifier != "" { - return version.UserAgentWithIdentifier(c.identifier) - } - return version.UserAgent() -} - -// WithFields clones the client, keeping the underlying delegate the same but adding -// fields to the logging context -func (c *client) WithFields(fields logrus.Fields) Client { - return &client{ - logger: c.logger.WithFields(fields), - identifier: c.identifier, - gqlc: c.gqlc, - delegate: c.delegate, - } -} - -var ( - teamRe = regexp.MustCompile(`^(.*)/(.*)$`) -) - -const ( - acceptNone = "" - githubApiVersion = "2022-11-28" - - // MaxRequestTime aborts requests that don't return in 5 mins. Longest graphql - // calls can take up to 2 minutes. This limit should ensure all successful calls - // return but will prevent an indefinite stall if GitHub never responds. - MaxRequestTime = 5 * time.Minute - - DefaultMaxRetries = 8 - DefaultMax404Retries = 2 - DefaultMaxSleepTime = 2 * time.Minute - DefaultInitialDelay = 2 * time.Second -) - -// Force the compiler to check if the TokenSource is implementing correctly. -// Tokensource is needed to dynamically update the token in the GraphQL client. -var _ oauth2.TokenSource = &reloadingTokenSource{} - -type reloadingTokenSource struct { - getToken func() []byte -} - -// Interface for how prow interacts with the http client, which we may throttle. -type httpClient interface { - Do(req *http.Request) (*http.Response, error) -} - -// Interface for how prow interacts with the graphql client, which we may throttle. -type gqlClient interface { - QueryWithGitHubAppsSupport(ctx context.Context, q interface{}, vars map[string]interface{}, org string) error - MutateWithGitHubAppsSupport(ctx context.Context, m interface{}, input githubql.Input, vars map[string]interface{}, org string) error - forUserAgent(userAgent string) gqlClient -} - -// throttler sets a ceiling on the rate of GitHub requests. -// Configure with Client.Throttle(). -// It gets reconstructed whenever forUserAgent() is called, -// whereas its *throttlerDelegate remains. -type throttler struct { - graph gqlClient - *throttlerDelegate -} - -type throttlerDelegate struct { - ticker map[string]*time.Ticker - throttle map[string]chan time.Time - http httpClient - slow map[string]*int32 // Helps log once when requests start/stop being throttled - lock sync.RWMutex -} - -func (t *throttler) Wait(ctx context.Context, org string) error { - start := time.Now() - log := logrus.WithFields(logrus.Fields{"client": "github", "throttled": true}) - defer func() { - waitTime := time.Since(start) - switch { - case waitTime > 15*time.Minute: - log.WithField("throttle-duration", waitTime.String()).Warn("Throttled clientside for more than 15 minutes") - case waitTime > time.Minute: - log.WithField("throttle-duration", waitTime.String()).Debug("Throttled clientside for more than a minute") - } - }() - t.lock.RLock() - defer t.lock.RUnlock() - if _, found := t.ticker[org]; !found { - org = throttlerGlobalKey - } - if _, hasThrottler := t.ticker[org]; !hasThrottler { - return nil - } - - var more bool - select { - case _, more = <-t.throttle[org]: - // If we were throttled and the channel is now somewhat (25%+) full, note this - if len(t.throttle[org]) > cap(t.throttle[org])/4 && atomic.CompareAndSwapInt32(t.slow[org], 1, 0) { - log.Debug("Unthrottled") - } - if !more { - log.Debug("Throttle channel closed") - } - return nil - default: // Do not wait if nothing is available right now - } - // If this is the first time we are waiting, note this - if slow := atomic.SwapInt32(t.slow[org], 1); slow == 0 { - log.Debug("Throttled") - } - - select { - case _, more = <-t.throttle[org]: - if !more { - log.Debug("Throttle channel closed") - } - case <-ctx.Done(): - return ctx.Err() - } - - return nil -} - -const throttlerGlobalKey = "*" - -func (t *throttler) Refund(org string) { - t.lock.RLock() - defer t.lock.RUnlock() - if _, found := t.ticker[org]; !found { - org = throttlerGlobalKey - } - if _, hasThrottler := t.ticker[org]; !hasThrottler { - return - } - select { - case t.throttle[org] <- time.Now(): - default: - } -} - -func (t *throttler) Do(req *http.Request) (*http.Response, error) { - org := extractOrgFromContext(req.Context()) - if err := t.Wait(req.Context(), org); err != nil { - return nil, err - } - resp, err := t.http.Do(req) - if err == nil { - cacheMode := ghcache.CacheResponseMode(resp.Header.Get(ghcache.CacheModeHeader)) - if ghcache.CacheModeIsFree(cacheMode) { - // This request was fulfilled by ghcache without using an API token. - // Refund the throttling token we preemptively consumed. - logrus.WithFields(logrus.Fields{ - "client": "github", - "throttled": true, - "cache-mode": string(cacheMode), - }).Debug("Throttler refunding token for free response from ghcache.") - t.Refund(org) - } else { - logrus.WithFields(logrus.Fields{ - "client": "github", - "throttled": true, - "cache-mode": string(cacheMode), - "path": req.URL.Path, - "method": req.Method, - }).Debug("Used token for request") - - } - } - return resp, err -} - -func (t *throttler) QueryWithGitHubAppsSupport(ctx context.Context, q interface{}, vars map[string]interface{}, org string) error { - if err := t.Wait(ctx, extractOrgFromContext(ctx)); err != nil { - return err - } - return t.graph.QueryWithGitHubAppsSupport(ctx, q, vars, org) -} - -func (t *throttler) MutateWithGitHubAppsSupport(ctx context.Context, m interface{}, input githubql.Input, vars map[string]interface{}, org string) error { - if err := t.Wait(ctx, extractOrgFromContext(ctx)); err != nil { - return err - } - return t.graph.MutateWithGitHubAppsSupport(ctx, m, input, vars, org) -} - -func (t *throttler) forUserAgent(userAgent string) gqlClient { - return &throttler{ - graph: t.graph.forUserAgent(userAgent), - throttlerDelegate: t.throttlerDelegate, - } -} - -// Throttle client to a rate of at most hourlyTokens requests per hour, -// allowing burst tokens. -func (c *client) Throttle(hourlyTokens, burst int, orgs ...string) error { - org := "*" - if len(orgs) > 0 { - if !c.usesAppsAuth { - return errors.New("passing an org to the throttler is only allowed when using github apps auth") - } - if len(orgs) > 1 { - return fmt.Errorf("may only pass one org for throttling, got %d", len(orgs)) - } - org = orgs[0] - } - c.log("Throttle", hourlyTokens, burst, org) - c.throttle.lock.Lock() - defer c.throttle.lock.Unlock() - if hourlyTokens <= 0 || burst <= 0 { // Disable throttle - if c.throttle.throttle[org] != nil { - delete(c.throttle.throttle, org) - delete(c.throttle.slow, org) - c.throttle.ticker[org].Stop() - delete(c.throttle.ticker, org) - } - return nil - } - period := time.Hour / time.Duration(hourlyTokens) // Duration between token refills - ticker := time.NewTicker(period) - throttle := make(chan time.Time, burst) - for i := 0; i < burst; i++ { // Fill up the channel - throttle <- time.Now() - } - go func() { - // Before refilling, wait the amount of time it would have taken to refill the burst channel. - // This prevents granting too many tokens in the first hour due to the initial burst. - for i := 0; i < burst; i++ { - <-ticker.C - } - // Refill the channel - for t := range ticker.C { - select { - case throttle <- t: - default: - } - } - }() - if c.throttle.http == nil { // Wrap clients if we haven't already - c.throttle.http = c.client - c.throttle.graph = c.gqlc - c.client = &c.throttle - c.gqlc = &c.throttle - } - - if c.throttle.ticker == nil { - c.throttle.ticker = map[string]*time.Ticker{} - } - c.throttle.ticker[org] = ticker - - if c.throttle.throttle == nil { - c.throttle.throttle = map[string]chan time.Time{} - } - c.throttle.throttle[org] = throttle - - if c.throttle.slow == nil { - c.throttle.slow = map[string]*int32{} - } - var i int32 - c.throttle.slow[org] = &i - - return nil -} - -func (c *client) SetMax404Retries(max int) { - c.max404Retries = max -} - -// ClientOptions holds options for creating a new client -type ClientOptions struct { - // censor knows how to censor output - Censor func([]byte) []byte - - // the following fields handle auth - GetToken func() []byte - AppID string - AppPrivateKey func() *rsa.PrivateKey - - // the following fields determine which server we talk to - GraphqlEndpoint string - Bases []string - - // the following fields determine client retry behavior - MaxRequestTime, InitialDelay, MaxSleepTime time.Duration - MaxRetries, Max404Retries int - - DryRun bool - // BaseRoundTripper is the last RoundTripper to be called. Used for testing, gets defaulted to http.DefaultTransport - BaseRoundTripper http.RoundTripper -} - -func (o ClientOptions) Default() ClientOptions { - if o.MaxRequestTime == 0 { - o.MaxRequestTime = MaxRequestTime - } - if o.InitialDelay == 0 { - o.InitialDelay = DefaultInitialDelay - } - if o.MaxSleepTime == 0 { - o.MaxSleepTime = DefaultMaxSleepTime - } - if o.MaxRetries == 0 { - o.MaxRetries = DefaultMaxRetries - } - if o.Max404Retries == 0 { - o.Max404Retries = DefaultMax404Retries - } - return o -} - -// TokenGenerator knows how to generate a token for use in git client calls -type TokenGenerator func(org string) (string, error) - -// UserGenerator knows how to identify this user for use in git client calls -type UserGenerator func() (string, error) - -// NewClientWithFields creates a new fully operational GitHub client. With -// added logging fields. -// 'getToken' is a generator for the GitHub access token to use. -// 'bases' is a variadic slice of endpoints to use in order of preference. -// -// An endpoint is used when all preceding endpoints have returned a conn err. -// This should be used when using the ghproxy GitHub proxy cache to allow -// this client to bypass the cache if it is temporarily unavailable. -func NewClientWithFields(fields logrus.Fields, getToken func() []byte, censor func([]byte) []byte, graphqlEndpoint string, bases ...string) (Client, error) { - _, _, client, err := NewClientFromOptions(fields, ClientOptions{ - Censor: censor, - GetToken: getToken, - GraphqlEndpoint: graphqlEndpoint, - Bases: bases, - DryRun: false, - }.Default()) - return client, err -} - -func NewAppsAuthClientWithFields(fields logrus.Fields, censor func([]byte) []byte, appID string, appPrivateKey func() *rsa.PrivateKey, graphqlEndpoint string, bases ...string) (TokenGenerator, UserGenerator, Client, error) { - return NewClientFromOptions(fields, ClientOptions{ - Censor: censor, - AppID: appID, - AppPrivateKey: appPrivateKey, - GraphqlEndpoint: graphqlEndpoint, - Bases: bases, - DryRun: false, - }.Default()) -} - -// NewClientFromOptions creates a new client from the options we expose. This method should be used over the more-specific ones. -func NewClientFromOptions(fields logrus.Fields, options ClientOptions) (TokenGenerator, UserGenerator, Client, error) { - options = options.Default() - - // Will be nil if github app authentication is used - if options.GetToken == nil { - options.GetToken = func() []byte { return nil } - } - if options.BaseRoundTripper == nil { - options.BaseRoundTripper = http.DefaultTransport - } - - httpClient := &http.Client{ - Transport: options.BaseRoundTripper, - Timeout: options.MaxRequestTime, - } - graphQLTransport := newAddHeaderTransport(options.BaseRoundTripper) - c := &client{ - logger: logrus.WithFields(fields).WithField("client", "github"), - gqlc: &graphQLGitHubAppsAuthClientWrapper{Client: githubql.NewEnterpriseClient( - options.GraphqlEndpoint, - &http.Client{ - Timeout: options.MaxRequestTime, - Transport: &oauth2.Transport{ - Source: newReloadingTokenSource(options.GetToken), - Base: graphQLTransport, - }, - })}, - delegate: &delegate{ - time: &standardTime{}, - client: httpClient, - bases: options.Bases, - throttle: throttler{throttlerDelegate: &throttlerDelegate{}}, - getToken: options.GetToken, - censor: options.Censor, - dry: options.DryRun, - usesAppsAuth: options.AppID != "", - maxRetries: options.MaxRetries, - max404Retries: options.Max404Retries, - initialDelay: options.InitialDelay, - maxSleepTime: options.MaxSleepTime, - }, - } - c.gqlc = c.gqlc.forUserAgent(c.userAgent()) - - var tokenGenerator func(_ string) (string, error) - var userGenerator func() (string, error) - if options.AppID != "" { - appsTransport, err := newAppsRoundTripper(options.AppID, options.AppPrivateKey, options.BaseRoundTripper, c, options.Bases) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to construct apps auth roundtripper: %w", err) - } - httpClient.Transport = appsTransport - graphQLTransport.upstream = appsTransport - - // Use github apps auth for git actions - // https://docs.github.com/en/free-pro-team@latest/developers/apps/authenticating-with-github-apps#http-based-git-access-by-an-installation= - tokenGenerator = func(org string) (string, error) { - res, _, err := appsTransport.installationTokenFor(org) - return res, err - } - userGenerator = func() (string, error) { - return "x-access-token", nil - } - } else { - // Use Personal Access token auth for git actions - tokenGenerator = func(_ string) (string, error) { - return string(options.GetToken()), nil - } - userGenerator = func() (string, error) { - user, err := c.BotUser() - if err != nil { - return "", err - } - return user.Login, nil - } - } - - return tokenGenerator, userGenerator, c, nil -} - -type graphQLGitHubAppsAuthClientWrapper struct { - *githubql.Client - userAgent string -} - -var userAgentContextKey = &struct{}{} - -func (c *graphQLGitHubAppsAuthClientWrapper) QueryWithGitHubAppsSupport(ctx context.Context, q interface{}, vars map[string]interface{}, org string) error { - ctx = context.WithValue(ctx, githubOrgHeaderKey, org) - ctx = context.WithValue(ctx, userAgentContextKey, c.userAgent) - return c.Client.Query(ctx, q, vars) -} - -func (c *graphQLGitHubAppsAuthClientWrapper) MutateWithGitHubAppsSupport(ctx context.Context, m interface{}, input githubql.Input, vars map[string]interface{}, org string) error { - ctx = context.WithValue(ctx, githubOrgHeaderKey, org) - ctx = context.WithValue(ctx, userAgentContextKey, c.userAgent) - return c.Client.Mutate(ctx, m, input, vars) -} - -func (c *graphQLGitHubAppsAuthClientWrapper) forUserAgent(userAgent string) gqlClient { - return &graphQLGitHubAppsAuthClientWrapper{ - Client: c.Client, - userAgent: userAgent, - } -} - -// addHeaderTransport implements http.RoundTripper -var _ http.RoundTripper = &addHeaderTransport{} - -func newAddHeaderTransport(upstream http.RoundTripper) *addHeaderTransport { - return &addHeaderTransport{upstream} -} - -type addHeaderTransport struct { - upstream http.RoundTripper -} - -func (s *addHeaderTransport) RoundTrip(r *http.Request) (*http.Response, error) { - // We have to add this header to enable the Checks scheme preview: - // https://docs.github.com/en/enterprise-server@2.22/graphql/overview/schema-previews - // Any GHE version after 2.22 will enable the Checks types per default - r.Header.Add("Accept", "application/vnd.github.antiope-preview+json") - - // We have to add this header to enable the Merge info scheme preview: - // https://docs.github.com/en/graphql/overview/schema-previews#merge-info-preview - r.Header.Add("Accept", "application/vnd.github.merge-info-preview+json") - - // We use the context to pass the UserAgent through the V4 client we depend on - if v := r.Context().Value(userAgentContextKey); v != nil { - r.Header.Add("User-Agent", v.(string)) - } - - return s.upstream.RoundTrip(r) -} - -// NewClient creates a new fully operational GitHub client. -func NewClient(getToken func() []byte, censor func([]byte) []byte, graphqlEndpoint string, bases ...string) (Client, error) { - return NewClientWithFields(logrus.Fields{}, getToken, censor, graphqlEndpoint, bases...) -} - -// NewDryRunClientWithFields creates a new client that will not perform mutating actions -// such as setting statuses or commenting, but it will still query GitHub and -// use up API tokens. Additional fields are added to the logger. -// 'getToken' is a generator the GitHub access token to use. -// 'bases' is a variadic slice of endpoints to use in order of preference. -// -// An endpoint is used when all preceding endpoints have returned a conn err. -// This should be used when using the ghproxy GitHub proxy cache to allow -// this client to bypass the cache if it is temporarily unavailable. -func NewDryRunClientWithFields(fields logrus.Fields, getToken func() []byte, censor func([]byte) []byte, graphqlEndpoint string, bases ...string) (Client, error) { - _, _, client, err := NewClientFromOptions(fields, ClientOptions{ - Censor: censor, - GetToken: getToken, - GraphqlEndpoint: graphqlEndpoint, - Bases: bases, - DryRun: true, - }.Default()) - return client, err -} - -// NewAppsAuthDryRunClientWithFields creates a new client that will not perform mutating actions -// such as setting statuses or commenting, but it will still query GitHub and -// use up API tokens. Additional fields are added to the logger. -func NewAppsAuthDryRunClientWithFields(fields logrus.Fields, censor func([]byte) []byte, appId string, appPrivateKey func() *rsa.PrivateKey, graphqlEndpoint string, bases ...string) (TokenGenerator, UserGenerator, Client, error) { - return NewClientFromOptions(fields, ClientOptions{ - Censor: censor, - AppID: appId, - AppPrivateKey: appPrivateKey, - GraphqlEndpoint: graphqlEndpoint, - Bases: bases, - DryRun: false, - }.Default()) -} - -// NewDryRunClient creates a new client that will not perform mutating actions -// such as setting statuses or commenting, but it will still query GitHub and -// use up API tokens. -// 'getToken' is a generator the GitHub access token to use. -// 'bases' is a variadic slice of endpoints to use in order of preference. -// -// An endpoint is used when all preceding endpoints have returned a conn err. -// This should be used when using the ghproxy GitHub proxy cache to allow -// this client to bypass the cache if it is temporarily unavailable. -func NewDryRunClient(getToken func() []byte, censor func([]byte) []byte, graphqlEndpoint string, bases ...string) (Client, error) { - return NewDryRunClientWithFields(logrus.Fields{}, getToken, censor, graphqlEndpoint, bases...) -} - -// NewFakeClient creates a new client that will not perform any actions at all. -func NewFakeClient() Client { - return &client{ - logger: logrus.WithField("client", "github"), - gqlc: &graphQLGitHubAppsAuthClientWrapper{}, - delegate: &delegate{ - time: &standardTime{}, - fake: true, - dry: true, - }, - } -} - -func (c *client) log(methodName string, args ...interface{}) (logDuration func()) { - c.used = true - if c.logger == nil { - return func() {} - } - var as []string - for _, arg := range args { - as = append(as, fmt.Sprintf("%v", arg)) - } - start := time.Now() - c.logger.Infof("%s(%s)", methodName, strings.Join(as, ", ")) - return func() { - c.logger.WithField("duration", time.Since(start).String()).Debugf("%s(%s) finished", methodName, strings.Join(as, ", ")) - } -} - -type request struct { - method string - path string - accept string - org string - requestBody interface{} - exitCodes []int -} - -type requestError struct { - StatusCode int - ClientError error - ErrorString string -} - -func (r requestError) Error() string { - return r.ErrorString -} - -func (r requestError) ErrorMessages() []string { - clientErr, isClientError := r.ClientError.(ClientError) - if isClientError { - errors := []string{} - for _, subErr := range clientErr.Errors { - errors = append(errors, subErr.Message) - } - return errors - } - alternativeClientErr, isAlternativeClientError := r.ClientError.(AlternativeClientError) - if isAlternativeClientError { - return alternativeClientErr.Errors - } - return []string{} -} - -// NewNotFound returns a NotFound error which may be useful for tests -func NewNotFound() error { - return requestError{ - ClientError: ClientError{ - Errors: []clientErrorSubError{{Message: "status code 404"}}, - }, - } -} - -func IsNotFound(err error) bool { - if err == nil { - return false - } - - var requestErr requestError - if !errors.As(err, &requestErr) { - return false - } - - if requestErr.StatusCode == http.StatusNotFound { - return true - } - - for _, errorMsg := range requestErr.ErrorMessages() { - if strings.Contains(errorMsg, "status code 404") { - return true - } - } - return false -} - -// Make a request with retries. If ret is not nil, unmarshal the response body -// into it. Returns an error if the exit code is not one of the provided codes. -func (c *client) request(r *request, ret interface{}) (int, error) { - return c.requestWithContext(context.Background(), r, ret) -} - -func (c *client) requestWithContext(ctx context.Context, r *request, ret interface{}) (int, error) { - statusCode, b, err := c.requestRawWithContext(ctx, r) - if err != nil { - return statusCode, err - } - if ret != nil { - if err := json.Unmarshal(b, ret); err != nil { - return statusCode, err - } - } - return statusCode, nil -} - -// requestRaw makes a request with retries and returns the response body. -// Returns an error if the exit code is not one of the provided codes. -func (c *client) requestRaw(r *request) (int, []byte, error) { - return c.requestRawWithContext(context.Background(), r) -} - -func (c *client) requestRawWithContext(ctx context.Context, r *request) (int, []byte, error) { - if c.fake || (c.dry && r.method != http.MethodGet) { - return r.exitCodes[0], nil, nil - } - resp, err := c.requestRetryWithContext(ctx, r.method, r.path, r.accept, r.org, r.requestBody) - if err != nil { - return 0, nil, err - } - defer resp.Body.Close() - b, err := io.ReadAll(resp.Body) - if err != nil { - return 0, nil, err - } - var okCode bool - for _, code := range r.exitCodes { - if code == resp.StatusCode { - okCode = true - break - } - } - if !okCode { - clientError := unmarshalClientError(b) - err = requestError{ - StatusCode: resp.StatusCode, - ClientError: clientError, - ErrorString: fmt.Sprintf("status code %d not one of %v, body: %s", resp.StatusCode, r.exitCodes, string(b)), - } - } - return resp.StatusCode, b, err -} - -// Retry on transport failures. Retries on 500s, retries after sleep on -// ratelimit exceeded, and retries 404s a couple times. -// This function closes the response body iff it also returns an error. -func (c *client) requestRetry(method, path, accept, org string, body interface{}) (*http.Response, error) { - return c.requestRetryWithContext(context.Background(), method, path, accept, org, body) -} - -func (c *client) requestRetryWithContext(ctx context.Context, method, path, accept, org string, body interface{}) (*http.Response, error) { - var hostIndex int - var resp *http.Response - var err error - backoff := c.initialDelay - for retries := 0; retries < c.maxRetries; retries++ { - if retries > 0 && resp != nil { - resp.Body.Close() - } - resp, err = c.doRequest(ctx, method, c.bases[hostIndex]+path, accept, org, body) - if err == nil { - if resp.StatusCode == 404 && retries < c.max404Retries { - // Retry 404s a couple times. Sometimes GitHub is inconsistent in - // the sense that they send us an event such as "PR opened" but an - // immediate request to GET the PR returns 404. We don't want to - // retry more than a couple times in this case, because a 404 may - // be caused by a bad API call and we'll just burn through API - // tokens. - c.logger.WithField("backoff", backoff.String()).Debug("Retrying 404") - c.time.Sleep(backoff) - backoff *= 2 - } else if resp.StatusCode == 403 { - if resp.Header.Get("X-RateLimit-Remaining") == "0" { - // If we are out of API tokens, sleep first. The X-RateLimit-Reset - // header tells us the time at which we can request again. - var t int - if t, err = strconv.Atoi(resp.Header.Get("X-RateLimit-Reset")); err == nil { - // Sleep an extra second plus how long GitHub wants us to - // sleep. If it's going to take too long, then break. - sleepTime := c.time.Until(time.Unix(int64(t), 0)) + time.Second - if sleepTime < c.maxSleepTime { - c.logger.WithField("backoff", sleepTime.String()).WithField("path", path).Debug("Retrying after token budget reset") - c.time.Sleep(sleepTime) - } else { - err = fmt.Errorf("sleep time for token reset exceeds max sleep time (%v > %v)", sleepTime, c.maxSleepTime) - resp.Body.Close() - break - } - } else { - err = fmt.Errorf("failed to parse rate limit reset unix time %q: %w", resp.Header.Get("X-RateLimit-Reset"), err) - resp.Body.Close() - break - } - } else if rawTime := resp.Header.Get("Retry-After"); rawTime != "" && rawTime != "0" { - // If we are getting abuse rate limited, we need to wait or - // else we risk continuing to make the situation worse - var t int - if t, err = strconv.Atoi(rawTime); err == nil { - // Sleep an extra second plus how long GitHub wants us to - // sleep. If it's going to take too long, then break. - sleepTime := time.Duration(t+1) * time.Second - if sleepTime < c.maxSleepTime { - c.logger.WithField("backoff", sleepTime.String()).WithField("path", path).Debug("Retrying after abuse ratelimit reset") - c.time.Sleep(sleepTime) - } else { - err = fmt.Errorf("sleep time for abuse rate limit exceeds max sleep time (%v > %v)", sleepTime, c.maxSleepTime) - resp.Body.Close() - break - } - } else { - err = fmt.Errorf("failed to parse abuse rate limit wait time %q: %w", rawTime, err) - resp.Body.Close() - break - } - } else { - acceptedScopes := resp.Header.Get("X-Accepted-OAuth-Scopes") - authorizedScopes := resp.Header.Get("X-OAuth-Scopes") - if authorizedScopes == "" { - authorizedScopes = "no" - } - - want := sets.New[string]() - for _, acceptedScope := range strings.Split(acceptedScopes, ",") { - want.Insert(strings.TrimSpace(acceptedScope)) - } - var got []string - for _, authorizedScope := range strings.Split(authorizedScopes, ",") { - got = append(got, strings.TrimSpace(authorizedScope)) - } - if acceptedScopes != "" && !want.HasAny(got...) { - err = fmt.Errorf("the account is using %s oauth scopes, please make sure you are using at least one of the following oauth scopes: %s", authorizedScopes, acceptedScopes) - } else { - body, _ := io.ReadAll(resp.Body) - err = fmt.Errorf("the GitHub API request returns a 403 error: %s", string(body)) - } - resp.Body.Close() - break - } - } else if resp.StatusCode < 500 { - // Normal, happy case. - break - } else { - // Retry 500 after a break. - c.logger.WithField("backoff", backoff.String()).Debug("Retrying 5XX") - c.time.Sleep(backoff) - backoff *= 2 - } - } else if errors.Is(err, &appsAuthError{}) { - c.logger.WithError(err).Error("Stopping retry due to appsAuthError") - return resp, err - } else if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - return resp, err - } else { - // Connection problem. Try a different host. - oldHostIndex := hostIndex - hostIndex = (hostIndex + 1) % len(c.bases) - c.logger.WithFields(logrus.Fields{ - "err": err, - "backoff": backoff.String(), - "old-endpoint": c.bases[oldHostIndex], - "new-endpoint": c.bases[hostIndex], - }).Debug("Retrying request due to connection problem") - c.time.Sleep(backoff) - backoff *= 2 - } - } - return resp, err -} - -func (c *client) doRequest(ctx context.Context, method, path, accept, org string, body interface{}) (*http.Response, error) { - var buf io.Reader - if body != nil { - b, err := json.Marshal(body) - if err != nil { - return nil, err - } - b = c.censor(b) - buf = bytes.NewBuffer(b) - } - req, err := http.NewRequestWithContext(ctx, method, path, buf) - if err != nil { - return nil, fmt.Errorf("failed creating new request: %w", err) - } - // We do not make use of the Set() method to set this header because - // the header name `X-GitHub-Api-Version` is non-canonical in nature. - // - // See https://pkg.go.dev/net/http#Header.Set for more info. - req.Header["X-GitHub-Api-Version"] = []string{githubApiVersion} - c.logger.Infof("Using GitHub REST API Version: %s", githubApiVersion) - if header := c.authHeader(); len(header) > 0 { - req.Header.Set("Authorization", header) - } - if accept == acceptNone { - req.Header.Add("Accept", "application/vnd.github.v3+json") - } else { - req.Header.Add("Accept", accept) - } - if userAgent := c.userAgent(); userAgent != "" { - req.Header.Add("User-Agent", userAgent) - } - if org != "" { - req = req.WithContext(context.WithValue(req.Context(), githubOrgHeaderKey, org)) - } - // Disable keep-alive so that we don't get flakes when GitHub closes the - // connection prematurely. - // https://go-review.googlesource.com/#/c/3210/ fixed it for GET, but not - // for POST. - req.Close = true - - c.logger.WithField("curl", toCurl(req)).Trace("Executing http request") - return c.client.Do(req) -} - -// toCurl is a slightly adjusted copy of https://github.com/kubernetes/kubernetes/blob/74053d555d71a14e3853b97e204d7d6415521375/staging/src/k8s.io/client-go/transport/round_trippers.go#L339 -func toCurl(r *http.Request) string { - headers := "" - for key, values := range r.Header { - for _, value := range values { - headers += fmt.Sprintf(` -H %q`, fmt.Sprintf("%s: %s", key, maskAuthorizationHeader(key, value))) - } - } - - return fmt.Sprintf("curl -k -v -X%s %s '%s'", r.Method, headers, r.URL.String()) -} - -var knownAuthTypes = sets.New[string]("bearer", "basic", "negotiate") - -// maskAuthorizationHeader masks credential content from authorization headers -// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization -func maskAuthorizationHeader(key string, value string) string { - if !strings.EqualFold(key, "Authorization") { - return value - } - if len(value) == 0 { - return "" - } - var authType string - if i := strings.Index(value, " "); i > 0 { - authType = value[0:i] - } else { - authType = value - } - if !knownAuthTypes.Has(strings.ToLower(authType)) { - return "" - } - if len(value) > len(authType)+1 { - value = authType + " " - } else { - value = authType - } - return value -} - -func (c *client) authHeader() string { - if c.getToken == nil { - return "" - } - token := c.getToken() - if len(token) == 0 { - return "" - } - return fmt.Sprintf("Bearer %s", token) -} - -// userInfo provides the 'github_user_info' vector that is indexed -// by the user's information. -var userInfo = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Name: "github_user_info", - Help: "Metadata about a user, tied to their token hash.", - }, - []string{"token_hash", "login", "email"}, -) - -func init() { - prometheus.MustRegister(userInfo) -} - -// Not thread-safe - callers need to hold c.mut. -func (c *client) getUserData(ctx context.Context) error { - if c.delegate.usesAppsAuth { - resp, err := c.GetAppWithContext(ctx) - if err != nil { - return err - } - c.userData = &UserData{ - Name: resp.Name, - Login: resp.Slug, - Email: fmt.Sprintf("%s@users.noreply.github.com", resp.Slug), - } - return nil - } - c.log("User") - var u User - _, err := c.requestWithContext(ctx, &request{ - method: http.MethodGet, - path: "/user", - exitCodes: []int{200}, - }, &u) - if err != nil { - return err - } - c.userData = &UserData{ - Name: u.Name, - Login: u.Login, - Email: u.Email, - } - // email needs to be publicly accessible via the profile - // of the current account. Read below for more info - // https://developer.github.com/v3/users/#get-a-single-user - - // record information for the user - authHeaderHash := fmt.Sprintf("%x", sha256.Sum256([]byte(c.authHeader()))) // use %x to make this a utf-8 string for use as a label - userInfo.With(prometheus.Labels{"token_hash": authHeaderHash, "login": c.userData.Login, "email": c.userData.Email}).Set(1) - return nil -} - -// BotUser returns the user data of the authenticated identity. -// -// See https://developer.github.com/v3/users/#get-the-authenticated-user -func (c *client) BotUser() (*UserData, error) { - c.mut.Lock() - defer c.mut.Unlock() - if c.userData == nil { - if err := c.getUserData(context.Background()); err != nil { - return nil, fmt.Errorf("fetching bot name from GitHub: %w", err) - } - } - return c.userData, nil -} - -func (c *client) BotUserChecker() (func(candidate string) bool, error) { - return c.BotUserCheckerWithContext(context.Background()) -} - -func (c *client) BotUserCheckerWithContext(ctx context.Context) (func(candidate string) bool, error) { - c.mut.Lock() - defer c.mut.Unlock() - if c.userData == nil { - if err := c.getUserData(ctx); err != nil { - return nil, fmt.Errorf("fetching userdata from GitHub: %w", err) - } - } - - botUser := c.userData.Login - return func(candidate string) bool { - if c.usesAppsAuth { - candidate = strings.TrimSuffix(candidate, "[bot]") - } - return candidate == botUser - }, nil -} - -// Email returns the user-configured email for the authenticated identity. -// -// See https://developer.github.com/v3/users/#get-the-authenticated-user -func (c *client) Email() (string, error) { - c.mut.Lock() - defer c.mut.Unlock() - if c.userData == nil { - if err := c.getUserData(context.Background()); err != nil { - return "", fmt.Errorf("fetching e-mail from GitHub: %w", err) - } - } - return c.userData.Email, nil -} - -// IsMember returns whether or not the user is a member of the org. -// -// See https://developer.github.com/v3/orgs/members/#check-membership -func (c *client) IsMember(org, user string) (bool, error) { - c.log("IsMember", org, user) - if org == user { - // Make it possible to run a couple of plugins on personal repos. - return true, nil - } - code, err := c.request(&request{ - method: http.MethodGet, - path: fmt.Sprintf("/orgs/%s/members/%s", org, user), - org: org, - exitCodes: []int{204, 404, 302}, - }, nil) - if err != nil { - return false, err - } - if code == 204 { - return true, nil - } else if code == 404 { - return false, nil - } else if code == 302 { - return false, fmt.Errorf("requester is not %s org member", org) - } - // Should be unreachable. - return false, fmt.Errorf("unexpected status: %d", code) -} - -func (c *client) listHooks(org string, repo *string) ([]Hook, error) { - var ret []Hook - var path string - if repo != nil { - path = fmt.Sprintf("/repos/%s/%s/hooks", org, *repo) - } else { - path = fmt.Sprintf("/orgs/%s/hooks", org) - } - err := c.readPaginatedResults( - path, - acceptNone, - org, - func() interface{} { - return &[]Hook{} - }, - func(obj interface{}) { - ret = append(ret, *(obj.(*[]Hook))...) - }, - ) - if err != nil { - return nil, err - } - return ret, nil -} - -// ListOrgHooks returns a list of hooks for the org. -// https://developer.github.com/v3/orgs/hooks/#list-hooks -func (c *client) ListOrgHooks(org string) ([]Hook, error) { - c.log("ListOrgHooks", org) - return c.listHooks(org, nil) -} - -// ListRepoHooks returns a list of hooks for the repo. -// https://developer.github.com/v3/repos/hooks/#list-hooks -func (c *client) ListRepoHooks(org, repo string) ([]Hook, error) { - c.log("ListRepoHooks", org, repo) - return c.listHooks(org, &repo) -} - -func (c *client) editHook(org string, repo *string, id int, req HookRequest) error { - if c.dry { - return nil - } - var path string - if repo != nil { - path = fmt.Sprintf("/repos/%s/%s/hooks/%d", org, *repo, id) - } else { - path = fmt.Sprintf("/orgs/%s/hooks/%d", org, id) - } - - _, err := c.request(&request{ - method: http.MethodPatch, - path: path, - org: org, - exitCodes: []int{200}, - requestBody: &req, - }, nil) - return err -} - -// EditRepoHook updates an existing hook with new info (events/url/secret) -// https://developer.github.com/v3/repos/hooks/#edit-a-hook -func (c *client) EditRepoHook(org, repo string, id int, req HookRequest) error { - c.log("EditRepoHook", org, repo, id) - return c.editHook(org, &repo, id, req) -} - -// EditOrgHook updates an existing hook with new info (events/url/secret) -// https://developer.github.com/v3/orgs/hooks/#edit-a-hook -func (c *client) EditOrgHook(org string, id int, req HookRequest) error { - c.log("EditOrgHook", org, id) - return c.editHook(org, nil, id, req) -} - -func (c *client) createHook(org string, repo *string, req HookRequest) (int, error) { - if c.dry { - return -1, nil - } - var path string - if repo != nil { - path = fmt.Sprintf("/repos/%s/%s/hooks", org, *repo) - } else { - path = fmt.Sprintf("/orgs/%s/hooks", org) - } - var ret Hook - _, err := c.request(&request{ - method: http.MethodPost, - path: path, - org: org, - exitCodes: []int{201}, - requestBody: &req, - }, &ret) - if err != nil { - return 0, err - } - return ret.ID, nil -} - -// CreateOrgHook creates a new hook for the org -// https://developer.github.com/v3/orgs/hooks/#create-a-hook -func (c *client) CreateOrgHook(org string, req HookRequest) (int, error) { - c.log("CreateOrgHook", org) - return c.createHook(org, nil, req) -} - -// CreateRepoHook creates a new hook for the repo -// https://developer.github.com/v3/repos/hooks/#create-a-hook -func (c *client) CreateRepoHook(org, repo string, req HookRequest) (int, error) { - c.log("CreateRepoHook", org, repo) - return c.createHook(org, &repo, req) -} - -func (c *client) deleteHook(org, path string) error { - if c.dry { - return nil - } - - _, err := c.request(&request{ - method: http.MethodDelete, - path: path, - org: org, - exitCodes: []int{204}, - }, nil) - return err -} - -// DeleteRepoHook deletes an existing repo level webhook. -// https://developer.github.com/v3/repos/hooks/#delete-a-hook -func (c *client) DeleteRepoHook(org, repo string, id int, req HookRequest) error { - c.log("DeleteRepoHook", org, repo, id) - path := fmt.Sprintf("/repos/%s/%s/hooks/%d", org, repo, id) - return c.deleteHook(org, path) -} - -// DeleteOrgHook deletes and existing org level webhook. -// https://developer.github.com/v3/orgs/hooks/#edit-a-hook -func (c *client) DeleteOrgHook(org string, id int, req HookRequest) error { - c.log("DeleteOrgHook", org, id) - path := fmt.Sprintf("/orgs/%s/hooks/%d", org, id) - return c.deleteHook(org, path) -} - -// GetOrg returns current metadata for the org -// -// https://developer.github.com/v3/orgs/#get-an-organization -func (c *client) GetOrg(name string) (*Organization, error) { - c.log("GetOrg", name) - var retOrg Organization - _, err := c.request(&request{ - method: http.MethodGet, - path: fmt.Sprintf("/orgs/%s", name), - org: name, - exitCodes: []int{200}, - }, &retOrg) - if err != nil { - return nil, err - } - return &retOrg, nil -} - -// EditOrg will update the metadata for this org. -// -// https://developer.github.com/v3/orgs/#edit-an-organization -func (c *client) EditOrg(name string, config Organization) (*Organization, error) { - c.log("EditOrg", name, config) - if c.dry { - return &config, nil - } - var retOrg Organization - _, err := c.request(&request{ - method: http.MethodPatch, - path: fmt.Sprintf("/orgs/%s", name), - org: name, - exitCodes: []int{200}, - requestBody: &config, - }, &retOrg) - if err != nil { - return nil, err - } - return &retOrg, nil -} - -// ListOrgInvitations lists pending invitations to th org. -// -// https://developer.github.com/v3/orgs/members/#list-pending-organization-invitations -func (c *client) ListOrgInvitations(org string) ([]OrgInvitation, error) { - c.log("ListOrgInvitations", org) - if c.fake { - return nil, nil - } - path := fmt.Sprintf("/orgs/%s/invitations", org) - var ret []OrgInvitation - err := c.readPaginatedResults( - path, - acceptNone, - org, - func() interface{} { - return &[]OrgInvitation{} - }, - func(obj interface{}) { - ret = append(ret, *(obj.(*[]OrgInvitation))...) - }, - ) - if err != nil { - return nil, err - } - return ret, nil -} - -// ListCurrentUserRepoInvitations lists pending invitations for the authenticated user. -// -// https://docs.github.com/en/rest/reference/repos#list-repository-invitations-for-the-authenticated-user -func (c *client) ListCurrentUserRepoInvitations() ([]UserRepoInvitation, error) { - c.log("ListCurrentUserRepoInvitations") - if c.fake { - return nil, nil - } - path := "/user/repository_invitations" - var ret []UserRepoInvitation - err := c.readPaginatedResults( - path, - acceptNone, - "", - func() interface{} { - return &[]UserRepoInvitation{} - }, - func(obj interface{}) { - ret = append(ret, *(obj.(*[]UserRepoInvitation))...) - }, - ) - if err != nil { - return nil, err - } - return ret, nil -} - -// AcceptUserRepoInvitation accepts invitation for the authenticated user. -// -// https://docs.github.com/en/rest/reference/repos#accept-a-repository-invitation -func (c *client) AcceptUserRepoInvitation(invitationID int) error { - c.log("AcceptUserRepoInvitation", invitationID) - - _, err := c.request(&request{ - method: http.MethodPatch, - path: fmt.Sprintf("/user/repository_invitations/%d", invitationID), - org: "", - exitCodes: []int{204}, - }, nil) - - return err -} - -// ListCurrentUserOrgInvitations lists org invitation for the authenticated user. -// -// https://docs.github.com/en/rest/reference/orgs#get-organization-membership-for-a-user -func (c *client) ListCurrentUserOrgInvitations() ([]UserOrgInvitation, error) { - c.log("ListCurrentUserOrgInvitations") - if c.fake { - return nil, nil - } - path := "/user/memberships/orgs" - var ret []UserOrgInvitation - err := c.readPaginatedResultsWithValues( - path, - url.Values{ - "per_page": []string{"100"}, - "state": []string{"pending"}, - }, - acceptNone, - "", - func() interface{} { - return &[]UserOrgInvitation{} - }, - func(obj interface{}) { - for _, uoi := range *(obj.(*[]UserOrgInvitation)) { - if uoi.State == "pending" { - ret = append(ret, uoi) - } - } - }, - ) - if err != nil { - return nil, err - } - return ret, nil -} - -// AcceptUserOrgInvitation accepts org invitation for the authenticated user. -// -// https://docs.github.com/en/rest/reference/orgs#update-an-organization-membership-for-the-authenticated-user -func (c *client) AcceptUserOrgInvitation(org string) error { - c.log("AcceptUserOrgInvitation", org) - - _, err := c.request(&request{ - method: http.MethodPatch, - path: fmt.Sprintf("/user/memberships/orgs/%s", org), - org: org, - requestBody: map[string]string{"state": "active"}, - exitCodes: []int{200}, - }, nil) - - return err -} - -// ListOrgMembers list all users who are members of an organization. If the authenticated -// user is also a member of this organization then both concealed and public members -// will be returned. -// -// Role options are "all", "admin" and "member" -// -// https://developer.github.com/v3/orgs/members/#members-list -func (c *client) ListOrgMembers(org, role string) ([]TeamMember, error) { - c.log("ListOrgMembers", org, role) - if c.fake { - return nil, nil - } - path := fmt.Sprintf("/orgs/%s/members", org) - var teamMembers []TeamMember - err := c.readPaginatedResultsWithValues( - path, - url.Values{ - "per_page": []string{"100"}, - "role": []string{role}, - }, - acceptNone, - org, - func() interface{} { - return &[]TeamMember{} - }, - func(obj interface{}) { - teamMembers = append(teamMembers, *(obj.(*[]TeamMember))...) - }, - ) - if err != nil { - return nil, err - } - return teamMembers, nil -} - -// HasPermission returns true if GetUserPermission() returns any of the roles. -func (c *client) HasPermission(org, repo, user string, roles ...string) (bool, error) { - perm, err := c.GetUserPermission(org, repo, user) - if err != nil { - return false, err - } - for _, r := range roles { - if r == perm { - return true, nil - } - } - return false, nil -} - -// GetUserPermission returns the user's permission level for a repo -// -// https://developer.github.com/v3/repos/collaborators/#review-a-users-permission-level -func (c *client) GetUserPermission(org, repo, user string) (string, error) { - c.log("GetUserPermission", org, repo, user) - - var perm struct { - Perm string `json:"permission"` - } - _, err := c.request(&request{ - method: http.MethodGet, - path: fmt.Sprintf("/repos/%s/%s/collaborators/%s/permission", org, repo, user), - org: org, - exitCodes: []int{200}, - }, &perm) - if err != nil { - return "", err - } - return perm.Perm, nil -} - -// UpdateOrgMembership invites a user to the org and/or updates their permission level. -// -// If the user is not already a member, this will invite them. -// This will also change the role to/from admin, on either the invitation or membership setting. -// -// https://developer.github.com/v3/orgs/members/#add-or-update-organization-membership -func (c *client) UpdateOrgMembership(org, user string, admin bool) (*OrgMembership, error) { - c.log("UpdateOrgMembership", org, user, admin) - om := OrgMembership{} - if admin { - om.Role = RoleAdmin - } else { - om.Role = RoleMember - } - if c.dry { - return &om, nil - } - - _, err := c.request(&request{ - method: http.MethodPut, - path: fmt.Sprintf("/orgs/%s/memberships/%s", org, user), - org: org, - requestBody: &om, - exitCodes: []int{200}, - }, &om) - return &om, err -} - -// RemoveOrgMembership removes the user from the org. -// -// https://developer.github.com/v3/orgs/members/#remove-organization-membership -func (c *client) RemoveOrgMembership(org, user string) error { - c.log("RemoveOrgMembership", org, user) - _, err := c.request(&request{ - method: http.MethodDelete, - org: org, - path: fmt.Sprintf("/orgs/%s/memberships/%s", org, user), - exitCodes: []int{204}, - }, nil) - return err -} - -// CreateComment creates a comment on the issue. -// -// See https://developer.github.com/v3/issues/comments/#create-a-comment -func (c *client) CreateComment(org, repo string, number int, comment string) error { - return c.CreateCommentWithContext(context.Background(), org, repo, number, comment) -} - -func (c *client) CreateCommentWithContext(ctx context.Context, org, repo string, number int, comment string) error { - c.log("CreateComment", org, repo, number, comment) - ic := IssueComment{ - Body: comment, - } - _, err := c.requestWithContext(ctx, &request{ - method: http.MethodPost, - path: fmt.Sprintf("/repos/%s/%s/issues/%d/comments", org, repo, number), - org: org, - requestBody: &ic, - exitCodes: []int{201}, - }, nil) - return err -} - -// DeleteComment deletes the comment. -// -// See https://developer.github.com/v3/issues/comments/#delete-a-comment -func (c *client) DeleteComment(org, repo string, id int) error { - return c.DeleteCommentWithContext(context.Background(), org, repo, id) -} - -func (c *client) DeleteCommentWithContext(ctx context.Context, org, repo string, id int) error { - c.log("DeleteComment", org, repo, id) - _, err := c.requestWithContext(ctx, &request{ - method: http.MethodDelete, - path: fmt.Sprintf("/repos/%s/%s/issues/comments/%d", org, repo, id), - org: org, - exitCodes: []int{204, 404}, - }, nil) - return err -} - -// EditComment changes the body of comment id in org/repo. -// -// See https://developer.github.com/v3/issues/comments/#edit-a-comment -func (c *client) EditComment(org, repo string, id int, comment string) error { - return c.EditCommentWithContext(context.Background(), org, repo, id, comment) -} - -func (c *client) EditCommentWithContext(ctx context.Context, org, repo string, id int, comment string) error { - c.log("EditComment", org, repo, id, comment) - ic := IssueComment{ - Body: comment, - } - _, err := c.requestWithContext(ctx, &request{ - method: http.MethodPatch, - path: fmt.Sprintf("/repos/%s/%s/issues/comments/%d", org, repo, id), - org: org, - requestBody: &ic, - exitCodes: []int{200}, - }, nil) - return err -} - -// CreateCommentReaction responds emotionally to comment id in org/repo. -// -// See https://developer.github.com/v3/reactions/#create-reaction-for-an-issue-comment -func (c *client) CreateCommentReaction(org, repo string, id int, reaction string) error { - c.log("CreateCommentReaction", org, repo, id, reaction) - r := Reaction{Content: reaction} - _, err := c.request(&request{ - method: http.MethodPost, - path: fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", org, repo, id), - accept: "application/vnd.github.squirrel-girl-preview", - org: org, - exitCodes: []int{201}, - requestBody: &r, - }, nil) - return err -} - -// CreateIssue creates a new issue and returns its number if -// the creation is successful, otherwise any error that is encountered. -// -// See https://developer.github.com/v3/issues/#create-an-issue -func (c *client) CreateIssue(org, repo, title, body string, milestone int, labels, assignees []string) (int, error) { - durationLogger := c.log("CreateIssue", org, repo, title) - defer durationLogger() - - data := struct { - Title string `json:"title,omitempty"` - Body string `json:"body,omitempty"` - Milestone int `json:"milestone,omitempty"` - Labels []string `json:"labels,omitempty"` - Assignees []string `json:"assignees,omitempty"` - }{ - Title: title, - Body: body, - Milestone: milestone, - Labels: labels, - Assignees: assignees, - } - var resp struct { - Num int `json:"number"` - } - _, err := c.request(&request{ - // allow the description and draft fields - // https://developer.github.com/changes/2019-02-14-draft-pull-requests/ - accept: "application/vnd.github+json, application/vnd.github.shadow-cat-preview", - method: http.MethodPost, - path: fmt.Sprintf("/repos/%s/%s/issues", org, repo), - org: org, - requestBody: &data, - exitCodes: []int{201}, - }, &resp) - if err != nil { - return 0, err - } - return resp.Num, nil -} - -// CreateIssueReaction responds emotionally to org/repo#id -// -// See https://developer.github.com/v3/reactions/#create-reaction-for-an-issue -func (c *client) CreateIssueReaction(org, repo string, id int, reaction string) error { - c.log("CreateIssueReaction", org, repo, id, reaction) - r := Reaction{Content: reaction} - _, err := c.request(&request{ - method: http.MethodPost, - path: fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", org, repo, id), - accept: "application/vnd.github.squirrel-girl-preview", - org: org, - requestBody: &r, - exitCodes: []int{200, 201}, - }, nil) - return err -} - -// DeleteStaleComments iterates over comments on an issue/PR, deleting those which the 'isStale' -// function identifies as stale. If 'comments' is nil, the comments will be fetched from GitHub. -func (c *client) DeleteStaleComments(org, repo string, number int, comments []IssueComment, isStale func(IssueComment) bool) error { - return c.DeleteStaleCommentsWithContext(context.Background(), org, repo, number, comments, isStale) -} - -func (c *client) DeleteStaleCommentsWithContext(ctx context.Context, org, repo string, number int, comments []IssueComment, isStale func(IssueComment) bool) error { - var err error - if comments == nil { - comments, err = c.ListIssueCommentsWithContext(ctx, org, repo, number) - if err != nil { - return fmt.Errorf("failed to list comments while deleting stale comments. err: %w", err) - } - } - for _, comment := range comments { - if isStale(comment) { - if err := c.DeleteComment(org, repo, comment.ID); err != nil { - return fmt.Errorf("failed to delete stale comment with ID '%d'", comment.ID) - } - } - } - return nil -} - -// readPaginatedResults iterates over all objects in the paginated result indicated by the given url. -// -// newObj() should return a new slice of the expected type -// accumulate() should accept that populated slice for each page of results. -// -// Returns an error any call to GitHub or object marshalling fails. -func (c *client) readPaginatedResults(path, accept, org string, newObj func() interface{}, accumulate func(interface{})) error { - return c.readPaginatedResultsWithContext(context.Background(), path, accept, org, newObj, accumulate) -} - -func (c *client) readPaginatedResultsWithContext(ctx context.Context, path, accept, org string, newObj func() interface{}, accumulate func(interface{})) error { - values := url.Values{ - "per_page": []string{"100"}, - } - return c.readPaginatedResultsWithValuesWithContext(ctx, path, values, accept, org, newObj, accumulate) -} - -// readPaginatedResultsWithValues is an override that allows control over the query string. -func (c *client) readPaginatedResultsWithValues(path string, values url.Values, accept, org string, newObj func() interface{}, accumulate func(interface{})) error { - return c.readPaginatedResultsWithValuesWithContext(context.Background(), path, values, accept, org, newObj, accumulate) -} - -func (c *client) readPaginatedResultsWithValuesWithContext(ctx context.Context, path string, values url.Values, accept, org string, newObj func() interface{}, accumulate func(interface{})) error { - pagedPath := path - if len(values) > 0 { - pagedPath += "?" + values.Encode() - } - for { - resp, err := c.requestRetryWithContext(ctx, http.MethodGet, pagedPath, accept, org, nil) - if err != nil { - return err - } - defer resp.Body.Close() - if resp.StatusCode < 200 || resp.StatusCode > 299 { - return fmt.Errorf("return code not 2XX: %s", resp.Status) - } - - b, err := io.ReadAll(resp.Body) - if err != nil { - return err - } - - obj := newObj() - if err := json.Unmarshal(b, obj); err != nil { - return err - } - - accumulate(obj) - - link := parseLinks(resp.Header.Get("Link"))["next"] - if link == "" { - break - } - - // Example for github.com: - // * c.bases[0]: api.github.com - // * initial call: api.github.com/repos/kubernetes/kubernetes/pulls?per_page=100 - // * next: api.github.com/repositories/22/pulls?per_page=100&page=2 - // * in this case prefix will be empty and we're just calling the path returned by next - // Example for github enterprise: - // * c.bases[0]: /api/v3 - // * initial call: /api/v3/repos/kubernetes/kubernetes/pulls?per_page=100 - // * next: /api/v3/repositories/22/pulls?per_page=100&page=2 - // * in this case prefix will be "/api/v3" and we will strip the prefix. If we don't do that, - // the next call will go to /api/v3/api/v3/repositories/22/pulls?per_page=100&page=2 - prefix := strings.TrimSuffix(resp.Request.URL.RequestURI(), pagedPath) - - u, err := url.Parse(link) - if err != nil { - return fmt.Errorf("failed to parse 'next' link: %w", err) - } - pagedPath = strings.TrimPrefix(u.RequestURI(), prefix) - } - return nil -} - -// ListIssueComments returns all comments on an issue. -// -// Each page of results consumes one API token. -// -// See https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue -func (c *client) ListIssueComments(org, repo string, number int) ([]IssueComment, error) { - return c.ListIssueCommentsWithContext(context.Background(), org, repo, number) -} - -func (c *client) ListIssueCommentsWithContext(ctx context.Context, org, repo string, number int) ([]IssueComment, error) { - c.log("ListIssueComments", org, repo, number) - if c.fake { - return nil, nil - } - path := fmt.Sprintf("/repos/%s/%s/issues/%d/comments", org, repo, number) - var comments []IssueComment - err := c.readPaginatedResultsWithContext( - ctx, - path, - acceptNone, - org, - func() interface{} { - return &[]IssueComment{} - }, - func(obj interface{}) { - comments = append(comments, *(obj.(*[]IssueComment))...) - }, - ) - if err != nil { - return nil, err - } - return comments, nil -} - -// ListOpenIssues returns all open issues, including pull requests -// -// Each page of results consumes one API token. -// -// See https://developer.github.com/v3/issues/#list-issues-for-a-repository -func (c *client) ListOpenIssues(org, repo string) ([]Issue, error) { - c.log("ListOpenIssues", org, repo) - if c.fake { - return nil, nil - } - path := fmt.Sprintf("/repos/%s/%s/issues", org, repo) - var issues []Issue - err := c.readPaginatedResults( - path, - acceptNone, - org, - func() interface{} { - return &[]Issue{} - }, - func(obj interface{}) { - issues = append(issues, *(obj.(*[]Issue))...) - }, - ) - if err != nil { - return nil, err - } - return issues, nil -} - -// GetPullRequests get all open pull requests for a repo. -// -// See https://developer.github.com/v3/pulls/#list-pull-requests -func (c *client) GetPullRequests(org, repo string) ([]PullRequest, error) { - c.log("GetPullRequests", org, repo) - var prs []PullRequest - if c.fake { - return prs, nil - } - path := fmt.Sprintf("/repos/%s/%s/pulls", org, repo) - err := c.readPaginatedResults( - path, - // allow the description and draft fields - // https://developer.github.com/changes/2018-02-22-label-description-search-preview/ - // https://developer.github.com/changes/2019-02-14-draft-pull-requests/ - "application/vnd.github.symmetra-preview+json, application/vnd.github.shadow-cat-preview", - org, - func() interface{} { - return &[]PullRequest{} - }, - func(obj interface{}) { - prs = append(prs, *(obj.(*[]PullRequest))...) - }, - ) - if err != nil { - return nil, err - } - return prs, err -} - -// GetPullRequest gets a pull request. -// -// See https://developer.github.com/v3/pulls/#get-a-single-pull-request -func (c *client) GetPullRequest(org, repo string, number int) (*PullRequest, error) { - durationLogger := c.log("GetPullRequest", org, repo, number) - defer durationLogger() - - var pr PullRequest - _, err := c.request(&request{ - // allow the description and draft fields - // https://developer.github.com/changes/2018-02-22-label-description-search-preview/ - // https://developer.github.com/changes/2019-02-14-draft-pull-requests/ - accept: "application/vnd.github.symmetra-preview+json, application/vnd.github.shadow-cat-preview", - method: http.MethodGet, - path: fmt.Sprintf("/repos/%s/%s/pulls/%d", org, repo, number), - org: org, - exitCodes: []int{200}, - }, &pr) - return &pr, err -} - -func (c *client) GetFailedActionRunsByHeadBranch(org, repo, branchName, headSHA string) ([]WorkflowRun, error) { - durationLogger := c.log("GetJobsByHeadBranch", org, repo) - defer durationLogger() - - var runs WorkflowRuns - - url := url.URL{ - Path: fmt.Sprintf("/repos/%s/%s/actions/runs", org, repo), - } - query := url.Query() - - query.Add("status", "failure") - query.Add("event", "pull_request") - query.Add("branch", branchName) - - url.RawQuery = query.Encode() - - _, err := c.request(&request{ - accept: "application/vnd.github.v3+json", - method: http.MethodGet, - path: url.String(), - org: org, - exitCodes: []int{200}, - }, &runs) - - prRuns := []WorkflowRun{} - - // keep only the runs matching the current PR headSHA - for _, run := range runs.WorflowRuns { - if run.HeadSha == headSHA { - prRuns = append(prRuns, run) - } - } - - return prRuns, err -} - -func (c *client) TriggerGitHubWorkflow(org, repo string, id int) error { - durationLogger := c.log("TriggerGitHubWorkflow", org, repo, id) - defer durationLogger() - _, err := c.request(&request{ - accept: "application/vnd.github.v3+json", - method: http.MethodPost, - path: fmt.Sprintf("/repos/%s/%s/actions/runs/%d/rerun", org, repo, id), - org: org, - exitCodes: []int{201}, - }, nil) - return err -} - -// EditPullRequest will update the pull request. -// -// See https://developer.github.com/v3/pulls/#update-a-pull-request -func (c *client) EditPullRequest(org, repo string, number int, pr *PullRequest) (*PullRequest, error) { - durationLogger := c.log("EditPullRequest", org, repo, number) - defer durationLogger() - - if c.dry { - return pr, nil - } - edit := struct { - Title string `json:"title,omitempty"` - Body string `json:"body,omitempty"` - State string `json:"state,omitempty"` - }{ - Title: pr.Title, - Body: pr.Body, - State: pr.State, - } - var ret PullRequest - _, err := c.request(&request{ - method: http.MethodPatch, - path: fmt.Sprintf("/repos/%s/%s/pulls/%d", org, repo, number), - org: org, - exitCodes: []int{200}, - requestBody: &edit, - }, &ret) - if err != nil { - return nil, err - } - return &ret, nil -} - -// GetIssue gets an issue. -// -// See https://developer.github.com/v3/issues/#get-a-single-issue -func (c *client) GetIssue(org, repo string, number int) (*Issue, error) { - durationLogger := c.log("GetIssue", org, repo, number) - defer durationLogger() - - var i Issue - _, err := c.request(&request{ - // allow emoji - // https://developer.github.com/changes/2018-02-22-label-description-search-preview/ - accept: "application/vnd.github.symmetra-preview+json", - method: http.MethodGet, - path: fmt.Sprintf("/repos/%s/%s/issues/%d", org, repo, number), - org: org, - exitCodes: []int{200}, - }, &i) - return &i, err -} - -// EditIssue will update the issue. -// -// See https://developer.github.com/v3/issues/#edit-an-issue -func (c *client) EditIssue(org, repo string, number int, issue *Issue) (*Issue, error) { - durationLogger := c.log("EditIssue", org, repo, number) - defer durationLogger() - - if c.dry { - return issue, nil - } - edit := struct { - Title string `json:"title,omitempty"` - Body string `json:"body,omitempty"` - State string `json:"state,omitempty"` - }{ - Title: issue.Title, - Body: issue.Body, - State: issue.State, - } - var ret Issue - _, err := c.request(&request{ - method: http.MethodPatch, - path: fmt.Sprintf("/repos/%s/%s/issues/%d", org, repo, number), - org: org, - exitCodes: []int{200}, - requestBody: &edit, - }, &ret) - if err != nil { - return nil, err - } - return &ret, nil -} - -// GetPullRequestPatch gets the patch version of a pull request. -// -// See https://developer.github.com/v3/media/#commits-commit-comparison-and-pull-requests -func (c *client) GetPullRequestPatch(org, repo string, number int) ([]byte, error) { - durationLogger := c.log("GetPullRequestPatch", org, repo, number) - defer durationLogger() - - _, patch, err := c.requestRaw(&request{ - accept: "application/vnd.github.VERSION.patch", - method: http.MethodGet, - path: fmt.Sprintf("/repos/%s/%s/pulls/%d", org, repo, number), - org: org, - exitCodes: []int{200}, - }) - return patch, err -} - -// CreatePullRequest creates a new pull request and returns its number if -// the creation is successful, otherwise any error that is encountered. -// -// See https://developer.github.com/v3/pulls/#create-a-pull-request -func (c *client) CreatePullRequest(org, repo, title, body, head, base string, canModify bool) (int, error) { - durationLogger := c.log("CreatePullRequest", org, repo, title) - defer durationLogger() - - data := struct { - Title string `json:"title"` - Body string `json:"body"` - Head string `json:"head"` - Base string `json:"base"` - // MaintainerCanModify allows maintainers of the repo to modify this - // pull request, eg. push changes to it before merging. - MaintainerCanModify bool `json:"maintainer_can_modify"` - }{ - Title: title, - Body: body, - Head: head, - Base: base, - - MaintainerCanModify: canModify, - } - var resp struct { - Num int `json:"number"` - } - _, err := c.request(&request{ - // allow the description and draft fields - // https://developer.github.com/changes/2018-02-22-label-description-search-preview/ - // https://developer.github.com/changes/2019-02-14-draft-pull-requests/ - accept: "application/vnd.github.symmetra-preview+json, application/vnd.github.shadow-cat-preview", - method: http.MethodPost, - path: fmt.Sprintf("/repos/%s/%s/pulls", org, repo), - org: org, - requestBody: &data, - exitCodes: []int{201}, - }, &resp) - if err != nil { - return 0, fmt.Errorf("failed to create pull request against %s/%s#%s from head %s: %w", org, repo, base, head, err) - } - return resp.Num, nil -} - -// UpdatePullRequest modifies the title, body, open state -func (c *client) UpdatePullRequest(org, repo string, number int, title, body *string, open *bool, branch *string, canModify *bool) error { - durationLogger := c.log("UpdatePullRequest", org, repo, title) - defer durationLogger() - - data := struct { - State *string `json:"state,omitempty"` - Title *string `json:"title,omitempty"` - Body *string `json:"body,omitempty"` - Base *string `json:"base,omitempty"` - // MaintainerCanModify allows maintainers of the repo to modify this - // pull request, eg. push changes to it before merging. - MaintainerCanModify *bool `json:"maintainer_can_modify,omitempty"` - }{ - Title: title, - Body: body, - Base: branch, - MaintainerCanModify: canModify, - } - if open != nil && *open { - op := "open" - data.State = &op - } else if open != nil { - cl := "closed" - data.State = &cl - } - _, err := c.request(&request{ - // allow the description and draft fields - // https://developer.github.com/changes/2018-02-22-label-description-search-preview/ - // https://developer.github.com/changes/2019-02-14-draft-pull-requests/ - accept: "application/vnd.github.symmetra-preview+json, application/vnd.github.shadow-cat-preview", - method: http.MethodPatch, - path: fmt.Sprintf("/repos/%s/%s/pulls/%d", org, repo, number), - org: org, - requestBody: &data, - exitCodes: []int{200}, - }, nil) - return err -} - -// GetPullRequestChanges gets a list of files modified in a pull request. -// -// See https://developer.github.com/v3/pulls/#list-pull-requests-files -func (c *client) GetPullRequestChanges(org, repo string, number int) ([]PullRequestChange, error) { - durationLogger := c.log("GetPullRequestChanges", org, repo, number) - defer durationLogger() - - if c.fake { - return []PullRequestChange{}, nil - } - path := fmt.Sprintf("/repos/%s/%s/pulls/%d/files", org, repo, number) - var changes []PullRequestChange - err := c.readPaginatedResults( - path, - acceptNone, - org, - func() interface{} { - return &[]PullRequestChange{} - }, - func(obj interface{}) { - changes = append(changes, *(obj.(*[]PullRequestChange))...) - }, - ) - if err != nil { - return nil, err - } - return changes, nil -} - -// ListPullRequestComments returns all *review* comments on a pull request. -// -// Multiple-pages of comments consumes multiple API tokens. -// -// See https://developer.github.com/v3/pulls/comments/#list-comments-on-a-pull-request -func (c *client) ListPullRequestComments(org, repo string, number int) ([]ReviewComment, error) { - durationLogger := c.log("ListPullRequestComments", org, repo, number) - defer durationLogger() - - if c.fake { - return nil, nil - } - path := fmt.Sprintf("/repos/%s/%s/pulls/%d/comments", org, repo, number) - var comments []ReviewComment - err := c.readPaginatedResults( - path, - acceptNone, - org, - func() interface{} { - return &[]ReviewComment{} - }, - func(obj interface{}) { - comments = append(comments, *(obj.(*[]ReviewComment))...) - }, - ) - if err != nil { - return nil, err - } - return comments, nil -} - -// ListReviews returns all reviews on a pull request. -// -// Multiple-pages of results consumes multiple API tokens. -// -// See https://developer.github.com/v3/pulls/reviews/#list-reviews-on-a-pull-request -func (c *client) ListReviews(org, repo string, number int) ([]Review, error) { - durationLogger := c.log("ListReviews", org, repo, number) - defer durationLogger() - - if c.fake { - return nil, nil - } - path := fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews", org, repo, number) - var reviews []Review - err := c.readPaginatedResults( - path, - acceptNone, - org, - func() interface{} { - return &[]Review{} - }, - func(obj interface{}) { - reviews = append(reviews, *(obj.(*[]Review))...) - }, - ) - if err != nil { - return nil, err - } - return reviews, nil -} - -// CreateStatus creates or updates the status of a commit. -// -// See https://docs.github.com/en/rest/reference/commits#create-a-commit-status -func (c *client) CreateStatus(org, repo, SHA string, s Status) error { - return c.CreateStatusWithContext(context.Background(), org, repo, SHA, s) -} - -func (c *client) CreateStatusWithContext(ctx context.Context, org, repo, SHA string, s Status) error { - durationLogger := c.log("CreateStatus", org, repo, SHA, s) - defer durationLogger() - _, err := c.requestWithContext(ctx, &request{ - method: http.MethodPost, - path: fmt.Sprintf("/repos/%s/%s/statuses/%s", org, repo, SHA), - org: org, - requestBody: &s, - exitCodes: []int{201}, - }, nil) - return err -} - -// ListStatuses gets commit statuses for a given ref. -// -// See https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref -func (c *client) ListStatuses(org, repo, ref string) ([]Status, error) { - durationLogger := c.log("ListStatuses", org, repo, ref) - defer durationLogger() - - path := fmt.Sprintf("/repos/%s/%s/statuses/%s", org, repo, ref) - var statuses []Status - err := c.readPaginatedResults( - path, - acceptNone, - org, - func() interface{} { - return &[]Status{} - }, - func(obj interface{}) { - statuses = append(statuses, *(obj.(*[]Status))...) - }, - ) - return statuses, err -} - -// GetRepo returns the repo for the provided owner/name combination. -// -// See https://developer.github.com/v3/repos/#get -func (c *client) GetRepo(owner, name string) (FullRepo, error) { - durationLogger := c.log("GetRepo", owner, name) - defer durationLogger() - - var repo FullRepo - _, err := c.request(&request{ - method: http.MethodGet, - path: fmt.Sprintf("/repos/%s/%s", owner, name), - org: owner, - exitCodes: []int{200}, - }, &repo) - return repo, err -} - -// CreateRepo creates a new repository -// See https://developer.github.com/v3/repos/#create -func (c *client) CreateRepo(owner string, isUser bool, repo RepoCreateRequest) (*FullRepo, error) { - durationLogger := c.log("CreateRepo", owner, isUser, repo) - defer durationLogger() - - if repo.Name == nil || *repo.Name == "" { - return nil, errors.New("repo.Name must be non-empty") - } - if c.fake { - return nil, nil - } else if c.dry { - return repo.ToRepo(), nil - } - - path := "/user/repos" - if !isUser { - path = fmt.Sprintf("/orgs/%s/repos", owner) - } - var retRepo FullRepo - _, err := c.request(&request{ - method: http.MethodPost, - path: path, - org: owner, - requestBody: &repo, - exitCodes: []int{201}, - }, &retRepo) - return &retRepo, err -} - -// UpdateRepo edits an existing repository -// See https://developer.github.com/v3/repos/#edit -func (c *client) UpdateRepo(owner, name string, repo RepoUpdateRequest) (*FullRepo, error) { - durationLogger := c.log("UpdateRepo", owner, name, repo) - defer durationLogger() - - if c.fake { - return nil, nil - } else if c.dry { - return repo.ToRepo(), nil - } - - path := fmt.Sprintf("/repos/%s/%s", owner, name) - var retRepo FullRepo - _, err := c.request(&request{ - method: http.MethodPatch, - path: path, - org: owner, - requestBody: &repo, - exitCodes: []int{200}, - }, &retRepo) - return &retRepo, err -} - -// GetRepos returns all repos in an org. -// -// This call uses multiple API tokens when results are paginated. -// -// See https://developer.github.com/v3/repos/#list-organization-repositories -func (c *client) GetRepos(org string, isUser bool) ([]Repo, error) { - durationLogger := c.log("GetRepos", org, isUser) - defer durationLogger() - - var ( - repos []Repo - nextURL string - ) - if c.fake { - return repos, nil - } - if isUser { - nextURL = fmt.Sprintf("/users/%s/repos", org) - } else { - nextURL = fmt.Sprintf("/orgs/%s/repos", org) - } - err := c.readPaginatedResults( - nextURL, // path - acceptNone, // accept - org, - func() interface{} { // newObj - return &[]Repo{} - }, - func(obj interface{}) { // accumulate - repos = append(repos, *(obj.(*[]Repo))...) - }, - ) - if err != nil { - return nil, err - } - return repos, nil -} - -// GetSingleCommit returns a single commit. -// -// See https://developer.github.com/v3/repos/#get -func (c *client) GetSingleCommit(org, repo, SHA string) (RepositoryCommit, error) { - durationLogger := c.log("GetSingleCommit", org, repo, SHA) - defer durationLogger() - - var commit RepositoryCommit - _, err := c.request(&request{ - method: http.MethodGet, - path: fmt.Sprintf("/repos/%s/%s/commits/%s", org, repo, SHA), - org: org, - exitCodes: []int{200}, - }, &commit) - return commit, err -} - -// GetBranches returns all branches in the repo. -// -// If onlyProtected is true it will only return repos with protection enabled, -// and branch.Protected will be true. Otherwise Protected is the default value (false). -// -// This call uses multiple API tokens when results are paginated. -// -// See https://developer.github.com/v3/repos/branches/#list-branches -func (c *client) GetBranches(org, repo string, onlyProtected bool) ([]Branch, error) { - durationLogger := c.log("GetBranches", org, repo, onlyProtected) - defer durationLogger() - - var branches []Branch - err := c.readPaginatedResultsWithValues( - fmt.Sprintf("/repos/%s/%s/branches", org, repo), - url.Values{ - "protected": []string{strconv.FormatBool(onlyProtected)}, - "per_page": []string{"100"}, - }, - acceptNone, - org, - func() interface{} { // newObj - return &[]Branch{} - }, - func(obj interface{}) { - branches = append(branches, *(obj.(*[]Branch))...) - }, - ) - if err != nil { - return nil, err - } - return branches, nil -} - -// GetBranchProtection returns current protection object for the branch -// -// See https://developer.github.com/v3/repos/branches/#get-branch-protection -func (c *client) GetBranchProtection(org, repo, branch string) (*BranchProtection, error) { - durationLogger := c.log("GetBranchProtection", org, repo, branch) - defer durationLogger() - - code, body, err := c.requestRaw(&request{ - method: http.MethodGet, - path: fmt.Sprintf("/repos/%s/%s/branches/%s/protection", org, repo, branch), - org: org, - // GitHub returns 404 for this call if either: - // - The branch is not protected - // - The access token used does not have sufficient privileges - // We therefore need to introspect the response body. - exitCodes: []int{200, 404}, - }) - - switch { - case err != nil: - return nil, err - case code == 200: - var bp BranchProtection - if err := json.Unmarshal(body, &bp); err != nil { - return nil, err - } - return &bp, nil - case code == 404: - // continue - default: - return nil, fmt.Errorf("unexpected status code: %d", code) - } - - var ge githubError - if err := json.Unmarshal(body, &ge); err != nil { - return nil, err - } - - // If the error was because the branch is not protected, we return a - // nil pointer to indicate this. - if ge.Message == "Branch not protected" { - return nil, nil - } - - // Otherwise we got some other 404 error. - return nil, fmt.Errorf("getting branch protection 404: %s", ge.Message) -} - -// RemoveBranchProtection unprotects org/repo=branch. -// -// See https://developer.github.com/v3/repos/branches/#remove-branch-protection -func (c *client) RemoveBranchProtection(org, repo, branch string) error { - durationLogger := c.log("RemoveBranchProtection", org, repo, branch) - defer durationLogger() - - _, err := c.request(&request{ - method: http.MethodDelete, - path: fmt.Sprintf("/repos/%s/%s/branches/%s/protection", org, repo, branch), - org: org, - exitCodes: []int{204}, - }, nil) - return err -} - -// UpdateBranchProtection configures org/repo=branch. -// -// See https://developer.github.com/v3/repos/branches/#update-branch-protection -func (c *client) UpdateBranchProtection(org, repo, branch string, config BranchProtectionRequest) error { - durationLogger := c.log("UpdateBranchProtection", org, repo, branch, config) - defer durationLogger() - - _, err := c.request(&request{ - accept: "application/vnd.github.luke-cage-preview+json", // for required_approving_review_count - method: http.MethodPut, - path: fmt.Sprintf("/repos/%s/%s/branches/%s/protection", org, repo, branch), - org: org, - requestBody: config, - exitCodes: []int{200}, - }, nil) - return err -} - -// AddRepoLabel adds a defined label given org/repo -// -// See https://developer.github.com/v3/issues/labels/#create-a-label -func (c *client) AddRepoLabel(org, repo, label, description, color string) error { - durationLogger := c.log("AddRepoLabel", org, repo, label, description, color) - defer durationLogger() - - _, err := c.request(&request{ - method: http.MethodPost, - path: fmt.Sprintf("/repos/%s/%s/labels", org, repo), - accept: "application/vnd.github.symmetra-preview+json", // allow the description field -- https://developer.github.com/changes/2018-02-22-label-description-search-preview/ - org: org, - requestBody: Label{Name: label, Description: description, Color: color}, - exitCodes: []int{201}, - }, nil) - return err -} - -// UpdateRepoLabel updates a org/repo label to new name, description, and color -// -// See https://developer.github.com/v3/issues/labels/#update-a-label -func (c *client) UpdateRepoLabel(org, repo, label, newName, description, color string) error { - durationLogger := c.log("UpdateRepoLabel", org, repo, label, newName, color) - defer durationLogger() - - _, err := c.request(&request{ - method: http.MethodPatch, - path: fmt.Sprintf("/repos/%s/%s/labels/%s", org, repo, label), - accept: "application/vnd.github.symmetra-preview+json", // allow the description field -- https://developer.github.com/changes/2018-02-22-label-description-search-preview/ - org: org, - requestBody: Label{Name: newName, Description: description, Color: color}, - exitCodes: []int{200}, - }, nil) - return err -} - -// DeleteRepoLabel deletes a label in org/repo -// -// See https://developer.github.com/v3/issues/labels/#delete-a-label -func (c *client) DeleteRepoLabel(org, repo, label string) error { - durationLogger := c.log("DeleteRepoLabel", org, repo, label) - defer durationLogger() - - _, err := c.request(&request{ - method: http.MethodDelete, - accept: "application/vnd.github.symmetra-preview+json", // allow the description field -- https://developer.github.com/changes/2018-02-22-label-description-search-preview/ - path: fmt.Sprintf("/repos/%s/%s/labels/%s", org, repo, label), - org: org, - requestBody: Label{Name: label}, - exitCodes: []int{204}, - }, nil) - return err -} - -// GetCombinedStatus returns the latest statuses for a given ref. -// -// See https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref -func (c *client) GetCombinedStatus(org, repo, ref string) (*CombinedStatus, error) { - durationLogger := c.log("GetCombinedStatus", org, repo, ref) - defer durationLogger() - - var combinedStatus CombinedStatus - err := c.readPaginatedResults( - fmt.Sprintf("/repos/%s/%s/commits/%s/status", org, repo, ref), - "", - org, - func() interface{} { - return &CombinedStatus{} - }, - func(obj interface{}) { - cs := *(obj.(*CombinedStatus)) - cs.Statuses = append(combinedStatus.Statuses, cs.Statuses...) - combinedStatus = cs - }, - ) - return &combinedStatus, err -} - -// getLabels is a helper function that retrieves a paginated list of labels from a github URI path. -func (c *client) getLabels(path, org string) ([]Label, error) { - var labels []Label - if c.fake { - return labels, nil - } - err := c.readPaginatedResults( - path, - "application/vnd.github.symmetra-preview+json", // allow the description field -- https://developer.github.com/changes/2018-02-22-label-description-search-preview/ - org, - func() interface{} { - return &[]Label{} - }, - func(obj interface{}) { - labels = append(labels, *(obj.(*[]Label))...) - }, - ) - if err != nil { - return nil, err - } - return labels, nil -} - -// GetRepoLabels returns the list of labels accessible to org/repo. -// -// See https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository -func (c *client) GetRepoLabels(org, repo string) ([]Label, error) { - durationLogger := c.log("GetRepoLabels", org, repo) - defer durationLogger() - - return c.getLabels(fmt.Sprintf("/repos/%s/%s/labels", org, repo), org) -} - -// GetIssueLabels returns the list of labels currently on issue org/repo#number. -// -// See https://developer.github.com/v3/issues/labels/#list-labels-on-an-issue -func (c *client) GetIssueLabels(org, repo string, number int) ([]Label, error) { - durationLogger := c.log("GetIssueLabels", org, repo, number) - defer durationLogger() - - return c.getLabels(fmt.Sprintf("/repos/%s/%s/issues/%d/labels", org, repo, number), org) -} - -// AddLabel adds label to org/repo#number, returning an error on a bad response code. -// -// See https://developer.github.com/v3/issues/labels/#add-labels-to-an-issue -func (c *client) AddLabel(org, repo string, number int, label string) error { - return c.AddLabelWithContext(context.Background(), org, repo, number, label) -} - -func (c *client) AddLabelWithContext(ctx context.Context, org, repo string, number int, label string) error { - return c.AddLabelsWithContext(ctx, org, repo, number, label) -} - -// AddLabels adds one or more labels to org/repo#number, returning an error on a bad response code. -// -// See https://developer.github.com/v3/issues/labels/#add-labels-to-an-issue -func (c *client) AddLabels(org, repo string, number int, labels ...string) error { - return c.AddLabelsWithContext(context.Background(), org, repo, number, labels...) -} - -func (c *client) AddLabelsWithContext(ctx context.Context, org, repo string, number int, labels ...string) error { - durationLogger := c.log("AddLabels", org, repo, number, labels) - defer durationLogger() - - _, err := c.requestWithContext(ctx, &request{ - method: http.MethodPost, - path: fmt.Sprintf("/repos/%s/%s/issues/%d/labels", org, repo, number), - org: org, - requestBody: labels, - exitCodes: []int{200}, - }, nil) - return err -} - -type githubError struct { - Message string `json:"message,omitempty"` -} - -// RemoveLabel removes label from org/repo#number, returning an error on any failure. -// -// See https://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue -func (c *client) RemoveLabel(org, repo string, number int, label string) error { - return c.RemoveLabelWithContext(context.Background(), org, repo, number, label) -} - -func (c *client) RemoveLabelWithContext(ctx context.Context, org, repo string, number int, label string) error { - durationLogger := c.log("RemoveLabel", org, repo, number, label) - defer durationLogger() - - code, body, err := c.requestRawWithContext(ctx, &request{ - method: http.MethodDelete, - path: fmt.Sprintf("/repos/%s/%s/issues/%d/labels/%s", org, repo, number, label), - org: org, - // GitHub sometimes returns 200 for this call, which is a bug on their end. - // Do not expect a 404 exit code and handle it separately because we need - // to introspect the request's response body. - exitCodes: []int{200, 204}, - }) - - switch { - case code == 200 || code == 204: - // If our code was 200 or 204, no error info. - return nil - case code == 404: - // continue - case err != nil: - return err - default: - return fmt.Errorf("unexpected status code: %d", code) - } - - ge := &githubError{} - if err := json.Unmarshal(body, ge); err != nil { - return err - } - - // If the error was because the label was not found, we don't really - // care since the label won't exist anyway. - if ge.Message == "Label does not exist" { - return nil - } - - // Otherwise we got some other 404 error. - return fmt.Errorf("deleting label 404: %s", ge.Message) -} - -func (c *client) WasLabelAddedByHuman(org, repo string, number int, label string) (bool, error) { - isBot, err := c.BotUserChecker() - if err != nil { - return false, fmt.Errorf("failed to construct bot user checker: %w", err) - } - - events, err := c.ListIssueEvents(org, repo, number) - if err != nil { - return false, fmt.Errorf("failed to list issue events: %w", err) - } - var lastAdded ListedIssueEvent - for _, event := range events { - if event.Event != IssueActionLabeled || event.Label.Name != label { - continue - } - lastAdded = event - } - - if lastAdded.Actor.Login == "" || isBot(lastAdded.Actor.Login) { - return false, nil - } - - return true, nil -} - -// MissingUsers is an error specifying the users that could not be unassigned. -type MissingUsers struct { - Users []string - action string - apiErr error -} - -func (m MissingUsers) Error() string { - return fmt.Sprintf("could not %s the following user(s): %s; %v.", m.action, strings.Join(m.Users, ", "), m.apiErr) -} - -// AssignIssue adds logins to org/repo#number, returning an error if any login is missing after making the call. -// -// See https://developer.github.com/v3/issues/assignees/#add-assignees-to-an-issue -func (c *client) AssignIssue(org, repo string, number int, logins []string) error { - durationLogger := c.log("AssignIssue", org, repo, number, logins) - defer durationLogger() - - assigned := make(map[string]bool) - var i Issue - _, err := c.request(&request{ - method: http.MethodPost, - path: fmt.Sprintf("/repos/%s/%s/issues/%d/assignees", org, repo, number), - org: org, - requestBody: map[string][]string{"assignees": logins}, - exitCodes: []int{201}, - }, &i) - if err != nil { - return err - } - for _, assignee := range i.Assignees { - assigned[NormLogin(assignee.Login)] = true - } - missing := MissingUsers{action: "assign"} - for _, login := range logins { - if !assigned[NormLogin(login)] { - missing.Users = append(missing.Users, login) - } - } - if len(missing.Users) > 0 { - return missing - } - return nil -} - -// ExtraUsers is an error specifying the users that could not be unassigned. -type ExtraUsers struct { - Users []string - action string -} - -func (e ExtraUsers) Error() string { - return fmt.Sprintf("could not %s the following user(s): %s.", e.action, strings.Join(e.Users, ", ")) -} - -// UnassignIssue removes logins from org/repo#number, returns an error if any login remains assigned. -// -// See https://developer.github.com/v3/issues/assignees/#remove-assignees-from-an-issue -func (c *client) UnassignIssue(org, repo string, number int, logins []string) error { - durationLogger := c.log("UnassignIssue", org, repo, number, logins) - defer durationLogger() - - assigned := make(map[string]bool) - var i Issue - _, err := c.request(&request{ - method: http.MethodDelete, - path: fmt.Sprintf("/repos/%s/%s/issues/%d/assignees", org, repo, number), - org: org, - requestBody: map[string][]string{"assignees": logins}, - exitCodes: []int{200}, - }, &i) - if err != nil { - return err - } - for _, assignee := range i.Assignees { - assigned[NormLogin(assignee.Login)] = true - } - extra := ExtraUsers{action: "unassign"} - for _, login := range logins { - if assigned[NormLogin(login)] { - extra.Users = append(extra.Users, login) - } - } - if len(extra.Users) > 0 { - return extra - } - return nil -} - -// CreateReview creates a review using the draft. -// -// https://developer.github.com/v3/pulls/reviews/#create-a-pull-request-review -func (c *client) CreateReview(org, repo string, number int, r DraftReview) error { - durationLogger := c.log("CreateReview", org, repo, number, r) - defer durationLogger() - - _, err := c.request(&request{ - method: http.MethodPost, - path: fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews", org, repo, number), - accept: "application/vnd.github.black-cat-preview+json", - org: org, - requestBody: r, - exitCodes: []int{200}, - }, nil) - return err -} - -// prepareReviewersBody separates reviewers from team_reviewers and prepares a map -// -// { -// "reviewers": [ -// "octocat", -// "hubot", -// "other_user" -// ], -// "team_reviewers": [ -// "justice-league" -// ] -// } -// -// https://developer.github.com/v3/pulls/review_requests/#create-a-review-request -func prepareReviewersBody(logins []string, org string) (map[string][]string, error) { - body := map[string][]string{} - var errors []error - for _, login := range logins { - mat := teamRe.FindStringSubmatch(login) - if mat == nil { - if _, exists := body["reviewers"]; !exists { - body["reviewers"] = []string{} - } - body["reviewers"] = append(body["reviewers"], login) - } else if mat[1] == org { - if _, exists := body["team_reviewers"]; !exists { - body["team_reviewers"] = []string{} - } - body["team_reviewers"] = append(body["team_reviewers"], mat[2]) - } else { - errors = append(errors, fmt.Errorf("team %s is not part of %s org", login, org)) - } - } - return body, utilerrors.NewAggregate(errors) -} - -func (c *client) tryRequestReview(org, repo string, number int, logins []string) (int, error) { - durationLogger := c.log("RequestReview", org, repo, number, logins) - defer durationLogger() - - var pr PullRequest - body, err := prepareReviewersBody(logins, org) - if err != nil { - // At least one team not in org, - // let RequestReview handle retries and alerting for each login. - return http.StatusUnprocessableEntity, err - } - return c.request(&request{ - method: http.MethodPost, - path: fmt.Sprintf("/repos/%s/%s/pulls/%d/requested_reviewers", org, repo, number), - org: org, - accept: "application/vnd.github.symmetra-preview+json", - requestBody: body, - exitCodes: []int{http.StatusCreated /*201*/}, - }, &pr) -} - -// RequestReview tries to add the users listed in 'logins' as requested reviewers of the specified PR. -// If any user in the 'logins' slice is not a contributor of the repo, the entire POST will fail -// without adding any reviewers. The GitHub API response does not specify which user(s) were invalid -// so if we fail to request reviews from the members of 'logins' we try to request reviews from -// each member individually. We try first with all users in 'logins' for efficiency in the common case. -// -// See https://developer.github.com/v3/pulls/review_requests/#create-a-review-request -func (c *client) RequestReview(org, repo string, number int, logins []string) error { - statusCode, err := c.tryRequestReview(org, repo, number, logins) - if err != nil && statusCode == http.StatusUnprocessableEntity /*422*/ { - // Failed to set all members of 'logins' as reviewers, try individually. - missing := MissingUsers{action: "request a PR review from", apiErr: err} - for _, user := range logins { - statusCode, err = c.tryRequestReview(org, repo, number, []string{user}) - if err != nil && statusCode == http.StatusUnprocessableEntity /*422*/ { - // User is not a contributor, or team not in org. - missing.Users = append(missing.Users, user) - } else if err != nil { - return fmt.Errorf("failed to add reviewer to PR. Status code: %d, errmsg: %w", statusCode, err) - } - } - if len(missing.Users) > 0 { - return missing - } - return nil - } - return err -} - -// UnrequestReview tries to remove the users listed in 'logins' from the requested reviewers of the -// specified PR. The GitHub API treats deletions of review requests differently than creations. Specifically, if -// 'logins' contains a user that isn't a requested reviewer, other users that are valid are still removed. -// Furthermore, the API response lists the set of requested reviewers after the deletion (unlike request creations), -// so we can determine if each deletion was successful. -// The API responds with http status code 200 no matter what the content of 'logins' is. -// -// See https://developer.github.com/v3/pulls/review_requests/#delete-a-review-request -func (c *client) UnrequestReview(org, repo string, number int, logins []string) error { - durationLogger := c.log("UnrequestReview", org, repo, number, logins) - defer durationLogger() - - var pr PullRequest - body, err := prepareReviewersBody(logins, org) - if len(body) == 0 { - // No point in doing request for none, - // if some logins didn't make it to body, extras.Users will catch them. - return err - } - _, err = c.request(&request{ - method: http.MethodDelete, - path: fmt.Sprintf("/repos/%s/%s/pulls/%d/requested_reviewers", org, repo, number), - accept: "application/vnd.github.symmetra-preview+json", - org: org, - requestBody: body, - exitCodes: []int{http.StatusOK /*200*/}, - }, &pr) - if err != nil { - return err - } - extras := ExtraUsers{action: "remove the PR review request for"} - for _, user := range pr.RequestedReviewers { - found := false - for _, toDelete := range logins { - if NormLogin(user.Login) == NormLogin(toDelete) { - found = true - break - } - } - if found { - extras.Users = append(extras.Users, user.Login) - } - } - if len(extras.Users) > 0 { - return extras - } - return nil -} - -// CloseIssue closes the existing, open issue provided -// CloseIssue also closes the issue with the reason being -// "completed" - default value for the state_reason attribute. -// -// See https://developer.github.com/v3/issues/#edit-an-issue -func (c *client) CloseIssue(org, repo string, number int) error { - durationLogger := c.log("CloseIssue", org, repo, number) - defer durationLogger() - - return c.closeIssue(org, repo, number, "completed") -} - -// CloseIssueAsNotPlanned closes the existing, open issue provided -// CloseIssueAsNotPlanned also closes the issue with the reason being -// "not_planned" - value passed for the state_reason attribute. -// -// See https://developer.github.com/v3/issues/#edit-an-issue -func (c *client) CloseIssueAsNotPlanned(org, repo string, number int) error { - durationLogger := c.log("CloseIssueAsNotPlanned", org, repo, number) - defer durationLogger() - - return c.closeIssue(org, repo, number, "not_planned") -} - -func (c *client) closeIssue(org, repo string, number int, reason string) error { - _, err := c.request(&request{ - method: http.MethodPatch, - path: fmt.Sprintf("/repos/%s/%s/issues/%d", org, repo, number), - org: org, - requestBody: map[string]string{"state": "closed", "state_reason": reason}, - exitCodes: []int{200}, - }, nil) - - return err -} - -// StateCannotBeChanged represents the "custom" GitHub API -// error that occurs when a resource cannot be changed -type StateCannotBeChanged struct { - Message string -} - -func (s StateCannotBeChanged) Error() string { - return s.Message -} - -// StateCannotBeChanged implements error -var _ error = (*StateCannotBeChanged)(nil) - -// convert to a StateCannotBeChanged if appropriate or else return the original error -func stateCannotBeChangedOrOriginalError(err error) error { - requestErr, ok := err.(requestError) - if ok { - for _, errorMsg := range requestErr.ErrorMessages() { - if strings.Contains(errorMsg, stateCannotBeChangedMessagePrefix) { - return StateCannotBeChanged{ - Message: errorMsg, - } - } - } - } - return err -} - -// ReopenIssue re-opens the existing, closed issue provided -// -// See https://developer.github.com/v3/issues/#edit-an-issue -func (c *client) ReopenIssue(org, repo string, number int) error { - durationLogger := c.log("ReopenIssue", org, repo, number) - defer durationLogger() - - _, err := c.request(&request{ - method: http.MethodPatch, - path: fmt.Sprintf("/repos/%s/%s/issues/%d", org, repo, number), - org: org, - requestBody: map[string]string{"state": "open"}, - exitCodes: []int{200}, - }, nil) - return stateCannotBeChangedOrOriginalError(err) -} - -// ClosePR closes the existing, open PR provided -// TODO: Rename to ClosePullRequest -// -// See https://developer.github.com/v3/pulls/#update-a-pull-request -func (c *client) ClosePR(org, repo string, number int) error { - durationLogger := c.log("ClosePR", org, repo, number) - defer durationLogger() - - _, err := c.request(&request{ - method: http.MethodPatch, - path: fmt.Sprintf("/repos/%s/%s/pulls/%d", org, repo, number), - org: org, - requestBody: map[string]string{"state": "closed"}, - exitCodes: []int{200}, - }, nil) - return err -} - -// ReopenPR re-opens the existing, closed PR provided -// TODO: Rename to ReopenPullRequest -// -// See https://developer.github.com/v3/pulls/#update-a-pull-request -func (c *client) ReopenPR(org, repo string, number int) error { - durationLogger := c.log("ReopenPR", org, repo, number) - defer durationLogger() - - _, err := c.request(&request{ - method: http.MethodPatch, - path: fmt.Sprintf("/repos/%s/%s/pulls/%d", org, repo, number), - org: org, - requestBody: map[string]string{"state": "open"}, - exitCodes: []int{200}, - }, nil) - return stateCannotBeChangedOrOriginalError(err) -} - -// GetRef returns the SHA of the given ref, such as "heads/master". -// -// See https://developer.github.com/v3/git/refs/#get-a-reference -// The gitbub api does prefix matching and might return multiple results, -// in which case we will return a GetRefTooManyResultsError -func (c *client) GetRef(org, repo, ref string) (string, error) { - durationLogger := c.log("GetRef", org, repo, ref) - defer durationLogger() - - res := GetRefResponse{} - _, err := c.request(&request{ - method: http.MethodGet, - path: fmt.Sprintf("/repos/%s/%s/git/refs/%s", org, repo, ref), - org: org, - exitCodes: []int{200}, - }, &res) - if err != nil { - return "", nil - } - - if n := len(res); n > 1 { - wantRef := "refs/" + ref - for _, r := range res { - if r.Ref == wantRef { - return r.Object.SHA, nil - } - } - return "", GetRefTooManyResultsError{org: org, repo: repo, ref: ref, resultsRefs: res.RefNames()} - } - return res[0].Object.SHA, nil -} - -type GetRefTooManyResultsError struct { - org, repo, ref string - resultsRefs []string -} - -func (GetRefTooManyResultsError) Is(err error) bool { - _, ok := err.(GetRefTooManyResultsError) - return ok -} - -func (e GetRefTooManyResultsError) Error() string { - return fmt.Sprintf("query for %s/%s ref %q didn't match one but multiple refs: %v", e.org, e.repo, e.ref, e.resultsRefs) -} - -type GetRefResponse []GetRefResult - -// We need a custom unmarshaler because the GetRefResult may either be a -// single GetRefResponse or multiple -func (grr *GetRefResponse) UnmarshalJSON(data []byte) error { - result := &GetRefResult{} - if err := json.Unmarshal(data, result); err == nil { - *(grr) = GetRefResponse{*result} - return nil - } - var response []GetRefResult - if err := json.Unmarshal(data, &response); err != nil { - return fmt.Errorf("failed to unmarshal response %s: %w", string(data), err) - } - *grr = GetRefResponse(response) - return nil -} - -func (grr *GetRefResponse) RefNames() []string { - var result []string - for _, item := range *grr { - result = append(result, item.Ref) - } - return result -} - -type GetRefResult struct { - Ref string `json:"ref,omitempty"` - NodeID string `json:"node_id,omitempty"` - URL string `json:"url,omitempty"` - Object struct { - Type string `json:"type,omitempty"` - SHA string `json:"sha,omitempty"` - URL string `json:"url,omitempty"` - } `json:"object,omitempty"` -} - -// DeleteRef deletes the given ref -// -// See https://developer.github.com/v3/git/refs/#delete-a-reference -func (c *client) DeleteRef(org, repo, ref string) error { - durationLogger := c.log("DeleteRef", org, repo, ref) - defer durationLogger() - - _, err := c.request(&request{ - method: http.MethodDelete, - path: fmt.Sprintf("/repos/%s/%s/git/refs/%s", org, repo, ref), - org: org, - exitCodes: []int{204}, - }, nil) - return err -} - -// ListFileCommits returns the commits for this file path. -// -// See https://developer.github.com/v3/repos/#list-commits -func (c *client) ListFileCommits(org, repo, filePath string) ([]RepositoryCommit, error) { - durationLogger := c.log("ListFileCommits", org, repo, filePath) - defer durationLogger() - - var commits []RepositoryCommit - err := c.readPaginatedResultsWithValues( - fmt.Sprintf("/repos/%s/%s/commits", org, repo), - url.Values{ - "path": []string{filePath}, - "per_page": []string{"100"}, - }, - acceptNone, - org, - func() interface{} { // newObj - return &[]RepositoryCommit{} - }, - func(obj interface{}) { - commits = append(commits, *(obj.(*[]RepositoryCommit))...) - }, - ) - if err != nil { - return nil, err - } - return commits, nil -} - -// FindIssues uses the GitHub search API to find issues which match a particular query. -// -// Input query the same way you would into the website. -// Order returned results with sort (usually "updated"). -// Control whether oldest/newest is first with asc. -// This method is not working in contexts where "github-app-id" is set. Please use FindIssuesWithOrg() in those cases. -// -// See https://help.github.com/articles/searching-issues-and-pull-requests/ for details. -func (c *client) FindIssues(query, sort string, asc bool) ([]Issue, error) { - return c.FindIssuesWithOrg("", query, sort, asc) -} - -// FindIssuesWithOrg uses the GitHub search API to find issues which match a particular query. -// -// Input query the same way you would into the website. -// Order returned results with sort (usually "updated"). -// Control whether oldest/newest is first with asc. -// This method is supposed to be used in contexts where "github-app-id" is set. -// -// See https://help.github.com/articles/searching-issues-and-pull-requests/ for details. -func (c *client) FindIssuesWithOrg(org, query, sort string, asc bool) ([]Issue, error) { - loggerName := "FindIssuesWithOrg" - if org == "" { - loggerName = "FindIssues" - } - durationLogger := c.log(loggerName, query) - defer durationLogger() - - values := url.Values{ - "per_page": []string{"100"}, - "q": []string{query}, - } - var issues []Issue - - if sort != "" { - values["sort"] = []string{sort} - if asc { - values["order"] = []string{"asc"} - } - } - err := c.readPaginatedResultsWithValues( - fmt.Sprintf("/search/issues"), - values, - acceptNone, - org, - func() interface{} { // newObj - return &IssuesSearchResult{} - }, - func(obj interface{}) { - issues = append(issues, obj.(*IssuesSearchResult).Issues...) - }, - ) - if err != nil { - return nil, err - } - return issues, err -} - -// FileNotFound happens when github cannot find the file requested by GetFile(). -type FileNotFound struct { - org, repo, path, commit string -} - -func (e *FileNotFound) Error() string { - return fmt.Sprintf("%s/%s/%s @ %s not found", e.org, e.repo, e.path, e.commit) -} - -// GetFile uses GitHub repo contents API to retrieve the content of a file with commit SHA. -// If commit is empty, it will grab content from repo's default branch, usually master. -// Use GetDirectory() method to retrieve a directory. -// -// See https://developer.github.com/v3/repos/contents/#get-contents -func (c *client) GetFile(org, repo, filepath, commit string) ([]byte, error) { - durationLogger := c.log("GetFile", org, repo, filepath, commit) - defer durationLogger() - - path := fmt.Sprintf("/repos/%s/%s/contents/%s", org, repo, filepath) - if commit != "" { - path = fmt.Sprintf("%s?ref=%s", path, url.QueryEscape(commit)) - } - - var res Content - code, err := c.request(&request{ - method: http.MethodGet, - path: path, - org: org, - exitCodes: []int{200, 404}, - }, &res) - - if err != nil { - return nil, err - } - - if code == 404 { - return nil, &FileNotFound{ - org: org, - repo: repo, - path: filepath, - commit: commit, - } - } - - decoded, err := base64.StdEncoding.DecodeString(res.Content) - if err != nil { - return nil, fmt.Errorf("error decoding %s : %w", res.Content, err) - } - - return decoded, nil -} - -// QueryWithGitHubAppsSupport runs a GraphQL query using shurcooL/githubql's client. -func (c *client) QueryWithGitHubAppsSupport(ctx context.Context, q interface{}, vars map[string]interface{}, org string) error { - // Don't log query here because Query is typically called multiple times to get all pages. - // Instead log once per search and include total search cost. - return c.gqlc.QueryWithGitHubAppsSupport(ctx, q, vars, org) -} - -// MutateWithGitHubAppsSupport runs a GraphQL mutation using shurcooL/githubql's client. -func (c *client) MutateWithGitHubAppsSupport(ctx context.Context, m interface{}, input githubql.Input, vars map[string]interface{}, org string) error { - return c.gqlc.MutateWithGitHubAppsSupport(ctx, m, input, vars, org) -} - -// CreateTeam adds a team with name to the org, returning a struct with the new ID. -// -// See https://developer.github.com/v3/teams/#create-team -func (c *client) CreateTeam(org string, team Team) (*Team, error) { - durationLogger := c.log("CreateTeam", org, team) - defer durationLogger() - - if team.Name == "" { - return nil, errors.New("team.Name must be non-empty") - } - if c.fake { - return nil, nil - } else if c.dry { - // When in dry mode we need a believable slug to call corresponding methods for this team - team.Slug = strings.ToLower(strings.ReplaceAll(team.Name, " ", "-")) - return &team, nil - } - path := fmt.Sprintf("/orgs/%s/teams", org) - var retTeam Team - _, err := c.request(&request{ - method: http.MethodPost, - path: path, - accept: "application/vnd.github+json", - org: org, - requestBody: &team, - exitCodes: []int{201}, - }, &retTeam) - return &retTeam, err -} - -// EditTeam patches team.Slug to contain the specified: -// name, description, privacy, permission, and parentTeamId values. -// -// See https://docs.github.com/en/rest/reference/teams#update-a-team -func (c *client) EditTeam(org string, t Team) (*Team, error) { - durationLogger := c.log("EditTeam", t) - defer durationLogger() - - if t.Slug == "" { - return nil, errors.New("team.Slug must be populated") - } - if c.dry { - return &t, nil - } - t.ID = 0 - // Need to send parent_team_id: null - team := struct { - Team - ParentTeamID *int `json:"parent_team_id"` - }{ - Team: t, - ParentTeamID: t.ParentTeamID, - } - var retTeam Team - path := fmt.Sprintf("/orgs/%s/teams/%s", org, t.Slug) - _, err := c.request(&request{ - method: http.MethodPatch, - path: path, - accept: "application/vnd.github+json", - org: org, - requestBody: &team, - exitCodes: []int{200, 201}, - }, &retTeam) - return &retTeam, err -} - -// DeleteTeam removes team.ID from GitHub. -// -// See https://developer.github.com/v3/teams/#delete-team -// Deprecated: please use DeleteTeamBySlug -func (c *client) DeleteTeam(org string, id int) error { - c.logger.WithField("methodName", "DeleteTeam"). - Warn("method is deprecated, and will result in multiple api calls to achieve result") - durationLogger := c.log("DeleteTeam", org, id) - defer durationLogger() - - organization, err := c.GetOrg(org) - if err != nil { - return err - } - - path := fmt.Sprintf("/organizations/%d/team/%d", organization.Id, id) - _, err = c.request(&request{ - method: http.MethodDelete, - path: path, - org: org, - exitCodes: []int{204}, - }, nil) - return err -} - -// DeleteTeamBySlug removes team.Slug from GitHub. -// -// See https://docs.github.com/en/rest/reference/teams#delete-a-team -func (c *client) DeleteTeamBySlug(org, teamSlug string) error { - durationLogger := c.log("DeleteTeamBySlug", org, teamSlug) - defer durationLogger() - path := fmt.Sprintf("/orgs/%s/teams/%s", org, teamSlug) - _, err := c.request(&request{ - method: http.MethodDelete, - path: path, - org: org, - exitCodes: []int{204}, - }, nil) - return err -} - -// ListTeams gets a list of teams for the given org -// -// See https://developer.github.com/v3/teams/#list-teams -func (c *client) ListTeams(org string) ([]Team, error) { - durationLogger := c.log("ListTeams", org) - defer durationLogger() - - if c.fake { - return nil, nil - } - path := fmt.Sprintf("/orgs/%s/teams", org) - var teams []Team - err := c.readPaginatedResults( - path, - "application/vnd.github+json", - org, - func() interface{} { - return &[]Team{} - }, - func(obj interface{}) { - teams = append(teams, *(obj.(*[]Team))...) - }, - ) - if err != nil { - return nil, err - } - return teams, nil -} - -// UpdateTeamMembership adds the user to the team and/or updates their role in that team. -// -// If the user is not a member of the org, GitHub will invite them to become an outside collaborator, setting their status to pending. -// -// https://developer.github.com/v3/teams/members/#add-or-update-team-membership -// Deprecated: please use UpdateTeamMembershipBySlug -func (c *client) UpdateTeamMembership(org string, id int, user string, maintainer bool) (*TeamMembership, error) { - c.logger.WithField("methodName", "UpdateTeamMembership"). - Warn("method is deprecated, and will result in multiple api calls to achieve result") - durationLogger := c.log("UpdateTeamMembership", org, id, user, maintainer) - defer durationLogger() - - if c.fake { - return nil, nil - } - tm := TeamMembership{} - if maintainer { - tm.Role = RoleMaintainer - } else { - tm.Role = RoleMember - } - - if c.dry { - return &tm, nil - } - - organization, err := c.GetOrg(org) - if err != nil { - return nil, err - } - - _, err = c.request(&request{ - method: http.MethodPut, - path: fmt.Sprintf("/organizations/%d/team/%d/memberships/%s", organization.Id, id, user), - org: org, - requestBody: &tm, - exitCodes: []int{200}, - }, &tm) - return &tm, err -} - -// UpdateTeamMembershipBySlug adds the user to the team and/or updates their role in that team. -// -// If the user is not a member of the org, GitHub will invite them to become an outside collaborator, setting their status to pending. -// -// https://docs.github.com/en/rest/reference/teams#add-or-update-team-membership-for-a-user -func (c *client) UpdateTeamMembershipBySlug(org, teamSlug, user string, maintainer bool) (*TeamMembership, error) { - durationLogger := c.log("UpdateTeamMembershipBySlug", org, teamSlug, user, maintainer) - defer durationLogger() - - if c.fake { - return nil, nil - } - tm := TeamMembership{} - if maintainer { - tm.Role = RoleMaintainer - } else { - tm.Role = RoleMember - } - - if c.dry { - return &tm, nil - } - - _, err := c.request(&request{ - method: http.MethodPut, - path: fmt.Sprintf("/orgs/%s/teams/%s/memberships/%s", org, teamSlug, user), - org: org, - requestBody: &tm, - exitCodes: []int{200}, - }, &tm) - return &tm, err -} - -// RemoveTeamMembership removes the user from the team (but not the org). -// -// https://developer.github.com/v3/teams/members/#remove-team-member -// Deprecated: please use RemoveTeamMembershipBySlug -func (c *client) RemoveTeamMembership(org string, id int, user string) error { - c.logger.WithField("methodName", "RemoveTeamMembership"). - Warn("method is deprecated, and will result in multiple api calls to achieve result") - durationLogger := c.log("RemoveTeamMembership", org, id, user) - defer durationLogger() - - if c.fake { - return nil - } - - organization, err := c.GetOrg(org) - if err != nil { - return err - } - - _, err = c.request(&request{ - method: http.MethodDelete, - path: fmt.Sprintf("/organizations/%d/team/%d/memberships/%s", organization.Id, id, user), - org: org, - exitCodes: []int{204}, - }, nil) - return err -} - -// RemoveTeamMembershipBySlug removes the user from the team (but not the org). -// -// https://docs.github.com/en/rest/reference/teams#remove-team-membership-for-a-user -func (c *client) RemoveTeamMembershipBySlug(org, teamSlug, user string) error { - durationLogger := c.log("RemoveTeamMembershipBySlug", org, teamSlug, user) - defer durationLogger() - - if c.fake { - return nil - } - _, err := c.request(&request{ - method: http.MethodDelete, - path: fmt.Sprintf("/orgs/%s/teams/%s/memberships/%s", org, teamSlug, user), - org: org, - exitCodes: []int{204}, - }, nil) - return err -} - -// ListTeamMembers gets a list of team members for the given team id -// -// Role options are "all", "maintainer" and "member" -// -// https://developer.github.com/v3/teams/members/#list-team-members -// Deprecated: please use ListTeamMembersBySlug -func (c *client) ListTeamMembers(org string, id int, role string) ([]TeamMember, error) { - c.logger.WithField("methodName", "ListTeamMembers"). - Warn("method is deprecated, please use ListTeamMembersBySlug") - durationLogger := c.log("ListTeamMembers", id, role) - defer durationLogger() - - if c.fake { - return nil, nil - } - path := fmt.Sprintf("/teams/%d/members", id) - var teamMembers []TeamMember - err := c.readPaginatedResultsWithValues( - path, - url.Values{ - "per_page": []string{"100"}, - "role": []string{role}, - }, - "application/vnd.github+json", - org, - func() interface{} { - return &[]TeamMember{} - }, - func(obj interface{}) { - teamMembers = append(teamMembers, *(obj.(*[]TeamMember))...) - }, - ) - if err != nil { - return nil, err - } - return teamMembers, nil -} - -// ListTeamMembersBySlug gets a list of team members for the given team slug -// -// Role options are "all", "maintainer" and "member" -// -// https://docs.github.com/en/rest/reference/teams#list-team-members -func (c *client) ListTeamMembersBySlug(org, teamSlug, role string) ([]TeamMember, error) { - durationLogger := c.log("ListTeamMembersBySlug", org, teamSlug, role) - defer durationLogger() - - if c.fake { - return nil, nil - } - path := fmt.Sprintf("/orgs/%s/teams/%s/members", org, teamSlug) - var teamMembers []TeamMember - err := c.readPaginatedResultsWithValues( - path, - url.Values{ - "per_page": []string{"100"}, - "role": []string{role}, - }, - "application/vnd.github.v3+json", - org, - func() interface{} { - return &[]TeamMember{} - }, - func(obj interface{}) { - teamMembers = append(teamMembers, *(obj.(*[]TeamMember))...) - }, - ) - if err != nil { - return nil, err - } - return teamMembers, nil -} - -// ListTeamRepos gets a list of team repos for the given team id -// -// https://developer.github.com/v3/teams/#list-team-repos -// Deprecated: please use ListTeamReposBySlug -func (c *client) ListTeamRepos(org string, id int) ([]Repo, error) { - c.logger.WithField("methodName", "ListTeamRepos"). - Warn("method is deprecated, and will result in multiple api calls to achieve result") - durationLogger := c.log("ListTeamRepos", org, id) - defer durationLogger() - - if c.fake { - return nil, nil - } - - organization, err := c.GetOrg(org) - if err != nil { - return nil, err - } - - path := fmt.Sprintf("/organizations/%d/team/%d/repos", organization.Id, id) - var repos []Repo - err = c.readPaginatedResultsWithValues( - path, - url.Values{ - "per_page": []string{"100"}, - }, - "application/vnd.github+json", - org, - func() interface{} { - return &[]Repo{} - }, - func(obj interface{}) { - for _, repo := range *obj.(*[]Repo) { - // Currently, GitHub API returns false for all permission levels - // for a repo on which the team has 'Maintain' or 'Triage' role. - // This check is to avoid listing a repo under the team but - // showing the permission level as none. - if LevelFromPermissions(repo.Permissions) != None { - repos = append(repos, repo) - } - } - }, - ) - if err != nil { - return nil, err - } - return repos, nil -} - -// ListTeamReposBySlug gets a list of team repos for the given team slug -// -// https://docs.github.com/en/rest/reference/teams#list-team-repositories -func (c *client) ListTeamReposBySlug(org, teamSlug string) ([]Repo, error) { - durationLogger := c.log("ListTeamReposBySlug", org, teamSlug) - defer durationLogger() - - if c.fake { - return nil, nil - } - path := fmt.Sprintf("/orgs/%s/teams/%s/repos", org, teamSlug) - var repos []Repo - err := c.readPaginatedResultsWithValues( - path, - url.Values{ - "per_page": []string{"100"}, - }, - "application/vnd.github.v3+json", - org, - func() interface{} { - return &[]Repo{} - }, - func(obj interface{}) { - for _, repo := range *obj.(*[]Repo) { - // Currently, GitHub API returns false for all permission levels - // for a repo on which the team has 'Maintain' or 'Triage' role. - // This check is to avoid listing a repo under the team but - // showing the permission level as none. - if LevelFromPermissions(repo.Permissions) != None { - repos = append(repos, repo) - } - } - }, - ) - if err != nil { - return nil, err - } - return repos, nil -} - -// UpdateTeamRepo adds the repo to the team with the provided role. -// -// https://developer.github.com/v3/teams/#add-or-update-team-repository -// Deprecated: please use UpdateTeamRepoBySlug -func (c *client) UpdateTeamRepo(id int, org, repo string, permission TeamPermission) error { - c.logger.WithField("methodName", "UpdateTeamRepo"). - Warn("method is deprecated, and will result in multiple api calls to achieve result") - durationLogger := c.log("UpdateTeamRepo", id, org, repo, permission) - defer durationLogger() - - if c.fake || c.dry { - return nil - } - - organization, err := c.GetOrg(org) - if err != nil { - return err - } - - data := struct { - Permission string `json:"permission"` - }{ - Permission: string(permission), - } - - _, err = c.request(&request{ - method: http.MethodPut, - path: fmt.Sprintf("/organizations/%d/team/%d/repos/%s/%s", organization.Id, id, org, repo), - org: org, - requestBody: &data, - exitCodes: []int{204}, - }, nil) - return err -} - -// UpdateTeamRepoBySlug adds the repo to the team with the provided role. -// -// https://docs.github.com/en/rest/reference/teams#add-or-update-team-repository-permissions -func (c *client) UpdateTeamRepoBySlug(org, teamSlug, repo string, permission TeamPermission) error { - durationLogger := c.log("UpdateTeamRepoBySlug", org, teamSlug, repo, permission) - defer durationLogger() - - if c.fake || c.dry { - return nil - } - - data := struct { - Permission string `json:"permission"` - }{ - Permission: string(permission), - } - - _, err := c.request(&request{ - method: http.MethodPut, - path: fmt.Sprintf("/orgs/%s/teams/%s/repos/%s/%s", org, teamSlug, org, repo), - org: org, - requestBody: &data, - exitCodes: []int{204}, - }, nil) - return err -} - -// RemoveTeamRepo removes the team from the repo. -// -// https://docs.github.com/en/rest/reference/teams#remove-a-repository-from-a-team-legacy -// Deprecated: please use RemoveTeamRepoBySlug -func (c *client) RemoveTeamRepo(id int, org, repo string) error { - c.logger.WithField("methodName", "RemoveTeamRepo"). - Warn("method is deprecated, and will result in multiple api calls to achieve result") - durationLogger := c.log("RemoveTeamRepo", id, org, repo) - defer durationLogger() - - if c.fake || c.dry { - return nil - } - - organization, err := c.GetOrg(org) - if err != nil { - return err - } - - _, err = c.request(&request{ - method: http.MethodDelete, - path: fmt.Sprintf("/organizations/%d/team/%d/repos/%s/%s", organization.Id, id, org, repo), - org: org, - exitCodes: []int{204}, - }, nil) - return err -} - -// RemoveTeamRepoBySlug removes the team from the repo. -// -// https://docs.github.com/en/rest/reference/teams#remove-a-repository-from-a-team -func (c *client) RemoveTeamRepoBySlug(org, teamSlug, repo string) error { - durationLogger := c.log("RemoveTeamRepoBySlug", org, teamSlug, repo) - defer durationLogger() - - if c.fake || c.dry { - return nil - } - - _, err := c.request(&request{ - method: http.MethodDelete, - path: fmt.Sprintf("/orgs/%s/teams/%s/repos/%s/%s", org, teamSlug, org, repo), - org: org, - exitCodes: []int{204}, - }, nil) - return err -} - -// ListTeamInvitations gets a list of team members with pending invitations for the -// given team id -// -// https://developer.github.com/v3/teams/members/#list-pending-team-invitations -// Deprecated: please use ListTeamInvitationsBySlug -func (c *client) ListTeamInvitations(org string, id int) ([]OrgInvitation, error) { - c.logger.WithField("methodName", "ListTeamInvitations"). - Warn("method is deprecated, and will result in multiple api calls to achieve result") - durationLogger := c.log("ListTeamInvitations", org, id) - defer durationLogger() - - if c.fake { - return nil, nil - } - - organization, err := c.GetOrg(org) - if err != nil { - return nil, err - } - path := fmt.Sprintf("/organizations/%d/team/%d/invitations", organization.Id, id) - var ret []OrgInvitation - err = c.readPaginatedResults( - path, - acceptNone, - org, - func() interface{} { - return &[]OrgInvitation{} - }, - func(obj interface{}) { - ret = append(ret, *(obj.(*[]OrgInvitation))...) - }, - ) - if err != nil { - return nil, err - } - return ret, nil -} - -// ListTeamInvitationsBySlug gets a list of team members with pending invitations for the given team slug -// -// https://docs.github.com/en/rest/reference/teams#list-pending-team-invitations -func (c *client) ListTeamInvitationsBySlug(org, teamSlug string) ([]OrgInvitation, error) { - durationLogger := c.log("ListTeamInvitationsBySlug", org, teamSlug) - defer durationLogger() - - if c.fake { - return nil, nil - } - - path := fmt.Sprintf("/orgs/%s/teams/%s/invitations", org, teamSlug) - var ret []OrgInvitation - err := c.readPaginatedResults( - path, - "application/vnd.github.v3+json", - org, - func() interface{} { - return &[]OrgInvitation{} - }, - func(obj interface{}) { - ret = append(ret, *(obj.(*[]OrgInvitation))...) - }, - ) - if err != nil { - return nil, err - } - return ret, nil -} - -// MergeDetails contains desired properties of the merge. -// -// See https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button -type MergeDetails struct { - // CommitTitle defaults to the automatic message. - CommitTitle string `json:"commit_title,omitempty"` - // CommitMessage defaults to the automatic message. - CommitMessage string `json:"commit_message,omitempty"` - // The PR HEAD must match this to prevent races. - SHA string `json:"sha,omitempty"` - // Can be "merge", "squash", or "rebase". Defaults to merge. - MergeMethod string `json:"merge_method,omitempty"` -} - -// ModifiedHeadError happens when github refuses to merge a PR because the PR changed. -type ModifiedHeadError string - -func (e ModifiedHeadError) Error() string { return string(e) } - -// UnmergablePRError happens when github refuses to merge a PR for other reasons (merge confclit). -type UnmergablePRError string - -func (e UnmergablePRError) Error() string { return string(e) } - -// UnmergablePRBaseChangedError happens when github refuses merging a PR because the base changed. -type UnmergablePRBaseChangedError string - -func (e UnmergablePRBaseChangedError) Error() string { return string(e) } - -// UnauthorizedToPushError happens when client is not allowed to push to github. -type UnauthorizedToPushError string - -func (e UnauthorizedToPushError) Error() string { return string(e) } - -// MergeCommitsForbiddenError happens when the repo disallows the merge strategy configured for the repo in Tide. -type MergeCommitsForbiddenError string - -func (e MergeCommitsForbiddenError) Error() string { return string(e) } - -// Merge merges a PR. -// -// See https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button -func (c *client) Merge(org, repo string, pr int, details MergeDetails) error { - durationLogger := c.log("Merge", org, repo, pr, details) - defer durationLogger() - - ge := githubError{} - ec, err := c.request(&request{ - method: http.MethodPut, - path: fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", org, repo, pr), - org: org, - requestBody: &details, - exitCodes: []int{200, 405, 409}, - }, &ge) - if err != nil { - return err - } - if ec == 405 { - if strings.Contains(ge.Message, "Base branch was modified") { - return UnmergablePRBaseChangedError(ge.Message) - } - if strings.Contains(ge.Message, "You're not authorized to push to this branch") { - return UnauthorizedToPushError(ge.Message) - } - if strings.Contains(ge.Message, "Merge commits are not allowed on this repository") { - return MergeCommitsForbiddenError(ge.Message) - } - return UnmergablePRError(ge.Message) - } else if ec == 409 { - return ModifiedHeadError(ge.Message) - } - - return nil -} - -// IsCollaborator returns whether or not the user is a collaborator of the repo. -// From GitHub's API reference: -// For organization-owned repositories, the list of collaborators includes -// outside collaborators, organization members that are direct collaborators, -// organization members with access through team memberships, organization -// members with access through default organization permissions, and -// organization owners. -// -// See https://developer.github.com/v3/repos/collaborators/ -func (c *client) IsCollaborator(org, repo, user string) (bool, error) { - // This call does not support etags and is therefore not cacheable today - // by ghproxy. If we can detect that we're using ghproxy, however, we can - // make a more expensive but cache-able call instead. Detecting that we - // are pointed at a ghproxy instance is not high fidelity, but a best-effort - // approach here is guaranteed to make a positive impact and no negative one. - if strings.Contains(c.bases[0], "ghproxy") { - users, err := c.ListCollaborators(org, repo) - if err != nil { - return false, err - } - for _, u := range users { - if NormLogin(u.Login) == NormLogin(user) { - return true, nil - } - } - return false, nil - } - durationLogger := c.log("IsCollaborator", org, user) - defer durationLogger() - - if org == user { - // Make it possible to run a couple of plugins on personal repos. - return true, nil - } - code, err := c.request(&request{ - method: http.MethodGet, - accept: "application/vnd.github+json", - path: fmt.Sprintf("/repos/%s/%s/collaborators/%s", org, repo, user), - org: org, - exitCodes: []int{204, 404, 302}, - }, nil) - if err != nil { - return false, err - } - if code == 204 { - return true, nil - } else if code == 404 { - return false, nil - } - return false, fmt.Errorf("unexpected status: %d", code) -} - -// ListCollaborators gets a list of all users who have access to a repo (and -// can become assignees or requested reviewers). -// -// See 'IsCollaborator' for more details. -// See https://developer.github.com/v3/repos/collaborators/ -func (c *client) ListCollaborators(org, repo string) ([]User, error) { - durationLogger := c.log("ListCollaborators", org, repo) - defer durationLogger() - - if c.fake { - return nil, nil - } - path := fmt.Sprintf("/repos/%s/%s/collaborators", org, repo) - var users []User - err := c.readPaginatedResults( - path, - "application/vnd.github+json", - org, - func() interface{} { - return &[]User{} - }, - func(obj interface{}) { - users = append(users, *(obj.(*[]User))...) - }, - ) - if err != nil { - return nil, err - } - return users, nil -} - -// CreateFork creates a fork for the authenticated user. Forking a repository -// happens asynchronously. Therefore, we may have to wait a short period before -// accessing the git objects. If this takes longer than 5 minutes, GitHub -// recommends contacting their support. -// -// See https://developer.github.com/v3/repos/forks/#create-a-fork -func (c *client) CreateFork(owner, repo string) (string, error) { - durationLogger := c.log("CreateFork", owner, repo) - defer durationLogger() - - resp := struct { - Name string `json:"name"` - }{} - - _, err := c.request(&request{ - method: http.MethodPost, - path: fmt.Sprintf("/repos/%s/%s/forks", owner, repo), - org: owner, - exitCodes: []int{202}, - }, &resp) - - // there are many reasons why GitHub may end up forking the - // repo under a different name -- the repo got re-named, the - // bot account already has a fork with that name, etc - return resp.Name, err -} - -// EnsureFork checks to see that there is a fork of org/repo in the forkedUsers repositories. -// If there is not, it makes one, and waits for the fork to be created before returning. -// The return value is the name of the repo that was created -// (This may be different then the one that is forked due to naming conflict) -func (c *client) EnsureFork(forkingUser, org, repo string) (string, error) { - // Fork repo if it doesn't exist. - fork := forkingUser + "/" + repo - repos, err := c.GetRepos(forkingUser, true) - if err != nil { - return repo, fmt.Errorf("could not fetch all existing repos: %w", err) - } - // if the repo does not exist, or it does, but is not a fork of the repo we want - if forkedRepo := getFork(fork, repos); forkedRepo == nil || forkedRepo.Parent.FullName != fmt.Sprintf("%s/%s", org, repo) { - if name, err := c.CreateFork(org, repo); err != nil { - return repo, fmt.Errorf("cannot fork %s/%s: %w", org, repo, err) - } else { - // we got a fork but it may be named differently - repo = name - } - if err := c.waitForRepo(forkingUser, repo); err != nil { - return repo, fmt.Errorf("fork of %s/%s cannot show up on GitHub: %w", org, repo, err) - } - } - return repo, nil - -} - -func (c *client) waitForRepo(owner, name string) error { - // Wait for at most 5 minutes for the fork to appear on GitHub. - // The documentation instructs us to contact support if this - // takes longer than five minutes. - after := time.After(6 * time.Minute) - tick := time.Tick(30 * time.Second) - - var ghErr string - for { - select { - case <-tick: - repo, err := c.GetRepo(owner, name) - if err != nil { - ghErr = fmt.Sprintf(": %v", err) - logrus.WithError(err).Warn("Error getting bot repository.") - continue - } - ghErr = "" - if forkedRepo := getFork(owner+"/"+name, []Repo{repo.Repo}); forkedRepo != nil { - return nil - } - case <-after: - return fmt.Errorf("timed out waiting for %s to appear on GitHub%s", owner+"/"+name, ghErr) - } - } -} - -func getFork(repo string, repos []Repo) *Repo { - for _, r := range repos { - if !r.Fork { - continue - } - if r.FullName == repo { - return &r - } - } - return nil -} - -// ListRepoTeams gets a list of all the teams with access to a repository -// See https://developer.github.com/v3/repos/#list-teams -func (c *client) ListRepoTeams(org, repo string) ([]Team, error) { - durationLogger := c.log("ListRepoTeams", org, repo) - defer durationLogger() - - if c.fake { - return nil, nil - } - path := fmt.Sprintf("/repos/%s/%s/teams", org, repo) - var teams []Team - err := c.readPaginatedResults( - path, - acceptNone, - org, - func() interface{} { - return &[]Team{} - }, - func(obj interface{}) { - teams = append(teams, *(obj.(*[]Team))...) - }, - ) - if err != nil { - return nil, err - } - return teams, nil -} - -// ListIssueEvents gets a list events from GitHub's events API that pertain to the specified issue. -// The events that are returned have a different format than webhook events and certain event types -// are excluded. -// -// See https://developer.github.com/v3/issues/events/ -func (c *client) ListIssueEvents(org, repo string, num int) ([]ListedIssueEvent, error) { - durationLogger := c.log("ListIssueEvents", org, repo, num) - defer durationLogger() - - if c.fake { - return nil, nil - } - path := fmt.Sprintf("/repos/%s/%s/issues/%d/events", org, repo, num) - var events []ListedIssueEvent - err := c.readPaginatedResults( - path, - acceptNone, - org, - func() interface{} { - return &[]ListedIssueEvent{} - }, - func(obj interface{}) { - events = append(events, *(obj.(*[]ListedIssueEvent))...) - }, - ) - if err != nil { - return nil, err - } - return events, nil -} - -// IsMergeable determines if a PR can be merged. -// Mergeability is calculated by a background job on GitHub and is not immediately available when -// new commits are added so the PR must be polled until the background job completes. -func (c *client) IsMergeable(org, repo string, number int, SHA string) (bool, error) { - backoff := time.Second * 3 - maxTries := 3 - for try := 0; try < maxTries; try++ { - pr, err := c.GetPullRequest(org, repo, number) - if err != nil { - return false, err - } - if pr.Head.SHA != SHA { - return false, fmt.Errorf("pull request head changed while checking mergeability (%s -> %s)", SHA, pr.Head.SHA) - } - if pr.Merged { - return false, errors.New("pull request was merged while checking mergeability") - } - if pr.Mergable != nil { - return *pr.Mergable, nil - } - if try+1 < maxTries { - c.time.Sleep(backoff) - backoff *= 2 - } - } - return false, fmt.Errorf("reached maximum number of retries (%d) checking mergeability", maxTries) -} - -// ClearMilestone clears the milestone from the specified issue -// -// See https://developer.github.com/v3/issues/#edit-an-issue -func (c *client) ClearMilestone(org, repo string, num int) error { - durationLogger := c.log("ClearMilestone", org, repo, num) - defer durationLogger() - - issue := &struct { - // Clearing the milestone requires providing a null value, and - // interface{} will serialize to null. - Milestone interface{} `json:"milestone"` - }{} - _, err := c.request(&request{ - method: http.MethodPatch, - path: fmt.Sprintf("/repos/%v/%v/issues/%d", org, repo, num), - org: org, - requestBody: &issue, - exitCodes: []int{200}, - }, nil) - return err -} - -// SetMilestone sets the milestone from the specified issue (if it is a valid milestone) -// -// See https://developer.github.com/v3/issues/#edit-an-issue -func (c *client) SetMilestone(org, repo string, issueNum, milestoneNum int) error { - durationLogger := c.log("SetMilestone", org, repo, issueNum, milestoneNum) - defer durationLogger() - - issue := &struct { - Milestone int `json:"milestone"` - }{Milestone: milestoneNum} - - _, err := c.request(&request{ - method: http.MethodPatch, - path: fmt.Sprintf("/repos/%v/%v/issues/%d", org, repo, issueNum), - org: org, - requestBody: &issue, - exitCodes: []int{200}, - }, nil) - return err -} - -// ListMilestones list all milestones in a repo -// -// See https://developer.github.com/v3/issues/milestones/#list-milestones-for-a-repository/ -func (c *client) ListMilestones(org, repo string) ([]Milestone, error) { - durationLogger := c.log("ListMilestones", org) - defer durationLogger() - - if c.fake { - return nil, nil - } - path := fmt.Sprintf("/repos/%s/%s/milestones", org, repo) - var milestones []Milestone - err := c.readPaginatedResults( - path, - acceptNone, - org, - func() interface{} { - return &[]Milestone{} - }, - func(obj interface{}) { - milestones = append(milestones, *(obj.(*[]Milestone))...) - }, - ) - if err != nil { - return nil, err - } - return milestones, nil -} - -// ListPRCommits lists the commits in a pull request. -// -// GitHub API docs: https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request -func (c *client) ListPRCommits(org, repo string, number int) ([]RepositoryCommit, error) { - durationLogger := c.log("ListPRCommits", org, repo, number) - defer durationLogger() - - if c.fake { - return nil, nil - } - var commits []RepositoryCommit - err := c.readPaginatedResults( - fmt.Sprintf("/repos/%v/%v/pulls/%d/commits", org, repo, number), - acceptNone, - org, - func() interface{} { // newObj returns a pointer to the type of object to create - return &[]RepositoryCommit{} - }, - func(obj interface{}) { // accumulate is the accumulation function for paginated results - commits = append(commits, *(obj.(*[]RepositoryCommit))...) - }, - ) - if err != nil { - return nil, err - } - return commits, nil -} - -// UpdatePullRequestBranch updates the pull request branch with the latest upstream changes by merging HEAD from the base branch into the pull request branch. -// -// GitHub API docs: https://developer.github.com/v3/pulls#update-a-pull-request-branch -func (c *client) UpdatePullRequestBranch(org, repo string, number int, expectedHeadSha *string) error { - durationLogger := c.log("UpdatePullRequestBranch", org, repo) - defer durationLogger() - - data := struct { - // The expected SHA of the pull request's HEAD ref. This is the most recent commit on the pull request's branch. - // If the expected SHA does not match the pull request's HEAD, you will receive a 422 Unprocessable Entity status. - // You can use the "List commits" endpoint to find the most recent commit SHA. Default: SHA of the pull request's current HEAD ref. - ExpectedHeadSha *string `json:"expected_head_sha,omitempty"` - }{ - ExpectedHeadSha: expectedHeadSha, - } - - code, err := c.request(&request{ - method: http.MethodPut, - path: fmt.Sprintf("/repos/%s/%s/pulls/%d/update-branch", org, repo, number), - accept: "application/vnd.github.lydian-preview+json", - org: org, - requestBody: &data, - exitCodes: []int{202, 422}, - }, nil) - if err != nil { - return err - } - - if code == http.StatusUnprocessableEntity { - msg := "mismatch expected head sha" - if expectedHeadSha != nil { - msg = fmt.Sprintf("%s: %s", msg, *expectedHeadSha) - } - return errors.New(msg) - } - - return nil -} - -// newReloadingTokenSource creates a reloadingTokenSource. -func newReloadingTokenSource(getToken func() []byte) *reloadingTokenSource { - return &reloadingTokenSource{ - getToken: getToken, - } -} - -// Token is an implementation for oauth2.TokenSource interface. -func (s *reloadingTokenSource) Token() (*oauth2.Token, error) { - return &oauth2.Token{ - AccessToken: string(s.getToken()), - }, nil -} - -// GetRepoProjects returns the list of projects in this repo. -// -// See https://developer.github.com/v3/projects/#list-repository-projects -func (c *client) GetRepoProjects(owner, repo string) ([]Project, error) { - durationLogger := c.log("GetOrgProjects", owner, repo) - defer durationLogger() - - path := fmt.Sprintf("/repos/%s/%s/projects", owner, repo) - var projects []Project - err := c.readPaginatedResults( - path, - "application/vnd.github.inertia-preview+json", - owner, - func() interface{} { - return &[]Project{} - }, - func(obj interface{}) { - projects = append(projects, *(obj.(*[]Project))...) - }, - ) - if err != nil { - return nil, err - } - return projects, nil -} - -// GetOrgProjects returns the list of projects in this org. -// -// See https://developer.github.com/v3/projects/#list-organization-projects -func (c *client) GetOrgProjects(org string) ([]Project, error) { - durationLogger := c.log("GetOrgProjects", org) - defer durationLogger() - - path := fmt.Sprintf("/orgs/%s/projects", org) - var projects []Project - err := c.readPaginatedResults( - path, - "application/vnd.github.inertia-preview+json", - org, - func() interface{} { - return &[]Project{} - }, - func(obj interface{}) { - projects = append(projects, *(obj.(*[]Project))...) - }, - ) - if err != nil { - return nil, err - } - return projects, nil -} - -// GetProjectColumns returns the list of columns in a project. -// -// See https://developer.github.com/v3/projects/columns/#list-project-columns -func (c *client) GetProjectColumns(org string, projectID int) ([]ProjectColumn, error) { - durationLogger := c.log("GetProjectColumns", projectID) - defer durationLogger() - - path := fmt.Sprintf("/projects/%d/columns", projectID) - var projectColumns []ProjectColumn - err := c.readPaginatedResults( - path, - "application/vnd.github.inertia-preview+json", - org, - func() interface{} { - return &[]ProjectColumn{} - }, - func(obj interface{}) { - projectColumns = append(projectColumns, *(obj.(*[]ProjectColumn))...) - }, - ) - if err != nil { - return nil, err - } - return projectColumns, nil -} - -// CreateProjectCard adds a project card to the specified project column. -// -// See https://developer.github.com/v3/projects/cards/#create-a-project-card -func (c *client) CreateProjectCard(org string, columnID int, projectCard ProjectCard) (*ProjectCard, error) { - durationLogger := c.log("CreateProjectCard", columnID, projectCard) - defer durationLogger() - - if (projectCard.ContentType != "Issue") && (projectCard.ContentType != "PullRequest") { - return nil, errors.New("projectCard.ContentType must be either Issue or PullRequest") - } - if c.dry { - return &projectCard, nil - } - path := fmt.Sprintf("/projects/columns/%d/cards", columnID) - var retProjectCard ProjectCard - _, err := c.request(&request{ - method: http.MethodPost, - path: path, - accept: "application/vnd.github.inertia-preview+json", - org: org, - requestBody: &projectCard, - exitCodes: []int{200}, - }, &retProjectCard) - return &retProjectCard, err -} - -// GetProjectColumnCards get all project cards in a column. This helps in iterating all -// issues and PRs that are under a column -func (c *client) GetColumnProjectCards(org string, columnID int) ([]ProjectCard, error) { - durationLogger := c.log("GetColumnProjectCards", columnID) - defer durationLogger() - - if c.fake { - return nil, nil - } - path := fmt.Sprintf("/projects/columns/%d/cards", columnID) - var cards []ProjectCard - err := c.readPaginatedResults( - path, - // projects api requies the accept header to be set this way - "application/vnd.github.inertia-preview+json", - org, - func() interface{} { - return &[]ProjectCard{} - }, - func(obj interface{}) { - cards = append(cards, *(obj.(*[]ProjectCard))...) - }, - ) - return cards, err -} - -// GetColumnProjectCard of a specific issue or PR for a specific column in a board/project -// This method requires the URL of the issue/pr to compare the issue with the content_url -// field of the card. See https://developer.github.com/v3/projects/cards/#list-project-cards -func (c *client) GetColumnProjectCard(org string, columnID int, issueURL string) (*ProjectCard, error) { - cards, err := c.GetColumnProjectCards(org, columnID) - if err != nil { - return nil, err - } - - for _, card := range cards { - if card.ContentURL == issueURL { - return &card, nil - } - } - return nil, nil -} - -// MoveProjectCard moves a specific project card to a specified column in the same project -// -// See https://developer.github.com/v3/projects/cards/#move-a-project-card -func (c *client) MoveProjectCard(org string, projectCardID int, newColumnID int) error { - durationLogger := c.log("MoveProjectCard", projectCardID, newColumnID) - defer durationLogger() - - reqParams := struct { - Position string `json:"position"` - ColumnID int `json:"column_id"` - }{"top", newColumnID} - - _, err := c.request(&request{ - method: http.MethodPost, - path: fmt.Sprintf("/projects/columns/cards/%d/moves", projectCardID), - accept: "application/vnd.github.inertia-preview+json", - org: org, - requestBody: reqParams, - exitCodes: []int{201}, - }, nil) - return err -} - -// DeleteProjectCard deletes the project card of a specific issue or PR -// -// See https://developer.github.com/v3/projects/cards/#delete-a-project-card -func (c *client) DeleteProjectCard(org string, projectCardID int) error { - durationLogger := c.log("DeleteProjectCard", projectCardID) - defer durationLogger() - - _, err := c.request(&request{ - method: http.MethodDelete, - accept: "application/vnd.github+json", - path: fmt.Sprintf("/projects/columns/cards/:%d", projectCardID), - org: org, - exitCodes: []int{204}, - }, nil) - return err -} - -// TeamHasMember checks if a user belongs to a team -// Deprecated: use TeamBySlugHasMember -func (c *client) TeamHasMember(org string, teamID int, memberLogin string) (bool, error) { - durationLogger := c.log("TeamHasMember", teamID, memberLogin) - defer durationLogger() - - projectMaintainers, err := c.ListTeamMembers(org, teamID, RoleAll) - if err != nil { - return false, err - } - for _, person := range projectMaintainers { - if NormLogin(person.Login) == NormLogin(memberLogin) { - return true, nil - } - } - return false, nil -} - -func (c *client) TeamBySlugHasMember(org string, teamSlug string, memberLogin string) (bool, error) { - durationLogger := c.log("TeamBySlugHasMember", teamSlug, org) - defer durationLogger() - - if c.fake { - return false, nil - } - exitCode, err := c.request(&request{ - method: http.MethodGet, - path: fmt.Sprintf("/orgs/%s/teams/%s/memberships/%s", org, teamSlug, memberLogin), - org: org, - exitCodes: []int{200, 404}, - }, nil) - return exitCode == 200, err -} - -// GetTeamBySlug returns information about that team -// -// See https://developer.github.com/v3/teams/#get-team-by-name -func (c *client) GetTeamBySlug(slug string, org string) (*Team, error) { - durationLogger := c.log("GetTeamBySlug", slug, org) - defer durationLogger() - - if c.fake { - return &Team{}, nil - } - var team Team - _, err := c.request(&request{ - method: http.MethodGet, - path: fmt.Sprintf("/orgs/%s/teams/%s", org, slug), - org: org, - exitCodes: []int{200}, - }, &team) - if err != nil { - return nil, err - } - return &team, err -} - -// ListCheckRuns lists all checkruns for the given ref -// -// See https://docs.github.com/en/rest/checks/runs#list-check-runs-for-a-git-reference -func (c *client) ListCheckRuns(org, repo, ref string) (*CheckRunList, error) { - durationLogger := c.log("ListCheckRuns", org, repo, ref) - defer durationLogger() - - var checkRunList CheckRunList - if err := c.readPaginatedResults( - fmt.Sprintf("/repos/%s/%s/commits/%s/check-runs", org, repo, ref), - "", - org, - func() interface{} { - return &CheckRunList{} - }, - func(obj interface{}) { - cr := *(obj.(*CheckRunList)) - cr.CheckRuns = append(checkRunList.CheckRuns, cr.CheckRuns...) - checkRunList = cr - }, - ); err != nil { - return nil, err - } - return &checkRunList, nil -} - -// CreateCheckRun Creates a new check run for a specific commit in a repository. -// -// See https://docs.github.com/en/rest/checks/runs#create-a-check-run -func (c *client) CreateCheckRun(org, repo string, checkRun CheckRun) error { - durationLogger := c.log("CreateCheckRun", org, repo, checkRun) - defer durationLogger() - _, err := c.request(&request{ - method: http.MethodPost, - path: fmt.Sprintf("/repos/%s/%s/check-runs", org, repo), - org: org, - requestBody: &checkRun, - exitCodes: []int{201}, - }, nil) - if err != nil { - return err - } - return nil -} - -// Simple function to check if GitHub App Authentication is being used -func (c *client) UsesAppAuth() bool { - return c.delegate.usesAppsAuth -} - -// ListAppInstallations lists the installations for the current app. Will not work with -// a Personal Access Token. -// -// See https://docs.github.com/en/free-pro-team@latest/rest/reference/apps#list-installations-for-the-authenticated-app -func (c *client) ListAppInstallations() ([]AppInstallation, error) { - durationLogger := c.log("AppInstallation") - defer durationLogger() - - var ais []AppInstallation - if err := c.readPaginatedResults( - "/app/installations", - acceptNone, - "", - func() interface{} { - return &[]AppInstallation{} - }, - func(obj interface{}) { - ais = append(ais, *(obj.(*[]AppInstallation))...) - }, - ); err != nil { - return nil, err - } - return ais, nil -} - -// IsAppInstalled returns true if there is an app installation for the provided org and repo -// Will not work with a Personal Access Token. -// -// See https://docs.github.com/en/rest/apps/apps#get-a-repository-installation-for-the-authenticated-app -func (c *client) IsAppInstalled(org, repo string) (bool, error) { - durationLogger := c.log("IsAppInstalled", org, repo) - defer durationLogger() - - if c.dry { - return false, fmt.Errorf("not getting AppInstallation in dry-run mode") - } - if !c.usesAppsAuth { - return false, fmt.Errorf("IsAppInstalled was called when not using appsAuth") - } - - code, err := c.request(&request{ - method: http.MethodGet, - path: fmt.Sprintf("/repos/%s/%s/installation", org, repo), - org: org, - exitCodes: []int{200, 404}, - }, nil) - if err != nil { - return false, err - } - - return code == 200, nil -} - -// ListAppInstallationsForOrg lists the installations for an organisation. -// The requestor must be an organization owner with admin:read scope -// -// See https://docs.github.com/en/rest/orgs/orgs#list-app-installations-for-an-organization -func (c *client) ListAppInstallationsForOrg(org string) ([]AppInstallation, error) { - durationLogger := c.log("AppInstallationForOrg") - defer durationLogger() - - var ais []AppInstallation - if err := c.readPaginatedResults( - fmt.Sprintf("/orgs/%s/installations", org), - acceptNone, - org, - func() interface{} { - return &AppInstallationList{} - }, - func(obj interface{}) { - ais = append(ais, obj.(*AppInstallationList).Installations...) - }, - ); err != nil { - return nil, err - } - return ais, nil -} - -func (c *client) getAppInstallationToken(installationId int64) (*AppInstallationToken, error) { - durationLogger := c.log("AppInstallationToken") - defer durationLogger() - - if c.dry { - return nil, fmt.Errorf("not requesting GitHub App access_token in dry-run mode") - } - - var token AppInstallationToken - if _, err := c.request(&request{ - method: http.MethodPost, - path: fmt.Sprintf("/app/installations/%d/access_tokens", installationId), - exitCodes: []int{201}, - }, &token); err != nil { - return nil, err - } - - return &token, nil -} - -// GetApp gets the current app. Will not work with a Personal Access Token. -func (c *client) GetApp() (*App, error) { - return c.GetAppWithContext(context.Background()) -} - -func (c *client) GetAppWithContext(ctx context.Context) (*App, error) { - durationLogger := c.log("App") - defer durationLogger() - - var app App - if _, err := c.requestWithContext(ctx, &request{ - method: http.MethodGet, - path: "/app", - exitCodes: []int{200}, - }, &app); err != nil { - return nil, err - } - - return &app, nil -} - -// GetDirectory uses GitHub repo contents API to retrieve the content of a directory with commit SHA. -// If commit is empty, it will grab content from repo's default branch, usually master. -// -// See https://developer.github.com/v3/repos/contents/#get-contents -func (c *client) GetDirectory(org, repo, dirpath, commit string) ([]DirectoryContent, error) { - durationLogger := c.log("GetDirectory", org, repo, dirpath, commit) - defer durationLogger() - - path := fmt.Sprintf("/repos/%s/%s/contents/%s", org, repo, dirpath) - if commit != "" { - path = fmt.Sprintf("%s?ref=%s", path, url.QueryEscape(commit)) - } - - var res []DirectoryContent - code, err := c.request(&request{ - method: http.MethodGet, - path: path, - org: org, - exitCodes: []int{200, 404}, - }, &res) - - if err != nil { - return nil, err - } - - if code == 404 { - return nil, &FileNotFound{ - org: org, - repo: repo, - path: dirpath, - commit: commit, - } - } - - return res, nil -} - -// CreatePullRequestReviewComment creates a review comment on a PR. -// -// See also: https://docs.github.com/en/rest/reference/pulls#create-a-review-comment-for-a-pull-request -func (c *client) CreatePullRequestReviewComment(org, repo string, number int, rc ReviewComment) error { - c.log("CreatePullRequestReviewComment", org, repo, number, rc) - - // TODO: remove custom Accept headers when their respective API fully launches. - acceptHeaders := []string{ - // https://developer.github.com/changes/2016-05-12-reactions-api-preview/ - "application/vnd.github.squirrel-girl-preview", - // https://developer.github.com/changes/2019-10-03-multi-line-comments/ - "application/vnd.github.comfort-fade-preview+json", - } - - _, err := c.request(&request{ - method: http.MethodPost, - accept: strings.Join(acceptHeaders, ", "), - path: fmt.Sprintf("/repos/%s/%s/pulls/%d/comments", org, repo, number), - org: org, - requestBody: &rc, - exitCodes: []int{201}, - }, nil) - return err -} diff --git a/third_party/k8s.io/test-infra/prow/github/helpers.go b/third_party/k8s.io/test-infra/prow/github/helpers.go deleted file mode 100644 index 527038c..0000000 --- a/third_party/k8s.io/test-infra/prow/github/helpers.go +++ /dev/null @@ -1,107 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package github - -import ( - "fmt" - "net/http" - "regexp" - "strconv" - "strings" -) - -// SecurityForkNameRE is a regexp matching repos that are temporary security forks. -// https://help.github.com/en/github/managing-security-vulnerabilities/collaborating-in-a-temporary-private-fork-to-resolve-a-security-vulnerability -var SecurityForkNameRE = regexp.MustCompile(`^[\w-]+-ghsa-[\w-]+$`) - -// ImageSizeLimit is the maximum image size GitHub allows in bytes (5MB). -const ImageSizeLimit = 5242880 - -// HasLabel checks if label is in the label set "issueLabels". -func HasLabel(label string, issueLabels []Label) bool { - for _, l := range issueLabels { - if strings.EqualFold(l.Name, label) { - return true - } - } - return false -} - -// HasLabels checks if all labels are in the github.label set "issueLabels". -func HasLabels(labels []string, issueLabels []Label) bool { - for _, label := range labels { - if !HasLabel(label, issueLabels) { - return false - } - } - return true -} - -// ImageTooBig checks if image is bigger than github limits. -func ImageTooBig(url string) (bool, error) { - // try to get the image size from Content-Length header - resp, err := http.Head(url) - if err != nil { - return true, fmt.Errorf("HEAD error: %w", err) - } - defer resp.Body.Close() - if sc := resp.StatusCode; sc != http.StatusOK { - return true, fmt.Errorf("failing %d response", sc) - } - size, _ := strconv.Atoi(resp.Header.Get("Content-Length")) - if size > ImageSizeLimit { - return true, nil - } - return false, nil -} - -// LevelFromPermissions adapts a repo permissions struct to the -// appropriate permission level used elsewhere. -func LevelFromPermissions(permissions RepoPermissions) RepoPermissionLevel { - if permissions.Admin { - return Admin - } else if permissions.Maintain { - return Maintain - } else if permissions.Push { - return Write - } else if permissions.Triage { - return Triage - } else if permissions.Pull { - return Read - } else { - return None - } -} - -// PermissionsFromTeamPermissions -func PermissionsFromTeamPermission(permission TeamPermission) RepoPermissions { - switch permission { - case RepoPull: - return RepoPermissions{Pull: true} - case RepoTriage: - return RepoPermissions{Pull: true, Triage: true} - case RepoPush: - return RepoPermissions{Pull: true, Triage: true, Push: true} - case RepoMaintain: - return RepoPermissions{Pull: true, Triage: true, Push: true, Maintain: true} - case RepoAdmin: - return RepoPermissions{Pull: true, Triage: true, Push: true, Maintain: true, Admin: true} - default: - // Should never happen unless the type gets new value - return RepoPermissions{} - } -} diff --git a/third_party/k8s.io/test-infra/prow/github/hmac.go b/third_party/k8s.io/test-infra/prow/github/hmac.go deleted file mode 100644 index de36adb..0000000 --- a/third_party/k8s.io/test-infra/prow/github/hmac.go +++ /dev/null @@ -1,128 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package github - -import ( - "crypto/hmac" - "crypto/sha1" - "encoding/hex" - "encoding/json" - "fmt" - "strings" - "time" - - "github.com/sirupsen/logrus" - "sigs.k8s.io/yaml" -) - -// HMACToken contains a hmac token and the time when it's created. -type HMACToken struct { - Value string `json:"value"` - CreatedAt time.Time `json:"created_at"` -} - -// HMACsForRepo contains all hmac tokens configured for a repo, org or globally. -type HMACsForRepo []HMACToken - -// ValidatePayload ensures that the request payload signature matches the key. -func ValidatePayload(payload []byte, sig string, tokenGenerator func() []byte) bool { - var event GenericEvent - if err := json.Unmarshal(payload, &event); err != nil { - logrus.WithError(err).Info("validatePayload couldn't unmarshal the github event payload") - return false - } - - if !strings.HasPrefix(sig, "sha1=") { - return false - } - sig = sig[5:] - sb, err := hex.DecodeString(sig) - if err != nil { - return false - } - - orgRepo := event.Repo.FullName - // If orgRepo is empty, the event is probably org-level, so try getting org name from the Org info. - if orgRepo == "" { - orgRepo = event.Org.Login - } - hmacs, err := extractHMACs(orgRepo, tokenGenerator) - if err != nil { - logrus.WithError(err).Warning("failed to get an appropriate hmac secret") - return false - } - - // If we have a match with any valid hmac, we can validate successfully. - for _, key := range hmacs { - mac := hmac.New(sha1.New, key) - mac.Write(payload) - expected := mac.Sum(nil) - if hmac.Equal(sb, expected) { - return true - } - } - return false -} - -// PayloadSignature returns the signature that matches the payload. -func PayloadSignature(payload []byte, key []byte) string { - mac := hmac.New(sha1.New, key) - mac.Write(payload) - sum := mac.Sum(nil) - return "sha1=" + hex.EncodeToString(sum) -} - -// extractHMACs returns all *valid* HMAC tokens for given repository/organization. -// It considers only the tokens at the most specific level configured for the given repo. -// For example : if a token for repo is present and it doesn't match the repo, we will -// not try to find a match with org level token. However if no token is present for repo, -// we will try to match with org level. -func extractHMACs(orgRepo string, tokenGenerator func() []byte) ([][]byte, error) { - t := tokenGenerator() - repoToTokenMap := map[string]HMACsForRepo{} - - if err := yaml.Unmarshal(t, &repoToTokenMap); err != nil { - // To keep backward compatibility, we are going to assume that in case of error, - // whole file is a single line hmac token. - // TODO: Once this code has been released and file has been moved to new format, - // we should delete this code and return error. - logrus.WithError(err).Trace("Couldn't unmarshal the hmac secret as hierarchical file. Parsing as single token format") - return [][]byte{t}, nil - } - - orgName := strings.Split(orgRepo, "/")[0] - - if val, ok := repoToTokenMap[orgRepo]; ok { - return extractTokens(val), nil - } - if val, ok := repoToTokenMap[orgName]; ok { - return extractTokens(val), nil - } - if val, ok := repoToTokenMap["*"]; ok { - return extractTokens(val), nil - } - return nil, fmt.Errorf("no hmac is configured for the org/repo %q and no legacy global token is configured", orgRepo) -} - -// extractTokens return tokens for any given level of tree. -func extractTokens(allTokens HMACsForRepo) [][]byte { - validTokens := make([][]byte, len(allTokens)) - for i := range allTokens { - validTokens[i] = []byte(allTokens[i].Value) - } - return validTokens -} diff --git a/third_party/k8s.io/test-infra/prow/github/links.go b/third_party/k8s.io/test-infra/prow/github/links.go deleted file mode 100644 index 6ee826d..0000000 --- a/third_party/k8s.io/test-infra/prow/github/links.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package github - -import ( - "regexp" -) - -var lre = regexp.MustCompile(`<([^>]*)>; *rel="([^"]*)"`) - -// Parse Link headers, returning a map from Rel to URL. -// Only understands the URI and "rel" parameter. Very limited. -// See https://tools.ietf.org/html/rfc5988#section-5 -func parseLinks(h string) map[string]string { - links := map[string]string{} - for _, m := range lre.FindAllStringSubmatch(h, 10) { - if len(m) != 3 { - continue - } - links[m[2]] = m[1] - } - return links -} diff --git a/third_party/k8s.io/test-infra/prow/github/types.go b/third_party/k8s.io/test-infra/prow/github/types.go deleted file mode 100644 index b9eca20..0000000 --- a/third_party/k8s.io/test-infra/prow/github/types.go +++ /dev/null @@ -1,1610 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package github - -import ( - "encoding/json" - "fmt" - "strings" - "time" - - utilerrors "k8s.io/apimachinery/pkg/util/errors" -) - -const ( - // EventGUID is sent by GitHub in a header of every webhook request. - // Used as a log field across prow. - EventGUID = "event-GUID" - // PrLogField is the number of a PR. - // Used as a log field across prow. - PrLogField = "pr" - // OrgLogField is the organization of a PR. - // Used as a log field across prow. - OrgLogField = "org" - // RepoLogField is the repository of a PR. - // Used as a log field across prow. - RepoLogField = "repo" - - // SearchTimeFormat is a time.Time format string for ISO8601 which is the - // format that GitHub requires for times specified as part of a search query. - SearchTimeFormat = "2006-01-02T15:04:05Z" - - // DefaultAPIEndpoint is the default GitHub API endpoint. - DefaultAPIEndpoint = "https://api.github.com" - - // DefaultHost is the default GitHub base endpoint. - DefaultHost = "github.com" - - // DefaultGraphQLEndpoint is the default GitHub GraphQL API endpoint. - DefaultGraphQLEndpoint = "https://api.github.com/graphql" -) - -var ( - // FoundingYear is the year GitHub was founded. This is just used so that - // we can lower bound dates related to PRs and issues. - FoundingYear, _ = time.Parse(SearchTimeFormat, "2007-01-01T00:00:00Z") -) - -// These are possible State entries for a Status. -const ( - StatusPending = "pending" - StatusSuccess = "success" - StatusError = "error" - StatusFailure = "failure" -) - -// Possible contents for reactions. -const ( - ReactionThumbsUp = "+1" - ReactionThumbsDown = "-1" - ReactionLaugh = "laugh" - ReactionConfused = "confused" - ReactionHeart = "heart" - ReactionHooray = "hooray" - stateCannotBeChangedMessagePrefix = "state cannot be changed." -) - -func unmarshalClientError(b []byte) error { - var errors []error - clientError := ClientError{} - err := json.Unmarshal(b, &clientError) - if err == nil { - return clientError - } - errors = append(errors, err) - alternativeClientError := AlternativeClientError{} - err = json.Unmarshal(b, &alternativeClientError) - if err == nil { - return alternativeClientError - } - errors = append(errors, err) - return utilerrors.NewAggregate(errors) -} - -// ClientError represents https://developer.github.com/v3/#client-errors -type ClientError struct { - Message string `json:"message"` - Errors []clientErrorSubError `json:"errors,omitempty"` -} - -type clientErrorSubError struct { - Resource string `json:"resource"` - Field string `json:"field"` - Code string `json:"code"` - Message string `json:"message,omitempty"` -} - -func (r ClientError) Error() string { - return r.Message -} - -// AlternativeClientError represents an alternative format for https://developer.github.com/v3/#client-errors -// This is probably a GitHub bug, as documentation_url should appear only in custom errors -type AlternativeClientError struct { - Message string `json:"message"` - Errors []string `json:"errors,omitempty"` - DocumentationURL string `json:"documentation_url,omitempty"` -} - -func (r AlternativeClientError) Error() string { - return r.Message -} - -// Reaction holds the type of emotional reaction. -type Reaction struct { - Content string `json:"content"` -} - -// Status is used to set a commit status line. -type Status struct { - State string `json:"state"` - TargetURL string `json:"target_url,omitempty"` - Description string `json:"description,omitempty"` - Context string `json:"context,omitempty"` -} - -// CombinedStatus is the latest statuses for a ref. -type CombinedStatus struct { - SHA string `json:"sha"` - Statuses []Status `json:"statuses"` - State string `json:"state"` -} - -// User is a GitHub user account. -type User struct { - Login string `json:"login"` - Name string `json:"name"` - Email string `json:"email"` - ID int `json:"id"` - HTMLURL string `json:"html_url"` - Permissions RepoPermissions `json:"permissions"` - Type string `json:"type"` -} - -const ( - // UserTypeUser identifies an actual user account in the User.Type field - UserTypeUser = "User" - // UserTypeBot identifies a github app bot user in the User.Type field - UserTypeBot = "Bot" -) - -// NormLogin normalizes GitHub login strings -func NormLogin(login string) string { - return strings.TrimPrefix(strings.ToLower(login), "@") -} - -// PullRequestEventAction enumerates the triggers for this -// webhook payload type. See also: -// https://developer.github.com/v3/activity/events/types/#pullrequestevent -type PullRequestEventAction string - -const ( - // PullRequestActionAssigned means assignees were added. - PullRequestActionAssigned PullRequestEventAction = "assigned" - // PullRequestActionUnassigned means assignees were removed. - PullRequestActionUnassigned PullRequestEventAction = "unassigned" - // PullRequestActionReviewRequested means review requests were added. - PullRequestActionReviewRequested PullRequestEventAction = "review_requested" - // PullRequestActionReviewRequestRemoved means review requests were removed. - PullRequestActionReviewRequestRemoved PullRequestEventAction = "review_request_removed" - // PullRequestActionLabeled means labels were added. - PullRequestActionLabeled PullRequestEventAction = "labeled" - // PullRequestActionUnlabeled means labels were removed - PullRequestActionUnlabeled PullRequestEventAction = "unlabeled" - // PullRequestActionOpened means the PR was created - PullRequestActionOpened PullRequestEventAction = "opened" - // PullRequestActionEdited means the PR body changed. - PullRequestActionEdited PullRequestEventAction = "edited" - // PullRequestActionClosed means the PR was closed (or was merged). - PullRequestActionClosed PullRequestEventAction = "closed" - // PullRequestActionReopened means the PR was reopened. - PullRequestActionReopened PullRequestEventAction = "reopened" - // PullRequestActionSynchronize means the git state changed. - PullRequestActionSynchronize PullRequestEventAction = "synchronize" - // PullRequestActionReadyForReview means the PR is no longer a draft PR. - PullRequestActionReadyForReview PullRequestEventAction = "ready_for_review" - // PullRequestActionConvertedToDraft means the PR is now a draft PR. - PullRequestActionConvertedToDraft PullRequestEventAction = "converted_to_draft" - // PullRequestActionLocked means labels were added. - PullRequestActionLocked PullRequestEventAction = "locked" - // PullRequestActionUnlocked means labels were removed - PullRequestActionUnlocked PullRequestEventAction = "unlocked" - // PullRequestActionAutoMergeEnabled means auto merge was enabled - PullRequestActionAutoMergeEnabled PullRequestEventAction = "auto_merge_enabled" - // PullRequestActionAutoMergeDisabled means auto merge was disabled - PullRequestActionAutoMergeDisabled PullRequestEventAction = "auto_merge_disabled" -) - -// GenericEvent is a lightweight struct containing just Sender, Organization and Repo as -// they are allWebhook payload object common properties: -// https://developer.github.com/webhooks/event-payloads/#webhook-payload-object-common-properties -type GenericEvent struct { - Sender User `json:"sender"` - Org Organization `json:"organization"` - Repo Repo `json:"repository"` -} - -// PullRequestEvent is what GitHub sends us when a PR is changed. -type PullRequestEvent struct { - Action PullRequestEventAction `json:"action"` - Number int `json:"number"` - PullRequest PullRequest `json:"pull_request"` - Repo Repo `json:"repository"` - Label Label `json:"label"` - Sender User `json:"sender"` - - // Changes holds raw change data, which we must inspect - // and deserialize later as this is a polymorphic field - Changes json.RawMessage `json:"changes"` - - // GUID is included in the header of the request received by GitHub. - GUID string -} - -const ( - PullRequestStateOpen = "open" - PullRequestStateClosed = "closed" -) - -// PullRequest contains information about a PullRequest. -type PullRequest struct { - ID int `json:"id"` - NodeID string `json:"node_id"` - Number int `json:"number"` - HTMLURL string `json:"html_url"` - User User `json:"user"` - Labels []Label `json:"labels"` - Base PullRequestBranch `json:"base"` - Head PullRequestBranch `json:"head"` - Title string `json:"title"` - Body string `json:"body"` - RequestedReviewers []User `json:"requested_reviewers"` - RequestedTeams []Team `json:"requested_teams"` - Assignees []User `json:"assignees"` - State string `json:"state"` - Draft bool `json:"draft"` - Merged bool `json:"merged"` - CreatedAt time.Time `json:"created_at,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - // ref https://developer.github.com/v3/pulls/#get-a-single-pull-request - // If Merged is true, MergeSHA is the SHA of the merge commit, or squashed commit - // If Merged is false, MergeSHA is a commit SHA that github created to test if - // the PR can be merged automatically. - MergeSHA *string `json:"merge_commit_sha"` - // ref https://developer.github.com/v3/pulls/#response-1 - // The value of the mergeable attribute can be true, false, or null. If the value - // is null, this means that the mergeability hasn't been computed yet, and a - // background job was started to compute it. When the job is complete, the response - // will include a non-null value for the mergeable attribute. - Mergable *bool `json:"mergeable,omitempty"` - // If the PR doesn't have any milestone, `milestone` is null and is unmarshaled to nil. - Milestone *Milestone `json:"milestone,omitempty"` - Commits int `json:"commits"` - AuthorAssociation string `json:"author_association,omitempty"` -} - -// PullRequestBranch contains information about a particular branch in a PR. -type PullRequestBranch struct { - Ref string `json:"ref"` - SHA string `json:"sha"` - Repo Repo `json:"repo"` -} - -// Label describes a GitHub label. -type Label struct { - URL string `json:"url"` - Name string `json:"name"` - Description string `json:"description"` - Color string `json:"color"` -} - -// PullRequestFileStatus enumerates the statuses for this webhook payload type. -type PullRequestFileStatus string - -const ( - // PullRequestFileModified means a file changed. - PullRequestFileModified PullRequestFileStatus = "modified" - // PullRequestFileAdded means a file was added. - PullRequestFileAdded = "added" - // PullRequestFileRemoved means a file was deleted. - PullRequestFileRemoved = "removed" - // PullRequestFileRenamed means a file moved. - PullRequestFileRenamed = "renamed" -) - -// PullRequestChange contains information about what a PR changed. -type PullRequestChange struct { - SHA string `json:"sha"` - Filename string `json:"filename"` - Status string `json:"status"` - Additions int `json:"additions"` - Deletions int `json:"deletions"` - Changes int `json:"changes"` - Patch string `json:"patch"` - BlobURL string `json:"blob_url"` - PreviousFilename string `json:"previous_filename"` -} - -// Repo contains general repository information: it includes fields available -// in repo records returned by GH "List" methods but not those returned by GH -// "Get" method. Use FullRepo struct for "Get" method. -// See also https://developer.github.com/v3/repos/#list-organization-repositories -type Repo struct { - Owner User `json:"owner"` - Name string `json:"name"` - FullName string `json:"full_name"` - HTMLURL string `json:"html_url"` - Fork bool `json:"fork"` - DefaultBranch string `json:"default_branch"` - Archived bool `json:"archived"` - Private bool `json:"private"` - Description string `json:"description"` - Homepage string `json:"homepage"` - HasIssues bool `json:"has_issues"` - HasProjects bool `json:"has_projects"` - HasWiki bool `json:"has_wiki"` - NodeID string `json:"node_id"` - // Permissions reflect the permission level for the requester, so - // on a repository GET call this will be for the user whose token - // is being used, if listing a team's repos this will be for the - // team's privilege level in the repo - Permissions RepoPermissions `json:"permissions"` - Parent ParentRepo `json:"parent"` -} - -// ParentRepo contains a small subsection of general repository information: it -// just includes the information needed to confirm that a parent repo exists -// and what the name of that repo is. -type ParentRepo struct { - Owner User `json:"owner"` - Name string `json:"name"` - FullName string `json:"full_name"` - HTMLURL string `json:"html_url"` -} - -// Repo contains detailed repository information, including items -// that are not available in repo records returned by GH "List" methods -// but are in those returned by GH "Get" method. -// See https://developer.github.com/v3/repos/#list-organization-repositories -// See https://developer.github.com/v3/repos/#get -type FullRepo struct { - Repo - - AllowSquashMerge bool `json:"allow_squash_merge,omitempty"` - AllowMergeCommit bool `json:"allow_merge_commit,omitempty"` - AllowRebaseMerge bool `json:"allow_rebase_merge,omitempty"` - SquashMergeCommitTitle string `json:"squash_merge_commit_title,omitempty"` - SquashMergeCommitMessage string `json:"squash_merge_commit_message,omitempty"` -} - -// RepoRequest contains metadata used in requests to create or update a Repo. -// Compared to `Repo`, its members are pointers to allow the "not set/use default -// semantics. -// See also: -// - https://developer.github.com/v3/repos/#create -// - https://developer.github.com/v3/repos/#edit -type RepoRequest struct { - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - Homepage *string `json:"homepage,omitempty"` - Private *bool `json:"private,omitempty"` - HasIssues *bool `json:"has_issues,omitempty"` - HasProjects *bool `json:"has_projects,omitempty"` - HasWiki *bool `json:"has_wiki,omitempty"` - AllowSquashMerge *bool `json:"allow_squash_merge,omitempty"` - AllowMergeCommit *bool `json:"allow_merge_commit,omitempty"` - AllowRebaseMerge *bool `json:"allow_rebase_merge,omitempty"` - SquashMergeCommitTitle *string `json:"squash_merge_commit_title,omitempty"` - SquashMergeCommitMessage *string `json:"squash_merge_commit_message,omitempty"` -} - -type WorkflowRuns struct { - Count int `json:"total_count,omitempty"` - WorflowRuns []WorkflowRun `json:"workflow_runs"` -} - -// RepoCreateRequest contains metadata used in requests to create a repo. -// See also: https://developer.github.com/v3/repos/#create -type RepoCreateRequest struct { - RepoRequest `json:",omitempty"` - - AutoInit *bool `json:"auto_init,omitempty"` - GitignoreTemplate *string `json:"gitignore_template,omitempty"` - LicenseTemplate *string `json:"license_template,omitempty"` -} - -func (r RepoRequest) ToRepo() *FullRepo { - setString := func(dest, src *string) { - if src != nil { - *dest = *src - } - } - setBool := func(dest, src *bool) { - if src != nil { - *dest = *src - } - } - - var repo FullRepo - setString(&repo.Name, r.Name) - setString(&repo.Description, r.Description) - setString(&repo.Homepage, r.Homepage) - setBool(&repo.Private, r.Private) - setBool(&repo.HasIssues, r.HasIssues) - setBool(&repo.HasProjects, r.HasProjects) - setBool(&repo.HasWiki, r.HasWiki) - setBool(&repo.AllowSquashMerge, r.AllowSquashMerge) - setBool(&repo.AllowMergeCommit, r.AllowMergeCommit) - setBool(&repo.AllowRebaseMerge, r.AllowRebaseMerge) - setString(&repo.SquashMergeCommitTitle, r.SquashMergeCommitTitle) - setString(&repo.SquashMergeCommitMessage, r.SquashMergeCommitMessage) - - return &repo -} - -// Defined returns true if at least one of the pointer fields are not nil -func (r RepoRequest) Defined() bool { - return r.Name != nil || r.Description != nil || r.Homepage != nil || r.Private != nil || - r.HasIssues != nil || r.HasProjects != nil || r.HasWiki != nil || r.AllowSquashMerge != nil || - r.AllowMergeCommit != nil || r.AllowRebaseMerge != nil -} - -// RepoUpdateRequest contains metadata used for updating a repository -// See also: https://developer.github.com/v3/repos/#edit -type RepoUpdateRequest struct { - RepoRequest `json:",omitempty"` - - DefaultBranch *string `json:"default_branch,omitempty"` - Archived *bool `json:"archived,omitempty"` -} - -func (r RepoUpdateRequest) ToRepo() *FullRepo { - repo := r.RepoRequest.ToRepo() - if r.DefaultBranch != nil { - repo.DefaultBranch = *r.DefaultBranch - } - if r.Archived != nil { - repo.Archived = *r.Archived - } - - return repo -} - -func (r RepoUpdateRequest) Defined() bool { - return r.RepoRequest.Defined() || r.DefaultBranch != nil || r.Archived != nil -} - -// RepoPermissions describes which permission level an entity has in a -// repo. At most one of the booleans here should be true. -type RepoPermissions struct { - // Pull is equivalent to "Read" permissions in the web UI - Pull bool `json:"pull"` - Triage bool `json:"triage"` - // Push is equivalent to "Edit" permissions in the web UI - Push bool `json:"push"` - Maintain bool `json:"maintain"` - Admin bool `json:"admin"` -} - -// RepoPermissionLevel is admin, write, read or none. -// -// See https://developer.github.com/v3/repos/collaborators/#review-a-users-permission-level -type RepoPermissionLevel string - -// For more information on access levels, see: -// https://docs.github.com/en/github/setting-up-and-managing-organizations-and-teams/repository-permission-levels-for-an-organization -const ( - // Read allows pull but not push - Read RepoPermissionLevel = "read" - // Triage allows Read and managing issues - // pull requests but not push - Triage RepoPermissionLevel = "triage" - // Write allows Read plus push - Write RepoPermissionLevel = "write" - // Maintain allows Write along with managing - // repository without access to sensitive or - // destructive instructions. - Maintain RepoPermissionLevel = "maintain" - // Admin allows Write plus change others' rights. - Admin RepoPermissionLevel = "admin" - // None disallows everything - None RepoPermissionLevel = "none" -) - -var repoPermissionLevels = map[RepoPermissionLevel]bool{ - Read: true, - Triage: true, - Write: true, - Maintain: true, - Admin: true, - None: true, -} - -// MarshalText returns the byte representation of the permission -func (l RepoPermissionLevel) MarshalText() ([]byte, error) { - return []byte(l), nil -} - -// UnmarshalText validates the text is a valid string -func (l *RepoPermissionLevel) UnmarshalText(text []byte) error { - v := RepoPermissionLevel(text) - if _, ok := repoPermissionLevels[v]; !ok { - return fmt.Errorf("bad repo permission: %s not in %v", v, repoPermissionLevels) - } - *l = v - return nil -} - -type TeamPermission string - -const ( - RepoPull TeamPermission = "pull" - RepoTriage TeamPermission = "triage" - RepoMaintain TeamPermission = "maintain" - RepoPush TeamPermission = "push" - RepoAdmin TeamPermission = "admin" -) - -// Branch contains general branch information. -type Branch struct { - Name string `json:"name"` - Protected bool `json:"protected"` // only included for ?protection=true requests - // TODO(fejta): consider including undocumented protection key -} - -// BranchProtection represents protections -// currently in place for a branch -// See also: https://developer.github.com/v3/repos/branches/#get-branch-protection -type BranchProtection struct { - RequiredStatusChecks *RequiredStatusChecks `json:"required_status_checks"` - EnforceAdmins EnforceAdmins `json:"enforce_admins"` - RequiredPullRequestReviews *RequiredPullRequestReviews `json:"required_pull_request_reviews"` - Restrictions *Restrictions `json:"restrictions"` - AllowForcePushes AllowForcePushes `json:"allow_force_pushes"` - RequiredLinearHistory RequiredLinearHistory `json:"required_linear_history"` - AllowDeletions AllowDeletions `json:"allow_deletions"` -} - -// AllowDeletions specifies whether to permit users with push access to delete matching branches. -type AllowDeletions struct { - Enabled bool `json:"enabled"` -} - -// RequiredLinearHistory specifies whether to prevent merge commits from being pushed to matching branches. -type RequiredLinearHistory struct { - Enabled bool `json:"enabled"` -} - -// AllowForcePushes specifies whether to permit force pushes for all users with push access. -type AllowForcePushes struct { - Enabled bool `json:"enabled"` -} - -// EnforceAdmins specifies whether to enforce the -// configured branch restrictions for administrators. -type EnforceAdmins struct { - Enabled bool `json:"enabled"` -} - -// RequiredPullRequestReviews exposes the state of review rights. -type RequiredPullRequestReviews struct { - DismissalRestrictions *DismissalRestrictions `json:"dismissal_restrictions"` - DismissStaleReviews bool `json:"dismiss_stale_reviews"` - RequireCodeOwnerReviews bool `json:"require_code_owner_reviews"` - RequiredApprovingReviewCount int `json:"required_approving_review_count"` - BypassRestrictions *BypassRestrictions `json:"bypass_pull_request_allowances"` -} - -// DismissalRestrictions exposes restrictions in github for an activity to people/teams. -type DismissalRestrictions struct { - Users []User `json:"users,omitempty"` - Teams []Team `json:"teams,omitempty"` -} - -// BypassRestrictions exposes bypass option in github for a pull request to people/teams. -type BypassRestrictions struct { - Users []User `json:"users,omitempty"` - Teams []Team `json:"teams,omitempty"` -} - -// Restrictions exposes restrictions in github for an activity to apps/people/teams. -type Restrictions struct { - Apps []App `json:"apps,omitempty"` - Users []User `json:"users,omitempty"` - Teams []Team `json:"teams,omitempty"` -} - -// BranchProtectionRequest represents -// protections to put in place for a branch. -// See also: https://developer.github.com/v3/repos/branches/#update-branch-protection -type BranchProtectionRequest struct { - RequiredStatusChecks *RequiredStatusChecks `json:"required_status_checks"` - EnforceAdmins *bool `json:"enforce_admins"` - RequiredPullRequestReviews *RequiredPullRequestReviewsRequest `json:"required_pull_request_reviews"` - Restrictions *RestrictionsRequest `json:"restrictions"` - RequiredLinearHistory bool `json:"required_linear_history"` - AllowForcePushes bool `json:"allow_force_pushes"` - AllowDeletions bool `json:"allow_deletions"` -} - -func (r BranchProtectionRequest) String() string { - bytes, err := json.Marshal(&r) - if err != nil { - return fmt.Sprintf("%#v", r) - } - return string(bytes) -} - -// RequiredStatusChecks specifies which contexts must pass to merge. -type RequiredStatusChecks struct { - Strict bool `json:"strict"` // PR must be up to date (include latest base branch commit). - Contexts []string `json:"contexts"` -} - -// RequiredPullRequestReviewsRequest controls a request for review rights. -type RequiredPullRequestReviewsRequest struct { - DismissalRestrictions DismissalRestrictionsRequest `json:"dismissal_restrictions"` - DismissStaleReviews bool `json:"dismiss_stale_reviews"` - RequireCodeOwnerReviews bool `json:"require_code_owner_reviews"` - RequiredApprovingReviewCount int `json:"required_approving_review_count"` - BypassRestrictions BypassRestrictionsRequest `json:"bypass_pull_request_allowances"` -} - -// DismissalRestrictionsRequest tells github to restrict an activity to people/teams. -// -// Use *[]string in order to distinguish unset and empty list. -// This is needed by dismissal_restrictions to distinguish -// do not restrict (empty object) and restrict everyone (nil user/teams list) -type DismissalRestrictionsRequest struct { - // Users is a list of user logins - Users *[]string `json:"users,omitempty"` - // Teams is a list of team slugs - Teams *[]string `json:"teams,omitempty"` -} - -// BypassRestrictionsRequest tells github to restrict PR bypass activity to people/teams. -// -// Use *[]string in order to distinguish unset and empty list. -// This is needed by bypass_pull_request_allowances to distinguish -// do not restrict (empty object) and restrict everyone (nil user/teams list) -type BypassRestrictionsRequest struct { - // Users is a list of user logins - Users *[]string `json:"users,omitempty"` - // Teams is a list of team slugs - Teams *[]string `json:"teams,omitempty"` -} - -// RestrictionsRequest tells github to restrict an activity to apps/people/teams. -// -// Use *[]string in order to distinguish unset and empty list. -// do not restrict (empty object) and restrict everyone (nil apps/user/teams list) -type RestrictionsRequest struct { - // Apps is a list of app names - Apps *[]string `json:"apps,omitempty"` - // Users is a list of user logins - Users *[]string `json:"users,omitempty"` - // Teams is a list of team slugs - Teams *[]string `json:"teams,omitempty"` -} - -// HookConfig holds the endpoint and its secret. -type HookConfig struct { - URL string `json:"url"` - ContentType *string `json:"content_type,omitempty"` - Secret *string `json:"secret,omitempty"` -} - -// Hook holds info about the webhook configuration. -type Hook struct { - ID int `json:"id"` - Name string `json:"name"` - Events []string `json:"events"` - Active bool `json:"active"` - Config HookConfig `json:"config"` -} - -// HookRequest can create and/or edit a webhook. -// -// AddEvents and RemoveEvents are only valid during an edit, and only for a repo -type HookRequest struct { - Name string `json:"name,omitempty"` // must be web or "", only create - Active *bool `json:"active,omitempty"` - AddEvents []string `json:"add_events,omitempty"` // only repo edit - Config *HookConfig `json:"config,omitempty"` - Events []string `json:"events,omitempty"` - RemoveEvents []string `json:"remove_events,omitempty"` // only repo edit -} - -// AllHookEvents causes github to send all events. -// https://developer.github.com/v3/activity/events/types/ -var AllHookEvents = []string{"*"} - -// IssueEventAction enumerates the triggers for this -// webhook payload type. See also: -// https://developer.github.com/v3/activity/events/types/#issuesevent -type IssueEventAction string - -const ( - // IssueActionAssigned means assignees were added. - IssueActionAssigned IssueEventAction = "assigned" - // IssueActionUnassigned means assignees were added. - IssueActionUnassigned IssueEventAction = "unassigned" - // IssueActionLabeled means labels were added. - IssueActionLabeled IssueEventAction = "labeled" - // IssueActionUnlabeled means labels were removed. - IssueActionUnlabeled IssueEventAction = "unlabeled" - // IssueActionOpened means issue was opened/created. - IssueActionOpened IssueEventAction = "opened" - // IssueActionEdited means issue body was edited. - IssueActionEdited IssueEventAction = "edited" - // IssueActionDeleted means the issue was deleted. - IssueActionDeleted IssueEventAction = "deleted" - // IssueActionMilestoned means the milestone was added/changed. - IssueActionMilestoned IssueEventAction = "milestoned" - // IssueActionDemilestoned means a milestone was removed. - IssueActionDemilestoned IssueEventAction = "demilestoned" - // IssueActionClosed means issue was closed. - IssueActionClosed IssueEventAction = "closed" - // IssueActionReopened means issue was reopened. - IssueActionReopened IssueEventAction = "reopened" - // IssueActionPinned means the issue was pinned. - IssueActionPinned IssueEventAction = "pinned" - // IssueActionUnpinned means the issue was unpinned. - IssueActionUnpinned IssueEventAction = "unpinned" - // IssueActionTransferred means the issue was transferred to another repo. - IssueActionTransferred IssueEventAction = "transferred" - // IssueActionLocked means the issue was locked. - IssueActionLocked IssueEventAction = "locked" - // IssueActionUnlocked means the issue was unlocked. - IssueActionUnlocked IssueEventAction = "unlocked" -) - -// IssueEvent represents an issue event from a webhook payload (not from the events API). -type IssueEvent struct { - Action IssueEventAction `json:"action"` - Issue Issue `json:"issue"` - Repo Repo `json:"repository"` - // Label is specified for IssueActionLabeled and IssueActionUnlabeled events. - Label Label `json:"label"` - Sender User `json:"sender"` - - // GUID is included in the header of the request received by GitHub. - GUID string -} - -// ListedIssueEvent represents an issue event from the events API (not from a webhook payload). -// https://developer.github.com/v3/issues/events/ -type ListedIssueEvent struct { - ID int64 `json:"id,omitempty"` - URL string `json:"url,omitempty"` - - // The User that generated this event. - Actor User `json:"actor"` - - // This is the same as IssueEvent.Action - Event IssueEventAction `json:"event"` - - CreatedAt time.Time `json:"created_at"` - Issue Issue `json:"issue,omitempty"` - - // Only present on certain events. - Assignee User `json:"assignee,omitempty"` - Assigner User `json:"assigner,omitempty"` - CommitID string `json:"commit_id,omitempty"` - Milestone Milestone `json:"milestone,omitempty"` - Label Label `json:"label"` - Rename Rename `json:"rename,omitempty"` - LockReason string `json:"lock_reason,omitempty"` - ProjectCard ProjectCard `json:"project_card,omitempty"` - DismissedReview DismissedReview `json:"dismissed_review,omitempty"` - RequestedReviewer User `json:"requested_reviewer,omitempty"` - ReviewRequester User `json:"review_requester,omitempty"` -} - -// Rename contains details for 'renamed' events. -type Rename struct { - From string `json:"from,omitempty"` - To string `json:"to,omitempty"` -} - -// DismissedReview represents details for 'dismissed_review' events. -type DismissedReview struct { - // State represents the state of the dismissed review.DismissedReview - // Possible values are: "commented", "approved", and "changes_requested". - State string `json:"state,omitempty"` - ReviewID int64 `json:"review_id,omitempty"` - DismissalMessage string `json:"dismissal_message,omitempty"` - DismissalCommitID string `json:"dismissal_commit_id,omitempty"` -} - -// IssueCommentEventAction enumerates the triggers for this -// webhook payload type. See also: -// https://developer.github.com/v3/activity/events/types/#issuecommentevent -type IssueCommentEventAction string - -const ( - // IssueCommentActionCreated means the comment was created. - IssueCommentActionCreated IssueCommentEventAction = "created" - // IssueCommentActionEdited means the comment was edited. - IssueCommentActionEdited IssueCommentEventAction = "edited" - // IssueCommentActionDeleted means the comment was deleted. - IssueCommentActionDeleted IssueCommentEventAction = "deleted" -) - -// IssueCommentEvent is what GitHub sends us when an issue comment is changed. -type IssueCommentEvent struct { - Action IssueCommentEventAction `json:"action"` - Issue Issue `json:"issue"` - Comment IssueComment `json:"comment"` - Repo Repo `json:"repository"` - - // GUID is included in the header of the request received by GitHub. - GUID string -} - -// Issue represents general info about an issue. -type Issue struct { - ID int `json:"id"` - NodeID string `json:"node_id"` - User User `json:"user"` - Number int `json:"number"` - Title string `json:"title"` - State string `json:"state"` - HTMLURL string `json:"html_url"` - Labels []Label `json:"labels"` - Assignees []User `json:"assignees"` - Body string `json:"body"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Milestone Milestone `json:"milestone"` - StateReason string `json:"state_reason"` - - // This will be non-nil if it is a pull request. - PullRequest *struct{} `json:"pull_request,omitempty"` -} - -// IsAssignee checks if a user is assigned to the issue. -func (i Issue) IsAssignee(login string) bool { - for _, assignee := range i.Assignees { - if NormLogin(login) == NormLogin(assignee.Login) { - return true - } - } - return false -} - -// IsAuthor checks if a user is the author of the issue. -func (i Issue) IsAuthor(login string) bool { - return NormLogin(i.User.Login) == NormLogin(login) -} - -// IsPullRequest checks if an issue is a pull request. -func (i Issue) IsPullRequest() bool { - return i.PullRequest != nil -} - -// HasLabel checks if an issue has a given label. -func (i Issue) HasLabel(labelToFind string) bool { - for _, label := range i.Labels { - if strings.EqualFold(label.Name, labelToFind) { - return true - } - } - return false -} - -// IssueComment represents general info about an issue comment. -type IssueComment struct { - ID int `json:"id,omitempty"` - Body string `json:"body"` - User User `json:"user,omitempty"` - HTMLURL string `json:"html_url,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` -} - -// StatusEvent fires whenever a git commit changes. -// -// See https://developer.github.com/v3/activity/events/types/#statusevent -type StatusEvent struct { - SHA string `json:"sha,omitempty"` - State string `json:"state,omitempty"` - Description string `json:"description,omitempty"` - TargetURL string `json:"target_url,omitempty"` - ID int `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Context string `json:"context,omitempty"` - Sender User `json:"sender,omitempty"` - Repo Repo `json:"repository,omitempty"` - - // GUID is included in the header of the request received by GitHub. - GUID string -} - -// IssuesSearchResult represents the result of an issues search. -type IssuesSearchResult struct { - Total int `json:"total_count,omitempty"` - Issues []Issue `json:"items,omitempty"` -} - -// PushEvent is what GitHub sends us when a user pushes to a repo. -type PushEvent struct { - Ref string `json:"ref"` - Before string `json:"before"` - After string `json:"after"` - Created bool `json:"created"` - Deleted bool `json:"deleted"` - Forced bool `json:"forced"` - Compare string `json:"compare"` - Commits []Commit `json:"commits"` - // Pusher is the user that pushed the commit, valid in a webhook event. - Pusher User `json:"pusher"` - // Sender contains more information that Pusher about the user. - Sender User `json:"sender"` - Repo Repo `json:"repository"` - - // GUID is included in the header of the request received by GitHub. - GUID string -} - -// Branch returns the name of the branch to which the user pushed. -func (pe PushEvent) Branch() string { - ref := strings.TrimPrefix(pe.Ref, "refs/heads/") // if Ref is a branch - ref = strings.TrimPrefix(ref, "refs/tags/") // if Ref is a tag - return ref -} - -// Commit represents general info about a commit. -type Commit struct { - ID string `json:"id"` - Message string `json:"message"` - Added []string `json:"added"` - Removed []string `json:"removed"` - Modified []string `json:"modified"` -} - -// Tree represents a GitHub tree. -type Tree struct { - SHA string `json:"sha,omitempty"` -} - -// ReviewEventAction enumerates the triggers for this -// webhook payload type. See also: -// https://developer.github.com/v3/activity/events/types/#pullrequestreviewevent -type ReviewEventAction string - -const ( - // ReviewActionSubmitted means the review was submitted. - ReviewActionSubmitted ReviewEventAction = "submitted" - // ReviewActionEdited means the review was edited. - ReviewActionEdited ReviewEventAction = "edited" - // ReviewActionDismissed means the review was dismissed. - ReviewActionDismissed ReviewEventAction = "dismissed" -) - -// ReviewEvent is what GitHub sends us when a PR review is changed. -type ReviewEvent struct { - Action ReviewEventAction `json:"action"` - PullRequest PullRequest `json:"pull_request"` - Repo Repo `json:"repository"` - Review Review `json:"review"` - - // GUID is included in the header of the request received by GitHub. - GUID string -} - -// ReviewState is the state a review can be in. -type ReviewState string - -// Possible review states. -const ( - ReviewStateApproved ReviewState = "APPROVED" - ReviewStateChangesRequested = "CHANGES_REQUESTED" - ReviewStateCommented = "COMMENTED" - ReviewStateDismissed = "DISMISSED" - ReviewStatePending = "PENDING" -) - -// Review describes a Pull Request review. -type Review struct { - ID int `json:"id"` - NodeID string `json:"node_id"` - User User `json:"user"` - Body string `json:"body"` - State ReviewState `json:"state"` - HTMLURL string `json:"html_url"` - SubmittedAt time.Time `json:"submitted_at"` -} - -// ReviewCommentEventAction enumerates the triggers for this -// webhook payload type. See also: -// https://developer.github.com/v3/activity/events/types/#pullrequestreviewcommentevent -type ReviewCommentEventAction string - -const ( - // ReviewCommentActionCreated means the comment was created. - ReviewCommentActionCreated ReviewCommentEventAction = "created" - // ReviewCommentActionEdited means the comment was edited. - ReviewCommentActionEdited ReviewCommentEventAction = "edited" - // ReviewCommentActionDeleted means the comment was deleted. - ReviewCommentActionDeleted ReviewCommentEventAction = "deleted" -) - -// ReviewCommentEvent is what GitHub sends us when a PR review comment is changed. -type ReviewCommentEvent struct { - Action ReviewCommentEventAction `json:"action"` - PullRequest PullRequest `json:"pull_request"` - Repo Repo `json:"repository"` - Comment ReviewComment `json:"comment"` - - // GUID is included in the header of the request received by GitHub. - GUID string -} - -// DiffSide enumerates the sides of the diff that the PR's changes appear on. -// See also: https://docs.github.com/en/rest/reference/pulls#create-a-review-comment-for-a-pull-request -type DiffSide string - -const ( - // DiffSideLeft means left side of the diff. - DiffSideLeft = "LEFT" - // DiffSideRight means right side of the diff. - DiffSideRight = "RIGHT" -) - -// ReviewComment describes a Pull Request review. -type ReviewComment struct { - ID int `json:"id"` - NodeID string `json:"node_id"` - ReviewID int `json:"pull_request_review_id"` - User User `json:"user"` - Body string `json:"body"` - Path string `json:"path"` - HTMLURL string `json:"html_url"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - // Position will be nil if the code has changed such that the comment is no - // longer relevant. - Position *int `json:"position,omitempty"` - Side DiffSide `json:"side,omitempty"` - StartSide DiffSide `json:"start_side,omitempty"` - Line int `json:"line,omitempty"` - StartLine int `json:"start_line,omitempty"` -} - -// ReviewAction is the action that a review can be made with. -type ReviewAction string - -// Possible review actions. Leave Action blank for a pending review. -const ( - Approve ReviewAction = "APPROVE" - RequestChanges = "REQUEST_CHANGES" - Comment = "COMMENT" -) - -// DraftReview is what we give GitHub when we want to make a PR Review. This is -// different than what we receive when we ask for a Review. -type DraftReview struct { - // If unspecified, defaults to the most recent commit in the PR. - CommitSHA string `json:"commit_id,omitempty"` - Body string `json:"body"` - // If unspecified, defaults to PENDING. - Action ReviewAction `json:"event,omitempty"` - Comments []DraftReviewComment `json:"comments,omitempty"` -} - -// DraftReviewComment is a comment in a draft review. -type DraftReviewComment struct { - Path string `json:"path"` - // Position in the patch, not the line number in the file. - Position int `json:"position"` - Body string `json:"body"` -} - -// Content is some base64 encoded github file content -// It include selected fields available in content record returned by -// GH "GET" method. See also: -// https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#get-repository-content -type Content struct { - Content string `json:"content"` - SHA string `json:"sha"` -} - -const ( - // PrivacySecret memberships are only visible to other team members. - PrivacySecret = "secret" - // PrivacyClosed memberships are visible to org members. - PrivacyClosed = "closed" -) - -// Team is a github organizational team -type Team struct { - ID int `json:"id,omitempty"` - Name string `json:"name"` - Slug string `json:"slug"` - Description string `json:"description,omitempty"` - Privacy string `json:"privacy,omitempty"` - Parent *Team `json:"parent,omitempty"` // Only present in responses - ParentTeamID *int `json:"parent_team_id,omitempty"` // Only valid in creates/edits - Permission TeamPermission `json:"permission,omitempty"` -} - -// TeamMember is a member of an organizational team -type TeamMember struct { - Login string `json:"login"` -} - -const ( - // RoleAll lists both members and admins - RoleAll = "all" - // RoleAdmin specifies the user is an org admin, or lists only admins - RoleAdmin = "admin" - // RoleMaintainer specifies the user is a team maintainer, or lists only maintainers - RoleMaintainer = "maintainer" - // RoleMember specifies the user is a regular user, or only lists regular users - RoleMember = "member" - // StatePending specifies the user has an invitation to the org/team. - StatePending = "pending" - // StateActive specifies the user's membership is active. - StateActive = "active" -) - -// Membership specifies the role and state details for an org and/or team. -type Membership struct { - // admin or member - Role string `json:"role"` - // pending or active - State string `json:"state,omitempty"` -} - -// Organization stores metadata information about an organization -type Organization struct { - // Login has the same meaning as Name, but it's more reliable to use as Name can sometimes be empty, - // see https://developer.github.com/v3/orgs/#list-organizations - Login string `json:"login"` - Id int `json:"id"` - // BillingEmail holds private billing address - BillingEmail string `json:"billing_email"` - Company string `json:"company"` - // Email is publicly visible - Email string `json:"email"` - Location string `json:"location"` - Name string `json:"name"` - Description string `json:"description"` - HasOrganizationProjects bool `json:"has_organization_projects"` - HasRepositoryProjects bool `json:"has_repository_projects"` - DefaultRepositoryPermission string `json:"default_repository_permission"` - MembersCanCreateRepositories bool `json:"members_can_create_repositories"` -} - -// OrgMembership contains Membership fields for user membership in an org. -type OrgMembership struct { - Membership -} - -// TeamMembership contains Membership fields for user membership on a team. -type TeamMembership struct { - Membership -} - -// OrgInvitation contains Login and other details about the invitation. -type OrgInvitation struct { - TeamMember - Email string `json:"email"` - Inviter TeamMember `json:"inviter"` -} - -// UserRepoInvitation is returned by repo invitation obtained by user. -type UserRepoInvitation struct { - InvitationID int `json:"id"` - Repository *Repo `json:"repository,omitempty"` - Permission RepoPermissionLevel `json:"permissions"` -} - -// OrgPermissionLevel is admin, and member -// -// See https://docs.github.com/en/rest/reference/orgs#set-organization-membership-for-a-user -type OrgPermissionLevel string - -const ( - // OrgMember is the member - OrgMember OrgPermissionLevel = "member" - // OrgAdmin manages the org - OrgAdmin OrgPermissionLevel = "admin" - // OrgUnaffiliated probably means user not a member yet, this was returned - // from an org invitation, had to add it so unmarshal doesn't crash - OrgUnaffiliated OrgPermissionLevel = "unaffiliated" - // OrgReinstate means the user was removed and the invited again before n months have passed. - // More info here: https://docs.github.com/en/github-ae@latest/organizations/managing-membership-in-your-organization/reinstating-a-former-member-of-your-organization - OrgReinstate OrgPermissionLevel = "reinstate" -) - -var orgPermissionLevels = map[OrgPermissionLevel]bool{ - OrgMember: true, - OrgAdmin: true, - OrgUnaffiliated: true, - OrgReinstate: true, -} - -// MarshalText returns the byte representation of the permission -func (l OrgPermissionLevel) MarshalText() ([]byte, error) { - return []byte(l), nil -} - -// UnmarshalText validates the text is a valid string -func (l *OrgPermissionLevel) UnmarshalText(text []byte) error { - v := OrgPermissionLevel(text) - if _, ok := orgPermissionLevels[v]; !ok { - return fmt.Errorf("bad org permission: %s not in %v", v, orgPermissionLevels) - } - *l = v - return nil -} - -// UserOrganization contains info consumed by UserOrgInvitation. -type UserOrganization struct { - // Login is the name of org - Login string `json:"login"` -} - -// UserOrgInvitation is returned by org invitation obtained by user. -type UserOrgInvitation struct { - State string `json:"state"` - Role OrgPermissionLevel `json:"role"` - Org UserOrganization `json:"organization"` -} - -// GenericCommentEventAction coerces multiple actions into its generic equivalent. -type GenericCommentEventAction string - -// Comments indicate values that are coerced to the specified value. -const ( - // GenericCommentActionCreated means something was created/opened/submitted - GenericCommentActionCreated GenericCommentEventAction = "created" // "opened", "submitted" - // GenericCommentActionEdited means something was edited. - GenericCommentActionEdited GenericCommentEventAction = "edited" - // GenericCommentActionDeleted means something was deleted/dismissed. - GenericCommentActionDeleted GenericCommentEventAction = "deleted" // "dismissed" -) - -// GenericCommentEvent is a fake event type that is instantiated for any github event that contains -// comment like content. -// The specific events that are also handled as GenericCommentEvents are: -// - issue_comment events -// - pull_request_review events -// - pull_request_review_comment events -// - pull_request events with action in ["opened", "edited"] -// - issue events with action in ["opened", "edited"] -// -// Issue and PR "closed" events are not coerced to the "deleted" Action and do not trigger -// a GenericCommentEvent because these events don't actually remove the comment content from GH. -type GenericCommentEvent struct { - ID int `json:"id"` - NodeID string `json:"node_id"` - CommentID *int - IsPR bool - Action GenericCommentEventAction - Body string - HTMLURL string - Number int - Repo Repo - User User - IssueAuthor User - Assignees []User - IssueState string - IssueTitle string - IssueBody string - IssueHTMLURL string - GUID string -} - -// Milestone is a milestone defined on a github repository -type Milestone struct { - Title string `json:"title"` - Number int `json:"number"` - State string `json:"state"` -} - -// RepositoryCommit represents a commit in a repo. -// Note that it's wrapping a GitCommit, so author/committer information is in two places, -// but contain different details about them: in RepositoryCommit "github details", in GitCommit - "git details". -// Get single commit also use it, see: https://developer.github.com/v3/repos/commits/#get-a-single-commit. -type RepositoryCommit struct { - NodeID string `json:"node_id,omitempty"` - SHA string `json:"sha,omitempty"` - Commit GitCommit `json:"commit,omitempty"` - Author User `json:"author,omitempty"` - Committer User `json:"committer,omitempty"` - Parents []GitCommit `json:"parents,omitempty"` - HTMLURL string `json:"html_url,omitempty"` - URL string `json:"url,omitempty"` - CommentsURL string `json:"comments_url,omitempty"` - - // Details about how many changes were made in this commit. Only filled in during GetCommit! - Stats *CommitStats `json:"stats,omitempty"` - // Details about which files, and how this commit touched. Only filled in during GetCommit! - Files []CommitFile `json:"files,omitempty"` -} - -// CommitStats represents the number of additions / deletions from a file in a given RepositoryCommit or GistCommit. -type CommitStats struct { - Additions int `json:"additions,omitempty"` - Deletions int `json:"deletions,omitempty"` - Total int `json:"total,omitempty"` -} - -// CommitFile represents a file modified in a commit. -type CommitFile struct { - SHA string `json:"sha,omitempty"` - Filename string `json:"filename,omitempty"` - Additions int `json:"additions,omitempty"` - Deletions int `json:"deletions,omitempty"` - Changes int `json:"changes,omitempty"` - Status string `json:"status,omitempty"` - Patch string `json:"patch,omitempty"` - BlobURL string `json:"blob_url,omitempty"` - RawURL string `json:"raw_url,omitempty"` - ContentsURL string `json:"contents_url,omitempty"` - PreviousFilename string `json:"previous_filename,omitempty"` -} - -// GitCommit represents a GitHub commit. -type GitCommit struct { - SHA string `json:"sha,omitempty"` - Author CommitAuthor `json:"author,omitempty"` - Committer CommitAuthor `json:"committer,omitempty"` - Message string `json:"message,omitempty"` - Tree Tree `json:"tree,omitempty"` - Parents []GitCommit `json:"parents,omitempty"` - Stats *CommitStats `json:"stats,omitempty"` - HTMLURL string `json:"html_url,omitempty"` - URL string `json:"url,omitempty"` - Verification *SignatureVerification `json:"verification,omitempty"` - NodeID string `json:"node_id,omitempty"` - - // CommentCount is the number of GitHub comments on the commit. This - // is only populated for requests that fetch GitHub data like - // Pulls.ListCommits, Repositories.ListCommits, etc. - CommentCount *int `json:"comment_count,omitempty"` -} - -// CommitAuthor represents the author or committer of a commit. The commit -// author may not correspond to a GitHub User. -type CommitAuthor struct { - Date time.Time `json:"date,omitempty"` - Name string `json:"name,omitempty"` - Email string `json:"email,omitempty"` - - // The following fields are only populated by Webhook events. - Login *string `json:"username,omitempty"` -} - -// SignatureVerification represents GPG signature verification. -type SignatureVerification struct { - Verified bool `json:"verified,omitempty"` - Reason string `json:"reason,omitempty"` - Signature string `json:"signature,omitempty"` - Payload string `json:"payload,omitempty"` -} - -// Project is a github project -type Project struct { - Name string `json:"name"` - ID int `json:"id"` -} - -// ProjectColumn is a colunm in a github project -type ProjectColumn struct { - Name string `json:"name"` - ID int `json:"id"` -} - -// ProjectCard is a github project card -type ProjectCard struct { - ID int `json:"id"` - ContentID int `json:"content_id"` - ContentType string `json:"content_type"` - ContentURL string `json:"content_url"` -} - -type CheckRunList struct { - Total int `json:"total_count,omitempty"` - CheckRuns []CheckRun `json:"check_runs,omitempty"` -} - -type CheckRun struct { - ID int64 `json:"id,omitempty"` - NodeID string `json:"node_id,omitempty"` - HeadSHA string `json:"head_sha,omitempty"` - ExternalID string `json:"external_id,omitempty"` - URL string `json:"url,omitempty"` - HTMLURL string `json:"html_url,omitempty"` - DetailsURL string `json:"details_url,omitempty"` - Status string `json:"status,omitempty"` - Conclusion string `json:"conclusion,omitempty"` - StartedAt string `json:"started_at,omitempty"` - CompletedAt string `json:"completed_at,omitempty"` - Output CheckRunOutput `json:"output,omitempty"` - Name string `json:"name,omitempty"` - CheckSuite CheckSuite `json:"check_suite,omitempty"` - App App `json:"app,omitempty"` - PullRequests []PullRequest `json:"pull_requests,omitempty"` -} - -type CheckRunOutput struct { - Title string `json:"title,omitempty"` - Summary string `json:"summary,omitempty"` - Text string `json:"text,omitempty"` - AnnotationsCount int `json:"annotations_count,omitempty"` - AnnotationsURL string `json:"annotations_url,omitempty"` - Annotations []CheckRunAnnotation `json:"annotations,omitempty"` - Images []CheckRunImage `json:"images,omitempty"` -} - -type CheckRunAnnotation struct { - Path string `json:"path,omitempty"` - StartLine int `json:"start_line,omitempty"` - EndLine int `json:"end_line,omitempty"` - StartColumn int `json:"start_column,omitempty"` - EndColumn int `json:"end_column,omitempty"` - AnnotationLevel string `json:"annotation_level,omitempty"` - Message string `json:"message,omitempty"` - Title string `json:"title,omitempty"` - RawDetails string `json:"raw_details,omitempty"` -} - -type CheckRunImage struct { - Alt string `json:"alt,omitempty"` - ImageURL string `json:"image_url,omitempty"` - Caption string `json:"caption,omitempty"` -} - -type CheckSuite struct { - ID int64 `json:"id,omitempty"` - NodeID string `json:"node_id,omitempty"` - HeadBranch string `json:"head_branch,omitempty"` - HeadSHA string `json:"head_sha,omitempty"` - URL string `json:"url,omitempty"` - BeforeSHA string `json:"before,omitempty"` - AfterSHA string `json:"after,omitempty"` - Status string `json:"status,omitempty"` - Conclusion string `json:"conclusion,omitempty"` - App *App `json:"app,omitempty"` - Repository *Repo `json:"repository,omitempty"` - PullRequests []PullRequest `json:"pull_requests,omitempty"` - - // The following fields are only populated by Webhook events. - HeadCommit *Commit `json:"head_commit,omitempty"` -} - -type App struct { - ID int64 `json:"id,omitempty"` - Slug string `json:"slug,omitempty"` - NodeID string `json:"node_id,omitempty"` - Owner User `json:"owner,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - ExternalURL string `json:"external_url,omitempty"` - HTMLURL string `json:"html_url,omitempty"` - CreatedAt string `json:"created_at,omitempty"` - UpdatedAt string `json:"updated_at,omitempty"` - Permissions *InstallationPermissions `json:"permissions,omitempty"` - Events []string `json:"events,omitempty"` -} - -type InstallationPermissions struct { - Administration string `json:"administration,omitempty"` - Blocking string `json:"blocking,omitempty"` - Checks string `json:"checks,omitempty"` - Contents string `json:"contents,omitempty"` - ContentReferences string `json:"content_references,omitempty"` - Deployments string `json:"deployments,omitempty"` - Emails string `json:"emails,omitempty"` - Followers string `json:"followers,omitempty"` - Issues string `json:"issues,omitempty"` - Metadata string `json:"metadata,omitempty"` - Members string `json:"members,omitempty"` - OrganizationAdministration string `json:"organization_administration,omitempty"` - OrganizationHooks string `json:"organization_hooks,omitempty"` - OrganizationPlan string `json:"organization_plan,omitempty"` - OrganizationPreReceiveHooks string `json:"organization_pre_receive_hooks,omitempty"` - OrganizationProjects string `json:"organization_projects,omitempty"` - OrganizationUserBlocking string `json:"organization_user_blocking,omitempty"` - Packages string `json:"packages,omitempty"` - Pages string `json:"pages,omitempty"` - PullRequests string `json:"pull_requests,omitempty"` - RepositoryHooks string `json:"repository_hooks,omitempty"` - RepositoryProjects string `json:"repository_projects,omitempty"` - RepositoryPreReceiveHooks string `json:"repository_pre_receive_hooks,omitempty"` - SingleFile string `json:"single_file,omitempty"` - Statuses string `json:"statuses,omitempty"` - TeamDiscussions string `json:"team_discussions,omitempty"` - VulnerabilityAlerts string `json:"vulnerability_alerts,omitempty"` -} - -// AppInstallation represents a GitHub Apps installation. -type AppInstallation struct { - ID int64 `json:"id,omitempty"` - AppSlug string `json:"app_slug,omitempty"` - NodeID string `json:"node_id,omitempty"` - AppID int64 `json:"app_id,omitempty"` - TargetID int64 `json:"target_id,omitempty"` - Account User `json:"account,omitempty"` - AccessTokensURL string `json:"access_tokens_url,omitempty"` - RepositoriesURL string `json:"repositories_url,omitempty"` - HTMLURL string `json:"html_url,omitempty"` - TargetType string `json:"target_type,omitempty"` - SingleFileName string `json:"single_file_name,omitempty"` - RepositorySelection string `json:"repository_selection,omitempty"` - Events []string `json:"events,omitempty"` - Permissions InstallationPermissions `json:"permissions,omitempty"` - CreatedAt string `json:"created_at,omitempty"` - UpdatedAt string `json:"updated_at,omitempty"` -} - -// AppInstallationList represents the result of an AppInstallationList search. -type AppInstallationList struct { - Total int `json:"total_count,omitempty"` - Installations []AppInstallation `json:"installations,omitempty"` -} - -// AppInstallationToken is the response when retrieving an app installation -// token. -type AppInstallationToken struct { - Token string `json:"token,omitempty"` - ExpiresAt time.Time `json:"expires_at,omitempty"` - Permissions InstallationPermissions `json:"permissions,omitempty"` - Repositories []Repo `json:"repositories,omitempty"` -} - -// DirectoryContent contains information about a github directory. -// It include selected fields available in content records returned by -// GH "GET" method. See also: -// https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#get-repository-content -type DirectoryContent struct { - SHA string `json:"sha"` - Type string `json:"type"` - Name string `json:"name"` - Path string `json:"path"` -} - -// WorkflowRunEvent holds information about an `workflow_run` GitHub webhook event. -// see // https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#workflow_run -type WorkflowRunEvent struct { - Action string `json:"action"` - WorkflowRun WorkflowRun `json:"workflow_run"` - Workflow Workflow `json:"workflow"` - Repo *Repo `json:"repository"` - Organization Organization `json:"organization"` - Sender User `json:"sender"` - - // GUID is included in the header of the request received by GitHub. - GUID string -} - -type WorkflowRun struct { - ID int `json:"id"` - Name string `json:"name"` - NodeID string `json:"node_id"` - HeadBranch string `json:"head_branch"` - HeadSha string `json:"head_sha"` - RunNumber int `json:"run_number"` - Event string `json:"event"` - Status string `json:"status"` - Conclusion string `json:"conclusion"` - WorkflowID int `json:"workflow_id"` - CheckSuiteID int64 `json:"check_suite_id"` - CheckSuiteNodeID string `json:"check_suite_node_id"` - URL string `json:"url"` - PullRequests []PullRequest `json:"pull_requests"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - RunAttempt int `json:"run_attempt"` - RunStartedAt time.Time `json:"run_started_at"` - HeadCommit *Commit `json:"head_commit"` - Repository *Repo `json:"repository"` -} - -type Workflow struct { - ID int `json:"id"` - NodeID string `json:"node_id"` - Name string `json:"name"` - Path string `json:"path"` - State string `json:"state"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} diff --git a/third_party/k8s.io/test-infra/prow/github/webhooks.go b/third_party/k8s.io/test-infra/prow/github/webhooks.go deleted file mode 100644 index d71915e..0000000 --- a/third_party/k8s.io/test-infra/prow/github/webhooks.go +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package github - -import ( - "io" - "net/http" - - "github.com/sirupsen/logrus" -) - -// ValidateWebhook ensures that the provided request conforms to the -// format of a GitHub webhook and the payload can be validated with -// the provided hmac secret. It returns the event type, the event guid, -// the payload of the request, whether the webhook is valid or not, -// and finally the resultant HTTP status code -func ValidateWebhook(w http.ResponseWriter, r *http.Request, tokenGenerator func() []byte) (string, string, []byte, bool, int) { - defer r.Body.Close() - - // Header checks: It must be a POST with an event type and a signature. - if r.Method != http.MethodPost { - responseHTTPError(w, http.StatusMethodNotAllowed, "405 Method not allowed") - return "", "", nil, false, http.StatusMethodNotAllowed - } - eventType := r.Header.Get("X-GitHub-Event") - if eventType == "" { - responseHTTPError(w, http.StatusBadRequest, "400 Bad Request: Missing X-GitHub-Event Header") - return "", "", nil, false, http.StatusBadRequest - } - eventGUID := r.Header.Get("X-GitHub-Delivery") - if eventGUID == "" { - responseHTTPError(w, http.StatusBadRequest, "400 Bad Request: Missing X-GitHub-Delivery Header") - return "", "", nil, false, http.StatusBadRequest - } - sig := r.Header.Get("X-Hub-Signature") - if sig == "" { - responseHTTPError(w, http.StatusForbidden, "403 Forbidden: Missing X-Hub-Signature") - return "", "", nil, false, http.StatusForbidden - } - contentType := r.Header.Get("content-type") - if contentType != "application/json" { - responseHTTPError(w, http.StatusBadRequest, "400 Bad Request: Hook only accepts content-type: application/json - please reconfigure this hook on GitHub") - return "", "", nil, false, http.StatusBadRequest - } - payload, err := io.ReadAll(r.Body) - if err != nil { - responseHTTPError(w, http.StatusInternalServerError, "500 Internal Server Error: Failed to read request body") - return "", "", nil, false, http.StatusInternalServerError - } - // Validate the payload with our HMAC secret. - if !ValidatePayload(payload, sig, tokenGenerator) { - responseHTTPError(w, http.StatusForbidden, "403 Forbidden: Invalid X-Hub-Signature") - return "", "", nil, false, http.StatusForbidden - } - - return eventType, eventGUID, payload, true, http.StatusOK -} - -func responseHTTPError(w http.ResponseWriter, statusCode int, response string) { - logrus.WithFields(logrus.Fields{ - "response": response, - "status-code": statusCode, - }).Debug(response) - http.Error(w, response, statusCode) -} diff --git a/third_party/k8s.io/test-infra/prow/logrusutil/logrusutil.go b/third_party/k8s.io/test-infra/prow/logrusutil/logrusutil.go deleted file mode 100644 index 0a566a6..0000000 --- a/third_party/k8s.io/test-infra/prow/logrusutil/logrusutil.go +++ /dev/null @@ -1,153 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package logrusutil implements some helpers for using logrus -package logrusutil - -import ( - "sync" - "time" - - "github.com/sirupsen/logrus" - "k8s.io/apimachinery/pkg/util/sets" - - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/secretutil" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/version" -) - -// DefaultFieldsFormatter wraps another logrus.Formatter, injecting -// DefaultFields into each Format() call, existing fields are preserved -// if they have the same key -type DefaultFieldsFormatter struct { - WrappedFormatter logrus.Formatter - DefaultFields logrus.Fields - PrintLineNumber bool -} - -// Init set Logrus formatter -// if DefaultFieldsFormatter.wrappedFormatter is nil &logrus.JSONFormatter{} will be used instead -func Init(formatter *DefaultFieldsFormatter) { - if formatter == nil { - return - } - if formatter.WrappedFormatter == nil { - formatter.WrappedFormatter = &logrus.JSONFormatter{} - } - logrus.SetFormatter(formatter) - logrus.SetReportCaller(formatter.PrintLineNumber) -} - -// ComponentInit is a syntax sugar for easier Init -func ComponentInit() { - Init( - &DefaultFieldsFormatter{ - PrintLineNumber: true, - DefaultFields: logrus.Fields{"component": version.Name}, - }, - ) -} - -// Format implements logrus.Formatter's Format. We allocate a new Fields -// map in order to not modify the caller's Entry, as that is not a thread -// safe operation. -func (f *DefaultFieldsFormatter) Format(entry *logrus.Entry) ([]byte, error) { - data := make(logrus.Fields, len(entry.Data)+len(f.DefaultFields)+1) - // GCP's log collection expects a "severity" field instead of "level" - data["severity"] = entry.Level - for k, v := range f.DefaultFields { - data[k] = v - } - for k, v := range entry.Data { - data[k] = v - } - return f.WrappedFormatter.Format(&logrus.Entry{ - Logger: entry.Logger, - Data: data, - Time: entry.Time, - Level: entry.Level, - Message: entry.Message, - Caller: entry.Caller, - }) -} - -// CensoringFormatter represents a logrus formatter that -// can be used to censor sensitive information -type CensoringFormatter struct { - delegate logrus.Formatter - censorer secretutil.Censorer -} - -func (f CensoringFormatter) Format(entry *logrus.Entry) ([]byte, error) { - // Depending on the formatter in the delegate, the message will actually - // change shape/content - think of a message with newlines or quotes going - // to a JSON output. In order to catch this, we need to pre-censor the message. - message := []byte(entry.Message) - f.censorer.Censor(&message) - entry.Message = string(message) - raw, err := f.delegate.Format(entry) - if err != nil { - return raw, err - } - f.censorer.Censor(&raw) - return raw, nil -} - -// NewCensoringFormatter generates a `CensoringFormatter` with -// a formatter as delegate and a set of strings to censor -func NewCensoringFormatter(f logrus.Formatter, getSecrets func() sets.Set[string]) CensoringFormatter { - censorer := secretutil.NewCensorer() - censorer.Refresh(sets.List(getSecrets())...) - return NewFormatterWithCensor(f, censorer) -} - -// NewFormatterWithCensor generates a `CensoringFormatter` with -// a formatter as delegate and censorer to use -func NewFormatterWithCensor(f logrus.Formatter, censorer secretutil.Censorer) CensoringFormatter { - return CensoringFormatter{ - censorer: censorer, - delegate: f, - } -} - -// ThrottledWarnf prints a warning the first time called and if at most `period` has elapsed since the last time. -func ThrottledWarnf(last *time.Time, period time.Duration, format string, args ...interface{}) { - if throttleCheck(last, period) { - logrus.Warnf(format, args...) - } -} - -var throttleLock sync.RWMutex // Rare updates and concurrent readers, so reuse the same lock - -// throttleCheck returns true when first called or if -// at least `period` has elapsed since the last time it returned true. -func throttleCheck(last *time.Time, period time.Duration) bool { - // has it been at least `period` since we won the race? - throttleLock.RLock() - fresh := time.Since(*last) <= period - throttleLock.RUnlock() - if fresh { // event occurred too recently - return false - } - // Event is stale, will we win the race? - throttleLock.Lock() - defer throttleLock.Unlock() - now := time.Now() // Recalculate now, we might wait awhile for the lock - if now.Sub(*last) <= period { // Nope, we lost - return false - } - *last = now - return true -} diff --git a/third_party/k8s.io/test-infra/prow/secretutil/censor.go b/third_party/k8s.io/test-infra/prow/secretutil/censor.go deleted file mode 100644 index 91f9f29..0000000 --- a/third_party/k8s.io/test-infra/prow/secretutil/censor.go +++ /dev/null @@ -1,126 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package secretutil implements utilities to operate on secret data. -package secretutil - -import ( - "encoding/base64" - "strings" - "sync" - - "go4.org/bytereplacer" -) - -// Censorer knows how to replace sensitive data from input. -type Censorer interface { - // Censor will remove sensitive data previously registered with the Censorer - // from the input. This is thread-safe, will mutate the input and will never - // change the overall size of the input. - Censor(input *[]byte) -} - -func NewCensorer() *ReloadingCensorer { - return &ReloadingCensorer{ - RWMutex: &sync.RWMutex{}, - Replacer: bytereplacer.New(), - } -} - -type ReloadingCensorer struct { - *sync.RWMutex - *bytereplacer.Replacer - largestSecret int -} - -var _ Censorer = &ReloadingCensorer{} - -// Censor will remove sensitive data previously registered with the Censorer -// from the input. This is thread-safe, will mutate the input and will never -// change the overall size of the input. -// Censoring will attempt to be intelligent about how content is removed from -// the input - when the ReloadingCensorer is given secrets to censor, we: -// - handle the case where whitespace is needed to be trimmed -// - censor not only the plaintext representation of the secret but also -// the base64-encoded representation of it, as it's common for k8s -// Secrets to contain information in this way -func (c *ReloadingCensorer) Censor(input *[]byte) { - c.RLock() - // we know our replacer will never have to allocate, as our replacements - // are the same size as what they're replacing, so we can throw away - // the return value from Replace() - c.Replacer.Replace(*input) - c.RUnlock() -} - -// LargestSecret returns the size of the largest secret we will censor. -func (c *ReloadingCensorer) LargestSecret() int { - c.RLock() - defer c.RUnlock() - return c.largestSecret -} - -// RefreshBytes refreshes the set of secrets that we censor. -func (c *ReloadingCensorer) RefreshBytes(secrets ...[]byte) { - var asStrings []string - for _, secret := range secrets { - asStrings = append(asStrings, string(secret)) - } - c.Refresh(asStrings...) -} - -// Refresh refreshes the set of secrets that we censor. -func (c *ReloadingCensorer) Refresh(secrets ...string) { - var largestSecret int - var replacements []string - addReplacement := func(s string) { - replacements = append(replacements, s, strings.Repeat(`X`, len(s))) - if len(s) > largestSecret { - largestSecret = len(s) - } - } - for _, secret := range secrets { - toEncode := []string{secret} - if trimmed := strings.TrimSpace(secret); trimmed != secret { - secret = trimmed - toEncode = append(toEncode, trimmed) - } - if secret == "" { - continue - } - addReplacement(secret) - for _, item := range toEncode { - encoded := base64.StdEncoding.EncodeToString([]byte(item)) - addReplacement(encoded) - } - } - c.Lock() - c.Replacer = bytereplacer.New(replacements...) - c.largestSecret = largestSecret - c.Unlock() -} - -// AdaptCensorer returns a func that censors without touching the input, to -// be used in places where the previous behavior is required while migrations -// occur. -func AdaptCensorer(censorer Censorer) func(input []byte) []byte { - return func(input []byte) []byte { - output := make([]byte, len(input)) - copy(output, input) - censorer.Censor(&output) - return output - } -} diff --git a/third_party/k8s.io/test-infra/prow/secretutil/doc.go b/third_party/k8s.io/test-infra/prow/secretutil/doc.go deleted file mode 100644 index 828eef6..0000000 --- a/third_party/k8s.io/test-infra/prow/secretutil/doc.go +++ /dev/null @@ -1,18 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Package secretutil contains utilities for operating with secret data. -package secretutil diff --git a/third_party/k8s.io/test-infra/prow/simplifypath/simplify.go b/third_party/k8s.io/test-infra/prow/simplifypath/simplify.go deleted file mode 100644 index 3c4b2af..0000000 --- a/third_party/k8s.io/test-infra/prow/simplifypath/simplify.go +++ /dev/null @@ -1,123 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package simplifypath - -import ( - "strings" - - "github.com/sirupsen/logrus" -) - -const unmatchedPath = "unmatched" - -// Simplifier knows how to simplify a path -type Simplifier interface { - Simplify(path string) string -} - -// NewSimplifier builds a new simplifier for the tree -func NewSimplifier(tree Node) Simplifier { - return &simplifier{ - tree: tree, - } -} - -type simplifier struct { - tree Node -} - -// Simplify returns a variable-free path that can be used as label for prometheus metrics -func (s *simplifier) Simplify(path string) string { - splitPath := strings.Split(path, "/") - resolvedPath, matches := resolve(s.tree, splitPath) - if !matches { - logrus.WithField("path", path).Debug("Path not handled. This is a bug, please open an issue against the kubernetes/test-infra repository with this error message.") - return unmatchedPath - } - return resolvedPath -} - -type Node struct { - PathFragment - children []Node - // Greedy makes the node match all remnaining path elements as well - Greedy bool -} - -// PathFragment Interface for tree leafs to help resolve paths -type PathFragment interface { - Matches(part string) bool - Represent() string -} - -type literal string - -func (l literal) Matches(part string) bool { - return string(l) == part -} - -func (l literal) Represent() string { - return string(l) -} - -type variable string - -func (v variable) Matches(part string) bool { - return true -} - -func (v variable) Represent() string { - return ":" + string(v) -} - -func L(fragment string, children ...Node) Node { - return Node{ - PathFragment: literal(fragment), - children: children, - } -} - -func VGreedy(fragment string) Node { - return Node{ - PathFragment: variable(fragment), - Greedy: true, - } -} - -func V(fragment string, children ...Node) Node { - return Node{ - PathFragment: variable(fragment), - children: children, - } -} - -func resolve(parent Node, path []string) (string, bool) { - if !parent.Matches(path[0]) { - return "", false - } - representation := parent.Represent() - if len(path) == 1 || parent.Greedy { - return representation, true - } - for _, child := range parent.children { - suffix, matched := resolve(child, path[1:]) - if matched { - return strings.Join([]string{representation, suffix}, "/"), true - } - } - return "", false -} diff --git a/third_party/k8s.io/test-infra/prow/version/doc.go b/third_party/k8s.io/test-infra/prow/version/doc.go deleted file mode 100644 index c1147e4..0000000 --- a/third_party/k8s.io/test-infra/prow/version/doc.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// version holds variables that identify a Prow binary's name and version -package version - -import ( - "fmt" - "regexp" - "time" -) - -var ( - // Name is the colloquial identifier for the compiled component - Name = "unset" - // Version is a concatenation of the commit SHA and date for the build - Version = "0" - // reVersion is a regex expression for extracting build time. - // Version derived from "v${build_date}-${git_commit}" as in /hack/print-workspace-status.sh - reVersion = regexp.MustCompile(`v(\d+)-.*`) -) - -// UserAgent exposes the component's name and version for user-agent header -func UserAgent() string { - return Name + "/" + Version -} - -// UserAgentFor exposes the component's name and version for user-agent header -// while embedding the additional identifier -func UserAgentWithIdentifier(identifier string) string { - return Name + "." + identifier + "/" + Version -} - -// VersionTimestamp returns the timestamp of date derived from version -func VersionTimestamp() (int64, error) { - var ver int64 - m := reVersion.FindStringSubmatch(Version) - if len(m) < 2 { - return ver, fmt.Errorf("version expected to be in form 'v${build_date}-${git_commit}': %q", Version) - } - t, err := time.Parse("20060102", m[1]) - if err != nil { - return ver, err - } - return t.Unix(), nil -} diff --git a/third_party/k8s.io/test-infra/prow/version/metrics.go b/third_party/k8s.io/test-infra/prow/version/metrics.go deleted file mode 100644 index b0ac937..0000000 --- a/third_party/k8s.io/test-infra/prow/version/metrics.go +++ /dev/null @@ -1,52 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package version - -import ( - "github.com/prometheus/client_golang/prometheus" - "github.com/sirupsen/logrus" -) - -var ( - prowVersion = prometheus.NewGauge(prometheus.GaugeOpts{ - Name: "prow_version", - Help: "Prow version.", - }) -) - -func init() { - prometheus.MustRegister(prowVersion) - // For components that import current package, if `gatherProwVersion` if not - // explicitly called, there will be no metrics for `prow_version`, and when - // querying prometheus for `prow_version` the value will be zero, which - // is inaccurate. Since the version would not change for the running binary, - // doing this once when binary starts should be fine. - gatherProwVersion() -} - -// gatherProwVersion reports prow version -func gatherProwVersion() { - // record prow version - version, err := VersionTimestamp() - if err != nil { - // Not worth panicking - logrus.WithError(err).Debug("Failed to get version timestamp") - prowVersion.Set(-1) - } else { - prowVersion.Set(float64(version)) - } -} From b70d988dc25d42e346132b5e2eddf0d45065ba1d Mon Sep 17 00:00:00 2001 From: Stephen Augustus Date: Sat, 27 Apr 2024 06:57:00 -0400 Subject: [PATCH 2/2] go.mod: Migrate to sigs.k8s.io/prow and update imports Signed-off-by: Stephen Augustus --- cmd/main.go | 2 +- config/config_test.go | 4 +- go.mod | 98 +++++- go.sum | 615 ++++++++++++++++++++++++++++++++++++- internal/helpers/helper.go | 2 +- main.go | 2 +- options/merge/merge.go | 2 +- options/root/flags.go | 2 +- options/root/root.go | 2 +- org/dump.go | 4 +- org/members.go | 4 +- org/org.go | 4 +- org/org_test.go | 4 +- org/repos.go | 4 +- org/teams.go | 4 +- 15 files changed, 700 insertions(+), 53 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 358455c..aa1c38c 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -23,7 +23,7 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/cobra" - proworg "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/config/org" + proworg "sigs.k8s.io/prow/pkg/config/org" "sigs.k8s.io/release-utils/version" "github.com/uwu-tools/peribolos/internal/yaml" diff --git a/config/config_test.go b/config/config_test.go index c2fe6ec..141e58a 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -23,9 +23,9 @@ import ( "sort" "strings" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/config/org" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/github" "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/prow/pkg/config/org" + "sigs.k8s.io/prow/pkg/github" "github.com/uwu-tools/peribolos/internal/yaml" ) diff --git a/go.mod b/go.mod index ee4efc4..5312f20 100644 --- a/go.mod +++ b/go.mod @@ -2,43 +2,57 @@ module github.com/uwu-tools/peribolos go 1.21 -// Upstream is unmaintained. This fork introduces two important changes: -// - We log an error if writing a cache key fails e.g., because disk is full -// - We inject a header that allows ghproxy to detect if the response was revalidated or a cache miss -replace github.com/gregjones/httpcache => github.com/alvaroaleman/httpcache v0.0.0-20210618195546-ab9a1a3f8a38 - require ( github.com/airconduct/go-probot v0.0.4 github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/caarlos0/env/v7 v7.1.0 - github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 - github.com/gomodule/redigo v1.9.2 github.com/google/go-cmp v0.6.0 - github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc - github.com/peterbourgon/diskv v2.0.1+incompatible - github.com/prometheus/client_golang v1.19.0 github.com/sethvargo/go-githubactions v1.2.0 - github.com/shurcooL/githubv4 v0.0.0-20230305132112-efb623903184 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 - go4.org v0.0.0-20230225012048-214862532bf5 - golang.org/x/oauth2 v0.19.0 - golang.org/x/sync v0.7.0 k8s.io/apimachinery v0.29.3 - k8s.io/utils v0.0.0-20230726121419-3b25d923346b + sigs.k8s.io/prow v0.0.0-20240424151722-0a31882002b9 sigs.k8s.io/release-utils v0.8.1 sigs.k8s.io/yaml v1.4.0 ) require ( + cloud.google.com/go v0.110.2 // indirect + cloud.google.com/go/compute v1.20.1 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v0.13.0 // indirect + cloud.google.com/go/storage v1.29.0 // indirect + contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d // indirect + contrib.go.opencensus.io/exporter/prometheus v0.4.0 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.29 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/logger v0.2.1 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/GoogleCloudPlatform/testgrid v0.0.123 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect + github.com/andygrunwald/go-jira v1.14.0 // indirect + github.com/aws/aws-sdk-go v1.38.49 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blendle/zapdriver v1.3.1 // indirect github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 // indirect + github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cjwagner/httpcache v0.0.0-20230907212505-d4841bbad466 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect github.com/emicklei/go-restful-openapi/v2 v2.9.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/fatih/structs v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/fvbommel/sortorder v1.0.1 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/zapr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -46,39 +60,89 @@ require ( github.com/go-openapi/spec v0.20.9 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt v3.2.1+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/gomodule/redigo v1.9.2 // indirect github.com/google/btree v1.0.1 // indirect + github.com/google/gnostic v0.6.9 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-containerregistry v0.15.2 // indirect github.com/google/go-github/v48 v48.2.0 // indirect github.com/google/go-github/v53 v53.2.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.1-0.20210504230335-f78f29fc09ea // indirect + github.com/google/s2a-go v0.1.4 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/google/wire v0.4.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect + github.com/googleapis/gax-go v2.0.2+incompatible // indirect + github.com/googleapis/gax-go/v2 v2.11.0 // indirect + github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.2 // indirect + github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.19.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/statsd_exporter v0.21.0 // indirect + github.com/shurcooL/githubv4 v0.0.0-20230305132112-efb623903184 // indirect github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/tektoncd/pipeline v0.45.0 // indirect + github.com/trivago/tgo v1.0.7 // indirect github.com/xanzy/go-gitlab v0.90.0 // indirect - go.uber.org/multierr v1.10.0 // indirect + go.opencensus.io v0.24.0 // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.25.0 // indirect + go4.org v0.0.0-20230225012048-214862532bf5 // indirect + gocloud.dev v0.19.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/net v0.23.0 // indirect + golang.org/x/oauth2 v0.19.0 // indirect + golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/api v0.126.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/grpc v1.55.0 // indirect google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.25.9 // indirect + k8s.io/client-go v0.25.9 // indirect + k8s.io/component-base v0.25.4 // indirect k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + knative.dev/pkg v0.0.0-20230221145627-8efb3485adcf // indirect + sigs.k8s.io/controller-runtime v0.12.3 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) diff --git a/go.sum b/go.sum index 5476a02..a5f2a6b 100644 --- a/go.sum +++ b/go.sum @@ -1,176 +1,447 @@ +bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +bitbucket.org/creachadair/stringset v0.0.9/go.mod h1:t+4WcQ4+PXTa8aQdNKe40ZP6iwesoMFWAxPGd3UGjyY= 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.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= 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.44.3/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.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.73.0/go.mod h1:BkDh9dFvGjCitVw03TNjKbBxXNKULXXIq6orU6HrJ4Q= +cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA= +cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= 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/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= +cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= 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/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= 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/pubsub v1.9.1/go.mod h1:7QTUeCiy+P1dVPO8hHVbZSHDfibbgm1gbKyOVYnqb8g= 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= +cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7nillojlL5Ho= +cloud.google.com/go/storage v1.29.0 h1:6weCgzRvMg7lzuUurI4697AqIRPU1SvzHhynwpW31jI= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= +contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0= +contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d h1:LblfooH1lKOpp1hIhukktmSAxFkqMPFk9KR6iZ0MJNI= +contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY= +contrib.go.opencensus.io/exporter/prometheus v0.4.0 h1:0QfIkj9z/iVZgK31D9H9ohjjIDApI2GOPScCKwxedbs= +contrib.go.opencensus.io/exporter/prometheus v0.4.0/go.mod h1:o7cosnyfuPVK0tB8q0QmaQNhGnptITnPQB+z1+qeFB0= +contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw= +contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= +contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU= +github.com/Azure/azure-pipeline-go v0.2.1 h1:OLBdZJ3yvOn2MezlWvbrBMTEUQC72zAftRZOMdj5HYo= +github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= +github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0= +github.com/Azure/azure-storage-blob-go v0.8.0 h1:53qhf0Oxa0nOjgbDeeYPUeyiNmafAFEY95rZLK0Tj6o= +github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0= +github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= +github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs= +github.com/Azure/go-autorest/autorest/adal v0.9.22 h1:/GblQdIudfEM3AWWZ0mrYJQSd7JS4S/Mbzh6F0ov0Xc= +github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= +github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +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/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo= +github.com/GoogleCloudPlatform/testgrid v0.0.123 h1:S5LE2LjkPsUlyt7blkIgwajiUfgFzv5s17+TkyKDfnI= +github.com/GoogleCloudPlatform/testgrid v0.0.123/go.mod h1:4Ojwl21NNySkM1rG8hT9K2bugPX9fIrc2hC+GHegLR8= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= +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/airconduct/go-probot v0.0.4 h1:8PXH1KXLvjVVc3EtJv4STw45h51w5ew8nKAO+DuB+54= github.com/airconduct/go-probot v0.0.4/go.mod h1:HwYbw0j0rBNVgFw0j3Gmy3XQXCTJlF3FEQcqirqUjqA= -github.com/alvaroaleman/httpcache v0.0.0-20210618195546-ab9a1a3f8a38 h1:ML+zGuJv9er8cg0N8D8edCNOwqI/Kb3WoV6VsIV93SU= -github.com/alvaroaleman/httpcache v0.0.0-20210618195546-ab9a1a3f8a38/go.mod h1:R4sf/p+3TZ6dvs7BxjxjozS7lIpzKtuS4jmQt4egJfU= +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/andygrunwald/go-jira v1.14.0 h1:7GT/3qhar2dGJ0kq8w0d63liNyHOnxZsUZ9Pe4+AKBI= +github.com/andygrunwald/go-jira v1.14.0/go.mod h1:KMo2f4DgMZA1C9FdImuLc04x4WQhn5derQpnsuBFgqE= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.38.49 h1:E31vxjCe6a5I+mJLmUGaZobiWmg9KdWaud9IfceYeYQ= +github.com/aws/aws-sdk-go v1.38.49/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +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/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= +github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 h1:IRY7Xy588KylkoycsUhFpW7cdGpy5Y5BPsz4IfuJtGk= github.com/bradleyfalzon/ghinstallation/v2 v2.6.0/go.mod h1:oQ3etOwN3TRH4EwgW5/7MxSVMGlMlzG/O8TU7eYdoSk= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg= github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E= +github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/cjwagner/httpcache v0.0.0-20230907212505-d4841bbad466 h1:eUjwn08FDjbj8vBM31026tjBraJCu+qpDvo/q0EAvQk= +github.com/cjwagner/httpcache v0.0.0-20230907212505-d4841bbad466/go.mod h1:f7xZ2fRr8CqTp834KCxLW2pOXC/raqwhTbEvtxu/lRo= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudevents/sdk-go/v2 v2.13.0 h1:2zxDS8RyY1/wVPULGGbdgniGXSzLaRJVl136fLXGsYw= +github.com/cloudevents/sdk-go/v2 v2.13.0/go.mod h1:xDmKfzNjM8gBvjaF8ijFjM1VYOVUEeUfapHMUX1T5To= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +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-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creachadair/staticfile v0.1.3/go.mod h1:a3qySzCIXEprDGxk6tSxSI+dBBdLzqeBOMhZ+o2d3pM= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +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/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-openapi/v2 v2.9.1 h1:Of8B1rXdG81il5TTiSY+9Qrh7pYOr8aLdynHIpvo7fM= github.com/emicklei/go-restful-openapi/v2 v2.9.1/go.mod h1:VKNgZyYviM1hnyrjD9RDzP2RuE94xTXxV+u6MGN4v4k= github.com/emicklei/go-restful/v3 v3.7.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +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.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/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 v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= 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.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE= +github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 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-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-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +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-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +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 v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= 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/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 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 v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= +github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= 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/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.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.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s= github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw= 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 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.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.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.15.2 h1:MMkSh+tjSdnmJZO7ljvEqV1DjfekB6VUEAZgy3a+TQE= +github.com/google/go-containerregistry v0.15.2/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q= github.com/google/go-github/v48 v48.2.0 h1:68puzySE6WqUY9KWmpOsDEQfDZsso98rT6pZcz9HqcE= github.com/google/go-github/v48 v48.2.0/go.mod h1:dDlehKBDo850ZPvCTK0sEqTCVWcrGl2LcDiajkYi89Y= github.com/google/go-github/v53 v53.2.0 h1:wvz3FyF53v4BK+AsnvCmeNhf8AkTaeh2SoYu/XUvTtI= github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao= +github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/go-replayers/grpcreplay v0.1.0 h1:eNb1y9rZFmY4ax45uEEECSa8fsxGRU+8Bil52ASAwic= +github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE= +github.com/google/go-replayers/httpreplay v0.1.0 h1:AX7FUb4BjrrzNvblr/OlgwrmFiep6soj5K2QSDW7BGk= +github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no= 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.1-0.20210504230335-f78f29fc09ea h1:VcIYpAGBae3Z6BVncE0OnTE/ZjlDXqtYhOZky88neLM= github.com/google/gofuzz v1.2.1-0.20210504230335-f78f29fc09ea/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible h1:xmapqc1AyLoB+ddYT6r04bD9lIjlOqGaREovi0SzFaE= +github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+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/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= 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-20200905233945-acf8798be1f7/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-20201117184057-ae444373da19/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= +github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +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.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s= +github.com/google/wire v0.4.0 h1:kXcsA/rIGzJImVqPdhfnr6q0xsS9gU0515q1EPpJ9fE= +github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= +github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww= +github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= 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/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= +github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 h1:lLT7ZLSzGLI08vc9cpd+tYmNWjdKDqyr/2L+f6U12Fk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE= github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v1.3.1 h1:vDwF1DFNZhntP4DAjuTpOw3uEgMUpXh1pB5fW9DqHpo= +github.com/hashicorp/go-hclog v1.3.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= 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/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 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= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +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-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 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.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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -178,40 +449,89 @@ github.com/kr/pty v1.1.1/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/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +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.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149 h1:HfxbT6/JcvIljmERptWhwa8XzP7H3T+Z2N26gTsaDaA= +github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +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.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +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.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +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/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +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.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/statsd_exporter v0.21.0 h1:hA05Q5RFeIjgwKIYEdFd59xu5Wwaznf33yKI+pyX6T8= +github.com/prometheus/statsd_exporter v0.21.0/go.mod h1:rbT83sZq2V+p73lHhPZfMc3MLCHmSHelCh9hSGYNLTQ= +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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= @@ -223,14 +543,21 @@ github.com/shurcooL/githubv4 v0.0.0-20230305132112-efb623903184 h1:QwdHPs+b2raoq github.com/shurcooL/githubv4 v0.0.0-20230305132112-efb623903184/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 h1:B1PEwpArrNp4dkQrfxh/abbBAOZBVp0ds+fBEOUOqOc= github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +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.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/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/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -244,36 +571,62 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tektoncd/pipeline v0.45.0 h1:Hv9kyutu5GWGXKtcMrM7PXdAULgeQc0F2HWDNg+jo5c= +github.com/tektoncd/pipeline v0.45.0/go.mod h1:20Xs6qk3BTpsLHYWEtLNPM44XKqNH5jYwoomXHOGNs8= +github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM= +github.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc= github.com/xanzy/go-gitlab v0.90.0 h1:j8ZUHfLfXdnC+B8njeNaW/kM44c1zw8fiuNj7D+qQN8= github.com/xanzy/go-gitlab v0.90.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +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.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= 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/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +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/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +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/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= +gocloud.dev v0.19.0 h1:EDRyaRAnMGSq/QBto486gWFxMLczAfIYUmusV7XLNBM= +gocloud.dev v0.19.0/go.mod h1:SmKwiR8YwIMMJvQBKLsC3fHNyMwXLw3PMDO+VVteJMI= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= @@ -284,7 +637,9 @@ golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm0 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= @@ -296,19 +651,28 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl 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.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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-20181114220301-adae6a3d119a/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= @@ -316,15 +680,37 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn 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-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-20191002035440-2ec189313ef0/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-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-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-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= @@ -335,9 +721,14 @@ golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 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-20190402181905-9f3314589c9a/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-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= @@ -347,36 +738,69 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ 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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-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-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-20191204072324-ce4227a45e2e/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-20200122134326-e047566fdf82/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-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-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-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20211007075335-d3039528d8ac/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -385,18 +809,24 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= 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/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/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -405,14 +835,18 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-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-20190422233926-fe54fb35175b/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= @@ -426,13 +860,38 @@ golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtn 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-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-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-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20200918232735-d647fc253266/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-20201202200335-bef1c476418a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +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.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= @@ -442,9 +901,13 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= 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= @@ -452,17 +915,37 @@ google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb 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.25.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.31.0/go.mod h1:CL+9IBCa2WWU6gRuBWaKqGWLFFwbEUXkfeMkHLQWYWo= +google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +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.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= +google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= 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/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-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/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= @@ -470,36 +953,107 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx 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-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-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-20200831141814-d751682dd103/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-20200914193844-75d14daec038/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5/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-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201203001206-6486ece9c497/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201209185603-f92720507ed4/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao= +google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= 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.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.32.0/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.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +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.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/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +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/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.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-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/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= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -507,20 +1061,49 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh 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.19.13/go.mod h1:xNpkpfCFYnVjbmKqIInsBPqUROkHefMc42rUA92XkdU= +k8s.io/api v0.25.9 h1:XuJ2bz2F52jZmp3YjUcp/pozH8kY1BlBHdXnoOXBP3U= +k8s.io/api v0.25.9/go.mod h1:9YRWzD0cRHzfsnf9e5OQsQ4Un6cbZ//Xv3jo44YKm2Y= +k8s.io/apiextensions-apiserver v0.25.4 h1:7hu9pF+xikxQuQZ7/30z/qxIPZc2J1lFElPtr7f+B6U= +k8s.io/apiextensions-apiserver v0.25.4/go.mod h1:bkSGki5YBoZWdn5pWtNIdGvDrrsRWlmnvl9a+tAw5vQ= +k8s.io/apimachinery v0.19.13/go.mod h1:RMyblyny2ZcDQ/oVE+lC31u7XTHUaSXEK2IhgtwGxfc= k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU= k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU= +k8s.io/client-go v0.25.9 h1:U0S3nc71NRfHXiA0utyCkPt3Mv1SWpQw0g5VfBCv5xg= +k8s.io/client-go v0.25.9/go.mod h1:tmPyOtpbbkneXj65EYZ4sXun1BE/2F2XlRABVj9CBgc= +k8s.io/component-base v0.25.4 h1:n1bjg9Yt+G1C0WnIDJmg2fo6wbEU1UGMRiQSjmj7hNQ= +k8s.io/component-base v0.25.4/go.mod h1:nnZJU8OP13PJEm6/p5V2ztgX2oyteIaAGKGMYb2L2cY= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +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.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +knative.dev/pkg v0.0.0-20230221145627-8efb3485adcf h1:TwvZFDpkyqpK2OCAwvNGV2Zjk14FzIh8X8Ci/du3jYI= +knative.dev/pkg v0.0.0-20230221145627-8efb3485adcf/go.mod h1:VO/fcEsq43seuONRQxZyftWHjpMabYzRHDtpSEQ/eoQ= +pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= 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/controller-runtime v0.12.3 h1:FCM8xeY/FI8hoAfh/V4XbbYMY20gElh9yh+A98usMio= +sigs.k8s.io/controller-runtime v0.12.3/go.mod h1:qKsk4WE6zW2Hfj0G4v10EnNB2jMG1C+NTb8h+DwCoU0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/prow v0.0.0-20240424151722-0a31882002b9 h1:ChRaWw3h3pWs5yAR93ukGDV1ffgH3XkjYMjv67925PU= +sigs.k8s.io/prow v0.0.0-20240424151722-0a31882002b9/go.mod h1:bikR7JOKUm6nvbhURQxY1HNrXir/99xaOQ+t1SKqrf8= sigs.k8s.io/release-utils v0.8.1 h1:qSA9p3vZzO6RAq7zvzupCZjR29+n3NK9DSJPe9bSf7w= sigs.k8s.io/release-utils v0.8.1/go.mod h1:vrQ3eR1VmudgX4OUwr4pUZEkYLRms9bdbv06mr3kchQ= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/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.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/helpers/helper.go b/internal/helpers/helper.go index c5673e8..4282550 100644 --- a/internal/helpers/helper.go +++ b/internal/helpers/helper.go @@ -21,7 +21,7 @@ import ( "os" "strings" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/config/org" + "sigs.k8s.io/prow/pkg/config/org" "github.com/uwu-tools/peribolos/internal/yaml" ) diff --git a/main.go b/main.go index 811ded7..d84794d 100644 --- a/main.go +++ b/main.go @@ -20,7 +20,7 @@ import ( "fmt" "github.com/sirupsen/logrus" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/logrusutil" + "sigs.k8s.io/prow/pkg/logrusutil" "github.com/uwu-tools/peribolos/cmd" "github.com/uwu-tools/peribolos/options/root" diff --git a/options/merge/merge.go b/options/merge/merge.go index bd0dace..d68927a 100644 --- a/options/merge/merge.go +++ b/options/merge/merge.go @@ -23,7 +23,7 @@ import ( "path/filepath" "github.com/sirupsen/logrus" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/config/org" + "sigs.k8s.io/prow/pkg/config/org" "github.com/uwu-tools/peribolos/internal/helpers" "github.com/uwu-tools/peribolos/internal/yaml" diff --git a/options/root/flags.go b/options/root/flags.go index b8ad4d3..e338722 100644 --- a/options/root/flags.go +++ b/options/root/flags.go @@ -22,7 +22,7 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/flagutil" + "sigs.k8s.io/prow/pkg/flagutil" ) const ( diff --git a/options/root/root.go b/options/root/root.go index db874d1..207f3dc 100644 --- a/options/root/root.go +++ b/options/root/root.go @@ -25,7 +25,7 @@ import ( "github.com/caarlos0/env/v7" actions "github.com/sethvargo/go-githubactions" "github.com/sirupsen/logrus" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/flagutil" + "sigs.k8s.io/prow/pkg/flagutil" ) const ( diff --git a/org/dump.go b/org/dump.go index 977cc4b..b54e375 100644 --- a/org/dump.go +++ b/org/dump.go @@ -20,8 +20,8 @@ import ( "fmt" "github.com/sirupsen/logrus" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/config/org" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/github" + "sigs.k8s.io/prow/pkg/config/org" + "sigs.k8s.io/prow/pkg/github" ) type dumpClient interface { diff --git a/org/members.go b/org/members.go index aec2a86..fd95389 100644 --- a/org/members.go +++ b/org/members.go @@ -21,10 +21,10 @@ import ( "strings" "github.com/sirupsen/logrus" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/config/org" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/github" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/prow/pkg/config/org" + "sigs.k8s.io/prow/pkg/github" "github.com/uwu-tools/peribolos/options/root" ) diff --git a/org/org.go b/org/org.go index 4f46719..4b7120f 100644 --- a/org/org.go +++ b/org/org.go @@ -20,9 +20,9 @@ import ( "fmt" "github.com/sirupsen/logrus" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/config/org" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/github" "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/prow/pkg/config/org" + "sigs.k8s.io/prow/pkg/github" "github.com/uwu-tools/peribolos/options/root" ) diff --git a/org/org_test.go b/org/org_test.go index 6a6d12c..8ee0db8 100644 --- a/org/org_test.go +++ b/org/org_test.go @@ -24,9 +24,9 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/config/org" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/github" "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/prow/pkg/config/org" + "sigs.k8s.io/prow/pkg/github" "github.com/uwu-tools/peribolos/options/root" ) diff --git a/org/repos.go b/org/repos.go index 6fd75a6..0febd28 100644 --- a/org/repos.go +++ b/org/repos.go @@ -21,9 +21,9 @@ import ( "strings" "github.com/sirupsen/logrus" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/config/org" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/github" utilerrors "k8s.io/apimachinery/pkg/util/errors" + "sigs.k8s.io/prow/pkg/config/org" + "sigs.k8s.io/prow/pkg/github" "github.com/uwu-tools/peribolos/options/root" ) diff --git a/org/teams.go b/org/teams.go index e9f8a27..ed4a603 100644 --- a/org/teams.go +++ b/org/teams.go @@ -21,10 +21,10 @@ import ( "strings" "github.com/sirupsen/logrus" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/config/org" - "github.com/uwu-tools/peribolos/third_party/k8s.io/test-infra/prow/github" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/prow/pkg/config/org" + "sigs.k8s.io/prow/pkg/github" "github.com/uwu-tools/peribolos/options/root" )