Compare commits

...

27 Commits

Author SHA1 Message Date
dependabot[bot]
a82b883276 chore(deps): bump github.com/coreos/go-oidc/v3 from 3.4.0 to 3.5.0 (#404)
Bumps [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/coreos/go-oidc/releases)
- [Commits](https://github.com/coreos/go-oidc/compare/v3.4.0...v3.5.0)

---
updated-dependencies:
- dependency-name: github.com/coreos/go-oidc/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-22 16:54:53 -05:00
TwiN
24e207c0c6 fix(metrics): Disable compression on prometheus/client_golang's handler
Fixes #406

Root cause seems to be that promhttp.Handler() has its own gzip compression https://github.com/prometheus/client_golang/issues/622
2023-01-19 23:06:38 -05:00
TwiN
90bb8f7b5f test: Don't run tests in parallel 2023-01-19 01:53:52 -05:00
TwiN
0db92f46da test: Add several tests for numerical conditions 2023-01-19 01:37:21 -05:00
dependabot[bot]
0ffa03f42d chore(deps): bump github.com/TwiN/deepmerge from 0.1.0 to 0.2.0 (#401)
Bumps [github.com/TwiN/deepmerge](https://github.com/TwiN/deepmerge) from 0.1.0 to 0.2.0.
- [Release notes](https://github.com/TwiN/deepmerge/releases)
- [Commits](https://github.com/TwiN/deepmerge/compare/v0.1.0...v0.2.0)

---
updated-dependencies:
- dependency-name: github.com/TwiN/deepmerge
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-11 01:05:17 -05:00
TwiN
e61a42220c fix: Log GATUS_CONFIG_FILE deprecation message only if it's non-empty 2023-01-10 19:04:19 -05:00
TwiN
78dccc90e1 fix(#400): Use proper configuration path when iterating over options 2023-01-10 00:24:56 -05:00
TwiN
6bdd3c94fe chore: Add deprecation message for GATUS_CONFIG_FILE 2023-01-09 23:24:20 -05:00
TwiN
4225d22369 docs: Add example of docker-compose with multiple configuration files 2023-01-08 18:34:13 -05:00
TwiN
3059e3e028 feat: Support multiple configuration files (#396)
* Revert "Revert "feat: Support multiple configuration files" (#395)"

This reverts commit 87740e74a6.

* feat: Properly implement support for config directory
2023-01-08 17:53:37 -05:00
TwiN
87740e74a6 Revert "feat: Support multiple configuration files" (#395)
Revert "feat: Support multiple configuration files (#389)"

This reverts commit 8e14302765.
2023-01-07 03:45:43 -05:00
Henning Janßen
8e14302765 feat: Support multiple configuration files (#389)
* Allow configuration to be distributed

* catch iteration errors when collecting config files

* rm unused func

* Fix suffix check for config loading

* test configuration loading

* GATUS_CONFIG_PATH can be a file or a directory now

* Add deprecation note

* Fix cs

Co-authored-by: TwiN <twin@linux.com>

* cs fixes

Co-authored-by: TwiN <twin@linux.com>

* cs fixes

Co-authored-by: TwiN <twin@linux.com>

* cs fixes

Co-authored-by: TwiN <twin@linux.com>

* cs + rm useless line

Co-authored-by: TwiN <twin@linux.com>

* Update config/config.go

Co-authored-by: TwiN <twin@linux.com>
2023-01-06 23:46:19 -05:00
TwiN
844f417ea1 docs: Update sponsor list 2023-01-06 20:06:56 -05:00
TwiN
2f7f782f11 docs: Improve documentation and add ghcr Docker image link 2023-01-06 20:04:21 -05:00
dependabot[bot]
37bea336ca chore(deps): bump github.com/TwiN/health from 1.5.0 to 1.6.0 (#394)
Bumps [github.com/TwiN/health](https://github.com/TwiN/health) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/TwiN/health/releases)
- [Commits](https://github.com/TwiN/health/compare/v1.5.0...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/TwiN/health
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-04 01:37:24 -05:00
TwiN
616a654b27 fix: Compress everything with Gzip 2022-12-30 21:37:52 -05:00
TwiN
a1c8422c2f ci: Make release also push latest tag 2022-12-23 10:00:17 -05:00
TwiN
947173bf71 fix: Prevent jsonpath from causing panic when body is expected to be array but isn't (#392)
* fix: Prevent jsonpath from causing panic when body is expected to be array but isn't

Fixes #391
2022-12-23 09:55:17 -05:00
TwiN
a81a83e2d4 docs: Update example 2022-12-22 17:38:27 -05:00
Salim B
4599fe4da7 Clarify description of len() function 2022-12-21 22:37:49 -05:00
TwiN
19e90cdf31 test(condition): Add has-key-of-map test case 2022-12-17 23:58:01 -05:00
TwiN
ecc0636a59 feat(alerting): Implement GitHub alerting provider 2022-12-15 23:38:44 -05:00
TwiN
27502acd10 chore: Improve oauth2 configuration error 2022-12-15 23:25:37 -05:00
TwiN
51255e33ea refactor(alerting): Use reflection to retrieve provider based on alert type 2022-12-15 21:37:34 -05:00
TwiN
be0962112e refactor: Remove unnecessary logs 2022-12-15 21:37:34 -05:00
TwiN
dfcea93080 fix(alerting): Use reflection to set invalid providers to nil instead of re-validating on every alert trigger/resolve 2022-12-15 21:37:34 -05:00
dependabot[bot]
a5f135c675 chore(deps): bump github.com/miekg/dns from 1.1.43 to 1.1.50 (#385)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.43 to 1.1.50.
- [Release notes](https://github.com/miekg/dns/releases)
- [Changelog](https://github.com/miekg/dns/blob/master/Makefile.release)
- [Commits](https://github.com/miekg/dns/compare/v1.1.43...v1.1.50)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-14 23:51:19 -05:00
457 changed files with 80141 additions and 8234 deletions

View File

@@ -0,0 +1,21 @@
endpoints:
- name: check-if-api-is-healthy
group: backend
url: "https://twin.sh/health"
interval: 5m
conditions:
- "[STATUS] == 200"
- "[BODY].status == UP"
- "[RESPONSE_TIME] < 1000"
- name: check-if-website-is-pingable
url: "icmp://example.org"
interval: 1m
conditions:
- "[CONNECTED] == true"
- name: check-domain-expiration
url: "https://example.org"
interval: 6h
conditions:
- "[DOMAIN_EXPIRATION] > 720h"

View File

@@ -0,0 +1,8 @@
endpoints:
- name: make-sure-html-rendering-works
group: frontend
url: "https://example.org"
interval: 5m
conditions:
- "[STATUS] == 200"
- "[BODY] == pat(*<h1>Example Domain</h1>*)" # Check for header in HTML page

View File

@@ -0,0 +1,8 @@
metrics: true
debug: false
ui:
header: Example Company
link: https://example.org
buttons:
- name: "Home"
link: "https://example.org"

View File

@@ -0,0 +1,10 @@
version: "3.8"
services:
gatus:
image: twinproduction/gatus:latest
ports:
- "8080:8080"
environment:
- GATUS_CONFIG_PATH=/config
volumes:
- ./config:/config

BIN
.github/assets/github-alerts.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -31,4 +31,7 @@ jobs:
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
pull: true
push: true
tags: ${{ env.IMAGE_REPOSITORY }}:${{ env.RELEASE }},${{ env.IMAGE_REPOSITORY }}:stable
tags: |
${{ env.IMAGE_REPOSITORY }}:${{ env.RELEASE }}
${{ env.IMAGE_REPOSITORY }}:stable
${{ env.IMAGE_REPOSITORY }}:latest

View File

@@ -28,4 +28,7 @@ jobs:
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
pull: true
push: true
tags: ${{ env.IMAGE_REPOSITORY }}:${{ env.RELEASE }},${{ env.IMAGE_REPOSITORY }}:stable
tags: |
${{ env.IMAGE_REPOSITORY }}:${{ env.RELEASE }}
${{ env.IMAGE_REPOSITORY }}:stable
${{ env.IMAGE_REPOSITORY }}:latest

View File

@@ -3,12 +3,14 @@ on:
pull_request:
paths-ignore:
- '*.md'
- '.examples/*'
push:
branches:
- master
paths-ignore:
- '*.md'
- '.github/*'
- '.examples/*'
jobs:
test:
runs-on: ubuntu-latest

View File

@@ -7,7 +7,7 @@ install:
go build -mod vendor -o $(BINARY) .
run:
GATUS_CONFIG_FILE=./config.yaml ./$(BINARY)
GATUS_CONFIG_PATH=./config.yaml ./$(BINARY)
clean:
rm $(BINARY)

122
README.md
View File

@@ -21,15 +21,19 @@ core applications: https://status.twin.sh/
```console
docker run -p 8080:8080 --name gatus twinproduction/gatus
```
You can also use GitHub Container Registry if you prefer:
```console
docker run -p 8080:8080 --name gatus ghcr.io/twin/gatus
```
For more details, see [Usage](#usage)
</details>
> ❤ Like this project? Please consider [sponsoring me](https://github.com/sponsors/TwiN).
![Gatus dashboard](.github/assets/dashboard-dark.png)
Have any feedback or questions? [Create a discussion](https://github.com/TwiN/gatus/discussions/new).
Like this project? Please consider [sponsoring me](https://github.com/sponsors/TwiN).
## Table of Contents
- [Why Gatus?](#why-gatus)
@@ -44,6 +48,7 @@ Like this project? Please consider [sponsoring me](https://github.com/sponsors/T
- [Alerting](#alerting)
- [Configuring Discord alerts](#configuring-discord-alerts)
- [Configuring Email alerts](#configuring-email-alerts)
- [Configuring GitHub alerts](#configuring-github-alerts)
- [Configuring Google Chat alerts](#configuring-google-chat-alerts)
- [Configuring Matrix alerts](#configuring-matrix-alerts)
- [Configuring Mattermost alerts](#configuring-mattermost-alerts)
@@ -128,9 +133,19 @@ The main features of Gatus are:
## Usage
By default, the configuration file is expected to be at `config/config.yaml`.
You can specify a custom path by setting the `GATUS_CONFIG_FILE` environment variable.
<details>
<summary><b>Quick start</b></summary>
```console
docker run -p 8080:8080 --name gatus twinproduction/gatus
```
You can also use GitHub Container Registry if you prefer:
```console
docker run -p 8080:8080 --name gatus ghcr.io/twin/gatus
```
If you want to create your own configuration, see [Docker](#docker) for information on how to mount a configuration file.
</details>
Here's a simple example:
```yaml
@@ -142,18 +157,31 @@ endpoints:
- "[STATUS] == 200" # Status must be 200
- "[BODY].status == UP" # The json path "$.status" must be equal to UP
- "[RESPONSE_TIME] < 300" # Response time must be under 300ms
- name: example
- name: make-sure-header-is-rendered
url: "https://example.org/"
interval: 60s
conditions:
- "[STATUS] == 200"
- "[STATUS] == 200" # Status must be 200
- "[BODY] == pat(*<h1>Example Domain</h1>*)" # Body must contain the specified header
```
This example would look similar to this:
![Simple example](.github/assets/example.png)
Note that you can also use environment variables in the configuration file (e.g. `$DOMAIN`, `${DOMAIN}`)
By default, the configuration file is expected to be at `config/config.yaml`.
You can specify a custom path by setting the `GATUS_CONFIG_PATH` environment variable.
If `GATUS_CONFIG_PATH` points to a directory, all `*.yaml` and `*.yml` files inside said directory and its
subdirectories are merged like so:
- All maps/objects are deep merged (i.e. you could define `alerting.slack` in one file and `alerting.pagerduty` in another file)
- All slices/arrays are appended (i.e. you can define `endpoints` in multiple files and each endpoint will be added to the final list of endpoints)
- Parameters with a primitive value (e.g. `debug`, `metrics`, `alerting.slack.webhook-url`, etc.) may only be defined once to forcefully avoid any ambiguity
- To clarify, this also means that you could not define `alerting.slack.webhook-url` in two files with different values. All files are merged into one before they are processed. This is by design.
> 💡 You can also use environment variables in the configuration file (e.g. `$DOMAIN`, `${DOMAIN}`)
If you want to test it locally, see [Docker](#docker).
@@ -208,6 +236,7 @@ If you want to test it locally, see [Docker](#docker).
| `ui.buttons[].link` | Link to open when the button is clicked. | Required `""` |
| `maintenance` | [Maintenance configuration](#maintenance). | `{}` |
### Conditions
Here are some examples of conditions you can use:
@@ -249,14 +278,14 @@ Here are some examples of conditions you can use:
#### Functions
| Function | Description | Example |
|:---------|:---------------------------------------------------------------------------------------------------------------|:-----------------------------------|
| `len` | Returns the length of the object/slice. Works only with the `[BODY]` placeholder. | `len([BODY].username) > 8` |
| `has` | Returns `true` or `false` based on whether a given path is valid. Works only with the `[BODY]` placeholder. | `has([BODY].errors) == false` |
| `pat` | Specifies that the string passed as parameter should be evaluated as a pattern. Works only with `==` and `!=`. | `[IP] == pat(192.168.*)` |
| `any` | Specifies that any one of the values passed as parameters is a valid value. Works only with `==` and `!=`. | `[BODY].ip == any(127.0.0.1, ::1)` |
| Function | Description | Example |
|:---------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------|
| `len` | If the given path leads to an array, returns its length. Otherwise, the JSON at the given path is minified and converted to a string, and the resulting number of characters is returned. Works only with the `[BODY]` placeholder. | `len([BODY].username) > 8` |
| `has` | Returns `true` or `false` based on whether a given path is valid. Works only with the `[BODY]` placeholder. | `has([BODY].errors) == false` |
| `pat` | Specifies that the string passed as parameter should be evaluated as a pattern. Works only with `==` and `!=`. | `[IP] == pat(192.168.*)` |
| `any` | Specifies that any one of the values passed as parameters is a valid value. Works only with `==` and `!=`. | `[BODY].ip == any(127.0.0.1, ::1)` |
**NOTE**: Use `pat` only when you need to. `[STATUS] == pat(2*)` is a lot more expensive than `[STATUS] < 300`.
> 💡 Use `pat` only when you need to. `[STATUS] == pat(2*)` is a lot more expensive than `[STATUS] < 300`.
### Storage
@@ -307,7 +336,7 @@ the client used to send the request.
| `client.oauth2.client-secret` | The client secret which should be used for the `Client credentials flow` | required `""` |
| `client.oauth2.scopes[]` | A list of `scopes` which should be used for the `Client credentials flow`. | required `[""]` |
Note that some of these parameters are ignored based on the type of endpoint. For instance, there's no certificate involved
> 📝 Some of these parameters are ignored based on the type of endpoint. For instance, there's no certificate involved
in ICMP requests (ping), therefore, setting `client.insecure` to `true` for an endpoint of that type will not do anything.
This default configuration is as follows:
@@ -363,7 +392,7 @@ endpoints:
Gatus supports multiple alerting providers, such as Slack and PagerDuty, and supports different alerts for each
individual endpoints with configurable descriptions and thresholds.
Note that if an alerting provider is not properly configured, all alerts configured with the provider's type will be
> 📝 If an alerting provider is not properly configured, all alerts configured with the provider's type will be
ignored.
| Parameter | Description | Default |
@@ -371,6 +400,7 @@ ignored.
| `alerting.custom` | Configuration for custom actions on failure or alerts. <br />See [Configuring Custom alerts](#configuring-custom-alerts). | `{}` |
| `alerting.discord` | Configuration for alerts of type `discord`. <br />See [Configuring Discord alerts](#configuring-discord-alerts). | `{}` |
| `alerting.email` | Configuration for alerts of type `email`. <br />See [Configuring Email alerts](#configuring-email-alerts). | `{}` |
| `alerting.github` | Configuration for alerts of type `github`. <br />See [Configuring GitHub alerts](#configuring-github-alerts). | `{}` |
| `alerting.googlechat` | Configuration for alerts of type `googlechat`. <br />See [Configuring Google Chat alerts](#configuring-google-chat-alerts). | `{}` |
| `alerting.matrix` | Configuration for alerts of type `matrix`. <br />See [Configuring Matrix alerts](#configuring-matrix-alerts). | `{}` |
| `alerting.mattermost` | Configuration for alerts of type `mattermost`. <br />See [Configuring Mattermost alerts](#configuring-mattermost-alerts). | `{}` |
@@ -385,7 +415,6 @@ ignored.
#### Configuring Discord alerts
| Parameter | Description | Default |
|:-------------------------------------------|:-------------------------------------------------------------------------------------------|:--------------|
| `alerting.discord` | Configuration for alerts of type `discord` | `{}` |
@@ -416,7 +445,6 @@ endpoints:
#### Configuring Email alerts
| Parameter | Description | Default |
|:-----------------------------------|:-------------------------------------------------------------------------------------------|:--------------|
| `alerting.email` | Configuration for alerts of type `email` | `{}` |
@@ -472,11 +500,47 @@ endpoints:
send-on-resolved: true
```
**NOTE:** Some mail servers are painfully slow.
> Some mail servers are painfully slow.
#### Configuring GitHub alerts
| Parameter | Description | Default |
|:---------------------------------|:-----------------------------------------------------------------------------------------------------------|:--------------|
| `alerting.github` | Configuration for alerts of type `github` | `{}` |
| `alerting.github.repository-url` | GitHub repository URL (e.g. `https://github.com/TwiN/example`) | Required `""` |
| `alerting.github.token` | Personal access token to use for authentication. <br />Must have at least RW on issues and RO on metadata. | Required `""` |
| `alerting.github.default-alert` | Default alert configuration. <br />See [Setting a default alert](#setting-a-default-alert). | N/A |
The GitHub alerting provider creates an issue prefixed with `alert(gatus):` and suffixed with the endpoint's display
name for each alert. If `send-on-resolved` is set to `true` on the endpoint alert, the issue will be automatically
closed when the alert is resolved.
```yaml
alerting:
github:
repository-url: "https://github.com/TwiN/test"
token: "github_pat_12345..."
endpoints:
- name: example
url: "https://twin.sh/health"
interval: 5m
conditions:
- "[STATUS] == 200"
- "[BODY].status == UP"
- "[RESPONSE_TIME] < 75"
alerts:
- type: github
failure-threshold: 2
success-threshold: 3
send-on-resolved: true
description: "Everything's burning AAAAAHHHHHHHHHHHHHHH"
```
![GitHub alert](.github/assets/github-alerts.png)
#### Configuring Google Chat alerts
| Parameter | Description | Default |
|:----------------------------------------------|:--------------------------------------------------------------------------------------------|:--------------|
| `alerting.googlechat` | Configuration for alerts of type `googlechat` | `{}` |
@@ -485,7 +549,7 @@ endpoints:
| `alerting.googlechat.default-alert` | Default alert configuration. <br />See [Setting a default alert](#setting-a-default-alert). | N/A |
| `alerting.googlechat.overrides` | List of overrides that may be prioritized over the default configuration | `[]` |
| `alerting.googlechat.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` |
| `alerting.googlechat.overrides[].webhook-url` | Teams Webhook URL | `""` |
| `alerting.googlechat.overrides[].webhook-url` | Google Chat Webhook URL | `""` |
```yaml
alerting:
@@ -733,6 +797,7 @@ endpoints:
| `alerting.slack.overrides` | List of overrides that may be prioritized over the default configuration | `[]` |
| `alerting.slack.overrides[].group` | Endpoint group for which the configuration will be overridden by this configuration | `""` |
| `alerting.slack.overrides[].webhook-url` | Slack Webhook URL | `""` |
```yaml
alerting:
slack:
@@ -1050,7 +1115,7 @@ To do that, you'll have to use the maintenance configuration:
| `maintenance.duration` | Duration of the maintenance window (e.g. `1h`, `30m`) | Required `""` |
| `maintenance.every` | Days on which the maintenance period applies (e.g. `[Monday, Thursday]`).<br />If left empty, the maintenance window applies every day | `[]` |
**Note that the maintenance configuration uses UTC.**
> 📝 The maintenance configuration uses UTC
Here's an example:
```yaml
@@ -1093,8 +1158,8 @@ security:
password-bcrypt-base64: "JDJhJDEwJHRiMnRFakxWazZLdXBzRERQazB1TE8vckRLY05Yb1hSdnoxWU0yQ1FaYXZRSW1McmladDYu"
```
**WARNING:** Make sure to carefully select to cost of the bcrypt hash. The higher the cost, the longer it takes to compute the hash,
and basic auth verifies the password against the hash on every request. As of 2022-01-08, I suggest a cost of 8.
> Make sure to carefully select to cost of the bcrypt hash. The higher the cost, the longer it takes to compute the hash,
and basic auth verifies the password against the hash on every request. As of 2023-01-06, I suggest a cost of 9.
#### OIDC
@@ -1252,7 +1317,7 @@ will send a `POST` request to `http://localhost:8080/playground` with the follow
### Recommended interval
> **NOTE**: This does not apply if `disable-monitoring-lock` is set to `true`, as the monitoring lock is what
> 📝 This does not apply if `disable-monitoring-lock` is set to `true`, as the monitoring lock is what
> tells Gatus to only evaluate one endpoint at a time.
To ensure that Gatus provides reliable and accurate results (i.e. response time), Gatus only evaluates one endpoint at a time
@@ -1308,7 +1373,7 @@ Placeholders `[STATUS]` and `[BODY]` as well as the fields `endpoints[].body`, `
This works for applications such as databases (Postgres, MySQL, etc.) and caches (Redis, Memcached, etc.).
**NOTE**: `[CONNECTED] == true` does not guarantee that the endpoint itself is healthy - it only guarantees that there's
> 📝 `[CONNECTED] == true` does not guarantee that the endpoint itself is healthy - it only guarantees that there's
something at the given address listening to the given port, and that a connection to that address was successfully
established.
@@ -1424,7 +1489,7 @@ endpoints:
- "[CERTIFICATE_EXPIRATION] > 240h"
```
**NOTE**: The usage of the `[DOMAIN_EXPIRATION]` placeholder requires Gatus to send a request to the official IANA WHOIS service [through a library](https://github.com/TwiN/whois)
> The usage of the `[DOMAIN_EXPIRATION]` placeholder requires Gatus to send a request to the official IANA WHOIS service [through a library](https://github.com/TwiN/whois)
and in some cases, a secondary request to a TLD-specific WHOIS server (e.g. `whois.nic.sh`).
To prevent the WHOIS service from throttling your IP address if you send too many requests, Gatus will prevent you from
using the `[DOMAIN_EXPIRATION]` placeholder on an endpoint with an interval of less than `5m`.
@@ -1465,7 +1530,7 @@ to make.
**If you are not using a file storage**, updating the configuration while Gatus is running is effectively
the same as restarting the application.
**NOTE:** Updates may not be detected if the config file is bound instead of the config folder. See [#151](https://github.com/TwiN/gatus/issues/151).
> 📝 Updates may not be detected if the config file is bound instead of the config folder. See [#151](https://github.com/TwiN/gatus/issues/151).
### Endpoint groups
@@ -1679,4 +1744,3 @@ No such header is required to query the API.
You can find the full list of sponsors [here](https://github.com/sponsors/TwiN).
[<img src="https://github.com/math280h.png" width="50" />](https://github.com/math280h)
[<img src="https://github.com/pyroscope-io.png" width="50" />](https://github.com/pyroscope-io)

View File

@@ -14,6 +14,9 @@ const (
// TypeEmail is the Type for the email alerting provider
TypeEmail Type = "email"
// TypeGitHub is the Type for the github alerting provider
TypeGitHub Type = "github"
// TypeGoogleChat is the Type for the googlechat alerting provider
TypeGoogleChat Type = "googlechat"

View File

@@ -1,11 +1,16 @@
package alerting
import (
"log"
"reflect"
"strings"
"github.com/TwiN/gatus/v5/alerting/alert"
"github.com/TwiN/gatus/v5/alerting/provider"
"github.com/TwiN/gatus/v5/alerting/provider/custom"
"github.com/TwiN/gatus/v5/alerting/provider/discord"
"github.com/TwiN/gatus/v5/alerting/provider/email"
"github.com/TwiN/gatus/v5/alerting/provider/github"
"github.com/TwiN/gatus/v5/alerting/provider/googlechat"
"github.com/TwiN/gatus/v5/alerting/provider/matrix"
"github.com/TwiN/gatus/v5/alerting/provider/mattermost"
@@ -24,15 +29,18 @@ type Config struct {
// Custom is the configuration for the custom alerting provider
Custom *custom.AlertProvider `yaml:"custom,omitempty"`
// GoogleChat is the configuration for the Google chat alerting provider
GoogleChat *googlechat.AlertProvider `yaml:"googlechat,omitempty"`
// Discord is the configuration for the discord alerting provider
Discord *discord.AlertProvider `yaml:"discord,omitempty"`
// Email is the configuration for the email alerting provider
Email *email.AlertProvider `yaml:"email,omitempty"`
// GitHub is the configuration for the github alerting provider
GitHub *github.AlertProvider `yaml:"github,omitempty"`
// GoogleChat is the configuration for the googlechat alerting provider
GoogleChat *googlechat.AlertProvider `yaml:"googlechat,omitempty"`
// Matrix is the configuration for the matrix alerting provider
Matrix *matrix.AlertProvider `yaml:"matrix,omitempty"`
@@ -65,92 +73,30 @@ type Config struct {
}
// GetAlertingProviderByAlertType returns an provider.AlertProvider by its corresponding alert.Type
func (config Config) GetAlertingProviderByAlertType(alertType alert.Type) provider.AlertProvider {
switch alertType {
case alert.TypeCustom:
if config.Custom == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
func (config *Config) GetAlertingProviderByAlertType(alertType alert.Type) provider.AlertProvider {
entityType := reflect.TypeOf(config).Elem()
for i := 0; i < entityType.NumField(); i++ {
field := entityType.Field(i)
if strings.ToLower(field.Name) == string(alertType) {
fieldValue := reflect.ValueOf(config).Elem().Field(i)
if fieldValue.IsNil() {
return nil
}
return fieldValue.Interface().(provider.AlertProvider)
}
return config.Custom
case alert.TypeDiscord:
if config.Discord == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config.Discord
case alert.TypeEmail:
if config.Email == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config.Email
case alert.TypeGoogleChat:
if config.GoogleChat == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config.GoogleChat
case alert.TypeMatrix:
if config.Matrix == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config.Matrix
case alert.TypeMattermost:
if config.Mattermost == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config.Mattermost
case alert.TypeMessagebird:
if config.Messagebird == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config.Messagebird
case alert.TypeNtfy:
if config.Ntfy == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config.Ntfy
case alert.TypeOpsgenie:
if config.Opsgenie == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config.Opsgenie
case alert.TypePagerDuty:
if config.PagerDuty == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config.PagerDuty
case alert.TypeSlack:
if config.Slack == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config.Slack
case alert.TypeTeams:
if config.Teams == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config.Teams
case alert.TypeTelegram:
if config.Telegram == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config.Telegram
case alert.TypeTwilio:
if config.Twilio == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config.Twilio
}
log.Printf("[alerting][GetAlertingProviderByAlertType] No alerting provider found for alert type %s", alertType)
return nil
}
// SetAlertingProviderToNil Sets an alerting provider to nil to avoid having to revalidate it every time an
// alert of its corresponding type is sent.
func (config *Config) SetAlertingProviderToNil(p provider.AlertProvider) {
entityType := reflect.TypeOf(config).Elem()
for i := 0; i < entityType.NumField(); i++ {
field := entityType.Field(i)
if field.Type == reflect.TypeOf(p) {
reflect.ValueOf(config).Elem().Field(i).Set(reflect.Zero(field.Type))
}
}
}

View File

@@ -0,0 +1,129 @@
package github
import (
"context"
"fmt"
"net/url"
"strings"
"time"
"github.com/TwiN/gatus/v5/alerting/alert"
"github.com/TwiN/gatus/v5/core"
"github.com/google/go-github/v48/github"
"golang.org/x/oauth2"
)
// AlertProvider is the configuration necessary for sending an alert using Discord
type AlertProvider struct {
RepositoryURL string `yaml:"repository-url"` // The URL of the GitHub repository to create issues in
Token string `yaml:"token"` // Token requires at least RW on issues and RO on metadata
// DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type
DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"`
username string
repositoryOwner string
repositoryName string
githubClient *github.Client
}
// IsValid returns whether the provider's configuration is valid
func (provider *AlertProvider) IsValid() bool {
if len(provider.Token) == 0 || len(provider.RepositoryURL) == 0 {
return false
}
// Validate format of the repository URL
repositoryURL, err := url.Parse(provider.RepositoryURL)
if err != nil {
return false
}
baseURL := repositoryURL.Scheme + "://" + repositoryURL.Host
pathParts := strings.Split(repositoryURL.Path, "/")
if len(pathParts) != 3 {
return false
}
provider.repositoryOwner = pathParts[1]
provider.repositoryName = pathParts[2]
// Create oauth2 HTTP client with GitHub token
httpClientWithStaticTokenSource := oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(&oauth2.Token{
AccessToken: provider.Token,
}))
// Create GitHub client
if baseURL == "https://github.com" {
provider.githubClient = github.NewClient(httpClientWithStaticTokenSource)
} else {
provider.githubClient, err = github.NewEnterpriseClient(baseURL, baseURL, httpClientWithStaticTokenSource)
if err != nil {
return false
}
}
// Retrieve the username once to validate that the token is valid
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
user, _, err := provider.githubClient.Users.Get(ctx, "")
if err != nil {
return false
}
provider.username = *user.Login
return true
}
// Send creates an issue in the designed RepositoryURL if the resolved parameter passed is false,
// or closes the relevant issue(s) if the resolved parameter passed is true.
func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) error {
title := "alert(gatus): " + endpoint.DisplayName()
if !resolved {
_, _, err := provider.githubClient.Issues.Create(context.Background(), provider.repositoryOwner, provider.repositoryName, &github.IssueRequest{
Title: github.String(title),
Body: github.String(provider.buildIssueBody(endpoint, alert, result)),
})
if err != nil {
return fmt.Errorf("failed to create issue: %w", err)
}
} else {
issues, _, err := provider.githubClient.Issues.ListByRepo(context.Background(), provider.repositoryOwner, provider.repositoryName, &github.IssueListByRepoOptions{
State: "open",
Creator: provider.username,
ListOptions: github.ListOptions{PerPage: 100},
})
if err != nil {
return fmt.Errorf("failed to list issues: %w", err)
}
for _, issue := range issues {
if *issue.Title == title {
_, _, err = provider.githubClient.Issues.Edit(context.Background(), provider.repositoryOwner, provider.repositoryName, *issue.Number, &github.IssueRequest{
State: github.String("closed"),
})
if err != nil {
return fmt.Errorf("failed to close issue: %w", err)
}
}
}
}
return nil
}
// buildIssueBody builds the body of the issue
func (provider *AlertProvider) buildIssueBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result) string {
var results string
for _, conditionResult := range result.ConditionResults {
var prefix string
if conditionResult.Success {
prefix = ":white_check_mark:"
} else {
prefix = ":x:"
}
results += fmt.Sprintf("- %s - `%s`\n", prefix, conditionResult.Condition)
}
var description string
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
description = ":\n> " + alertDescription
}
message := fmt.Sprintf("An alert for **%s** has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
return message + description + "\n\n## Condition results\n" + results
}
// GetDefaultAlert returns the provider's default alert configuration
func (provider AlertProvider) GetDefaultAlert() *alert.Alert {
return provider.DefaultAlert
}

View File

@@ -0,0 +1,158 @@
package github
import (
"net/http"
"strings"
"testing"
"github.com/TwiN/gatus/v5/alerting/alert"
"github.com/TwiN/gatus/v5/client"
"github.com/TwiN/gatus/v5/core"
"github.com/TwiN/gatus/v5/test"
"github.com/google/go-github/v48/github"
)
func TestAlertDefaultProvider_IsValid(t *testing.T) {
scenarios := []struct {
Name string
Provider AlertProvider
Expected bool
}{
{
Name: "invalid",
Provider: AlertProvider{RepositoryURL: "", Token: ""},
Expected: false,
},
{
Name: "invalid-token",
Provider: AlertProvider{RepositoryURL: "https://github.com/TwiN/test", Token: "12345"},
Expected: false,
},
{
Name: "missing-repository-name",
Provider: AlertProvider{RepositoryURL: "https://github.com/TwiN", Token: "12345"},
Expected: false,
},
{
Name: "enterprise-client",
Provider: AlertProvider{RepositoryURL: "https://github.example.com/TwiN/test", Token: "12345"},
Expected: false,
},
{
Name: "invalid-url",
Provider: AlertProvider{RepositoryURL: "github.com/TwiN/test", Token: "12345"},
Expected: false,
},
}
for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {
if scenario.Provider.IsValid() != scenario.Expected {
t.Errorf("expected %t, got %t", scenario.Expected, scenario.Provider.IsValid())
}
})
}
}
func TestAlertProvider_Send(t *testing.T) {
defer client.InjectHTTPClient(nil)
firstDescription := "description-1"
secondDescription := "description-2"
scenarios := []struct {
Name string
Provider AlertProvider
Alert alert.Alert
Resolved bool
MockRoundTripper test.MockRoundTripper
ExpectedError bool
}{
{
Name: "triggered-error",
Provider: AlertProvider{RepositoryURL: "https://github.com/TwiN/test", Token: "12345"},
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
Resolved: false,
ExpectedError: true,
},
{
Name: "resolved-error",
Provider: AlertProvider{RepositoryURL: "https://github.com/TwiN/test", Token: "12345"},
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
Resolved: true,
ExpectedError: true,
},
}
for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {
scenario.Provider.githubClient = github.NewClient(nil)
client.InjectHTTPClient(&http.Client{Transport: scenario.MockRoundTripper})
err := scenario.Provider.Send(
&core.Endpoint{Name: "endpoint-name", Group: "endpoint-group"},
&scenario.Alert,
&core.Result{
ConditionResults: []*core.ConditionResult{
{Condition: "[CONNECTED] == true", Success: scenario.Resolved},
{Condition: "[STATUS] == 200", Success: scenario.Resolved},
},
},
scenario.Resolved,
)
if scenario.ExpectedError && err == nil {
t.Error("expected error, got none")
}
if !scenario.ExpectedError && err != nil {
t.Error("expected no error, got", err.Error())
}
})
}
}
func TestAlertProvider_buildRequestBody(t *testing.T) {
firstDescription := "description-1"
scenarios := []struct {
Name string
Endpoint core.Endpoint
Provider AlertProvider
Alert alert.Alert
ExpectedBody string
}{
{
Name: "triggered",
Endpoint: core.Endpoint{Name: "endpoint-name", URL: "https://example.org"},
Provider: AlertProvider{},
Alert: alert.Alert{Description: &firstDescription, FailureThreshold: 3},
ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\n> description-1\n\n## Condition results\n- :white_check_mark: - `[CONNECTED] == true`\n- :x: - `[STATUS] == 200`",
},
{
Name: "no-description",
Endpoint: core.Endpoint{Name: "endpoint-name", URL: "https://example.org"},
Provider: AlertProvider{},
Alert: alert.Alert{FailureThreshold: 10},
ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 10 time(s) in a row\n\n## Condition results\n- :white_check_mark: - `[CONNECTED] == true`\n- :x: - `[STATUS] == 200`",
},
}
for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {
body := scenario.Provider.buildIssueBody(
&scenario.Endpoint,
&scenario.Alert,
&core.Result{
ConditionResults: []*core.ConditionResult{
{Condition: "[CONNECTED] == true", Success: true},
{Condition: "[STATUS] == 200", Success: false},
},
},
)
if strings.TrimSpace(body) != strings.TrimSpace(scenario.ExpectedBody) {
t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
}
})
}
}
func TestAlertProvider_GetDefaultAlert(t *testing.T) {
if (AlertProvider{DefaultAlert: &alert.Alert{}}).GetDefaultAlert() == nil {
t.Error("expected default alert to be not nil")
}
if (AlertProvider{DefaultAlert: nil}).GetDefaultAlert() != nil {
t.Error("expected default alert to be nil")
}
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/TwiN/gatus/v5/alerting/provider/custom"
"github.com/TwiN/gatus/v5/alerting/provider/discord"
"github.com/TwiN/gatus/v5/alerting/provider/email"
"github.com/TwiN/gatus/v5/alerting/provider/github"
"github.com/TwiN/gatus/v5/alerting/provider/googlechat"
"github.com/TwiN/gatus/v5/alerting/provider/matrix"
"github.com/TwiN/gatus/v5/alerting/provider/mattermost"
@@ -58,6 +59,7 @@ var (
_ AlertProvider = (*custom.AlertProvider)(nil)
_ AlertProvider = (*discord.AlertProvider)(nil)
_ AlertProvider = (*email.AlertProvider)(nil)
_ AlertProvider = (*github.AlertProvider)(nil)
_ AlertProvider = (*googlechat.AlertProvider)(nil)
_ AlertProvider = (*matrix.AlertProvider)(nil)
_ AlertProvider = (*mattermost.AlertProvider)(nil)

View File

@@ -36,6 +36,7 @@ func TestGetHTTPClient(t *testing.T) {
}
func TestGetDomainExpiration(t *testing.T) {
t.Parallel()
if domainExpiration, err := GetDomainExpiration("example.com"); err != nil {
t.Fatalf("expected error to be nil, but got: `%s`", err)
} else if domainExpiration <= 0 {
@@ -63,6 +64,7 @@ func TestGetDomainExpiration(t *testing.T) {
}
func TestPing(t *testing.T) {
t.Parallel()
if success, rtt := Ping("127.0.0.1", &Config{Timeout: 500 * time.Millisecond}); !success {
t.Error("expected true")
if rtt == 0 {
@@ -121,6 +123,7 @@ func TestCanPerformStartTLS(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
connected, _, err := CanPerformStartTLS(tt.args.address, &Config{Insecure: tt.args.insecure, Timeout: 5 * time.Second})
if (err != nil) != tt.wantErr {
t.Errorf("CanPerformStartTLS() err=%v, wantErr=%v", err, tt.wantErr)
@@ -171,6 +174,7 @@ func TestCanPerformTLS(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
connected, _, err := CanPerformTLS(tt.args.address, &Config{Insecure: tt.args.insecure, Timeout: 5 * time.Second})
if (err != nil) != tt.wantErr {
t.Errorf("CanPerformTLS() err=%v, wantErr=%v", err, tt.wantErr)

View File

@@ -22,7 +22,7 @@ const (
var (
ErrInvalidDNSResolver = errors.New("invalid DNS resolver specified. Required format is {proto}://{ip}:{port}")
ErrInvalidDNSResolverPort = errors.New("invalid DNS resolver port")
ErrInvalidClientOAuth2Config = errors.New("invalid OAuth2 configuration, all fields are required")
ErrInvalidClientOAuth2Config = errors.New("invalid oauth2 configuration: must define all fields for client credentials flow (token-url, client-id, client-secret, scopes)")
defaultConfig = Config{
Insecure: false,

View File

@@ -3,10 +3,13 @@ package config
import (
"errors"
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"time"
"github.com/TwiN/deepmerge"
"github.com/TwiN/gatus/v5/alerting"
"github.com/TwiN/gatus/v5/alerting/alert"
"github.com/TwiN/gatus/v5/alerting/provider"
@@ -18,12 +21,12 @@ import (
"github.com/TwiN/gatus/v5/security"
"github.com/TwiN/gatus/v5/storage"
"github.com/TwiN/gatus/v5/util"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
const (
// DefaultConfigurationFilePath is the default path that will be used to search for the configuration file
// if a custom path isn't configured through the GATUS_CONFIG_FILE environment variable
// if a custom path isn't configured through the GATUS_CONFIG_PATH environment variable
DefaultConfigurationFilePath = "config/config.yaml"
// DefaultFallbackConfigurationFilePath is the default fallback path that will be used to search for the
@@ -32,14 +35,17 @@ const (
)
var (
// ErrNoEndpointInConfig is an error returned when a configuration file has no endpoints configured
ErrNoEndpointInConfig = errors.New("configuration file should contain at least 1 endpoint")
// ErrNoEndpointInConfig is an error returned when a configuration file or directory has no endpoints configured
ErrNoEndpointInConfig = errors.New("configuration should contain at least 1 endpoint")
// ErrConfigFileNotFound is an error returned when the configuration file could not be found
// ErrConfigFileNotFound is an error returned when a configuration file could not be found
ErrConfigFileNotFound = errors.New("configuration file not found")
// ErrInvalidSecurityConfig is an error returned when the security configuration is invalid
ErrInvalidSecurityConfig = errors.New("invalid security configuration")
// errEarlyReturn is returned to break out of a loop from a callback early
errEarlyReturn = errors.New("early escape")
)
// Config is the main configuration structure
@@ -84,11 +90,12 @@ type Config struct {
// WARNING: This is in ALPHA and may change or be completely removed in the future
Remote *remote.Config `yaml:"remote,omitempty"`
filePath string // path to the file from which config was loaded from
configPath string // path to the file or directory from which config was loaded
lastFileModTime time.Time // last modification time
}
func (config *Config) GetEndpointByKey(key string) *core.Endpoint {
// TODO: Should probably add a mutex here to prevent concurrent access
for i := 0; i < len(config.Endpoints); i++ {
ep := config.Endpoints[i]
if util.ConvertGroupAndEndpointNameToKey(ep.Group, ep.Name) == key {
@@ -98,63 +105,111 @@ func (config *Config) GetEndpointByKey(key string) *core.Endpoint {
return nil
}
// HasLoadedConfigurationFileBeenModified returns whether the file that the
// HasLoadedConfigurationBeenModified returns whether one of the file that the
// configuration has been loaded from has been modified since it was last read
func (config Config) HasLoadedConfigurationFileBeenModified() bool {
if fileInfo, err := os.Stat(config.filePath); err == nil {
if !fileInfo.ModTime().IsZero() {
return config.lastFileModTime.Unix() != fileInfo.ModTime().Unix()
}
func (config Config) HasLoadedConfigurationBeenModified() bool {
lastMod := config.lastFileModTime.Unix()
fileInfo, err := os.Stat(config.configPath)
if err != nil {
return false
}
return false
if fileInfo.IsDir() {
err = walkConfigDir(config.configPath, func(path string, d fs.DirEntry, err error) error {
if info, err := d.Info(); err == nil && lastMod < info.ModTime().Unix() {
return errEarlyReturn
}
return nil
})
return err == errEarlyReturn
}
return !fileInfo.ModTime().IsZero() && config.lastFileModTime.Unix() < fileInfo.ModTime().Unix()
}
// UpdateLastFileModTime refreshes Config.lastFileModTime
func (config *Config) UpdateLastFileModTime() {
if fileInfo, err := os.Stat(config.filePath); err == nil {
if !fileInfo.ModTime().IsZero() {
config.lastFileModTime = fileInfo.ModTime()
config.lastFileModTime = time.Now()
}
// LoadConfiguration loads the full configuration composed from the main configuration file
// and all composed configuration files
func LoadConfiguration(configPath string) (*Config, error) {
var configBytes []byte
var fileInfo os.FileInfo
var usedConfigPath string
// Figure out what config path we'll use (either configPath or the default config path)
for _, configurationPath := range []string{configPath, DefaultConfigurationFilePath, DefaultFallbackConfigurationFilePath} {
if len(configurationPath) == 0 {
continue
}
var err error
fileInfo, err = os.Stat(configurationPath)
if err != nil {
continue
}
usedConfigPath = configurationPath
break
}
if len(usedConfigPath) == 0 {
return nil, ErrConfigFileNotFound
}
var config *Config
if fileInfo.IsDir() {
err := walkConfigDir(configPath, func(path string, d fs.DirEntry, err error) error {
if err != nil {
log.Printf("[config][LoadConfiguration] Error walking path=%s: %s", path, err)
return err
}
log.Printf("[config][LoadConfiguration] Reading configuration from %s", path)
data, err := os.ReadFile(path)
if err != nil {
log.Printf("[config][LoadConfiguration] Error reading configuration from %s: %s", path, err)
return fmt.Errorf("error reading configuration from file %s: %w", path, err)
}
configBytes, err = deepmerge.YAML(configBytes, data)
return err
})
if err != nil {
return nil, fmt.Errorf("error reading configuration from directory %s: %w", usedConfigPath, err)
}
} else {
log.Println("[config][UpdateLastFileModTime] Ran into error updating lastFileModTime:", err.Error())
}
}
// Load loads a custom configuration file
// Note that the misconfiguration of some fields may lead to panics. This is on purpose.
func Load(configFile string) (*Config, error) {
log.Printf("[config][Load] Reading configuration from configFile=%s", configFile)
cfg, err := readConfigurationFile(configFile)
if err != nil {
if os.IsNotExist(err) {
return nil, ErrConfigFileNotFound
log.Printf("[config][LoadConfiguration] Reading configuration from configFile=%s", configPath)
if data, err := os.ReadFile(usedConfigPath); err != nil {
return nil, err
} else {
configBytes = data
}
}
if len(configBytes) == 0 {
return nil, ErrConfigFileNotFound
}
config, err := parseAndValidateConfigBytes(configBytes)
if err != nil {
return nil, err
}
cfg.filePath = configFile
cfg.UpdateLastFileModTime()
return cfg, nil
config.configPath = usedConfigPath
config.UpdateLastFileModTime()
return config, err
}
// LoadDefaultConfiguration loads the default configuration file
func LoadDefaultConfiguration() (*Config, error) {
cfg, err := Load(DefaultConfigurationFilePath)
if err != nil {
if err == ErrConfigFileNotFound {
return Load(DefaultFallbackConfigurationFilePath)
// walkConfigDir is a wrapper for filepath.WalkDir that strips directories and non-config files
func walkConfigDir(path string, fn fs.WalkDirFunc) error {
if len(path) == 0 {
// If the user didn't provide a directory, we'll just use the default config file, so we can return nil now.
return nil
}
return filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil
}
return nil, err
}
return cfg, nil
}
func readConfigurationFile(fileName string) (config *Config, err error) {
var bytes []byte
if bytes, err = os.ReadFile(fileName); err == nil {
// file exists, so we'll parse it and return it
return parseAndValidateConfigBytes(bytes)
}
return
if d == nil || d.IsDir() {
return nil
}
ext := filepath.Ext(path)
if ext != ".yml" && ext != ".yaml" {
return nil
}
return fn(path, d, err)
})
}
// parseAndValidateConfigBytes parses a Gatus configuration file into a Config struct and validates its parameters
@@ -288,6 +343,7 @@ func validateAlertingConfig(alertingConfig *alerting.Config, endpoints []*core.E
alertTypes := []alert.Type{
alert.TypeCustom,
alert.TypeDiscord,
alert.TypeGitHub,
alert.TypeGoogleChat,
alert.TypeEmail,
alert.TypeMatrix,
@@ -323,6 +379,7 @@ func validateAlertingConfig(alertingConfig *alerting.Config, endpoints []*core.E
} else {
log.Printf("[config][validateAlertingConfig] Ignoring provider=%s because configuration is invalid", alertType)
invalidProviders = append(invalidProviders, alertType)
alertingConfig.SetAlertingProviderToNil(alertProvider)
}
} else {
invalidProviders = append(invalidProviders, alertType)

View File

@@ -1,7 +1,10 @@
package config
import (
"errors"
"fmt"
"os"
"path/filepath"
"testing"
"time"
@@ -11,6 +14,7 @@ import (
"github.com/TwiN/gatus/v5/alerting/provider/custom"
"github.com/TwiN/gatus/v5/alerting/provider/discord"
"github.com/TwiN/gatus/v5/alerting/provider/email"
"github.com/TwiN/gatus/v5/alerting/provider/github"
"github.com/TwiN/gatus/v5/alerting/provider/googlechat"
"github.com/TwiN/gatus/v5/alerting/provider/matrix"
"github.com/TwiN/gatus/v5/alerting/provider/mattermost"
@@ -26,20 +30,270 @@ import (
"github.com/TwiN/gatus/v5/config/web"
"github.com/TwiN/gatus/v5/core"
"github.com/TwiN/gatus/v5/storage"
"gopkg.in/yaml.v3"
)
func TestLoadFileThatDoesNotExist(t *testing.T) {
_, err := Load("file-that-does-not-exist.yaml")
if err == nil {
t.Error("Should've returned an error, because the file specified doesn't exist")
func TestLoadConfiguration(t *testing.T) {
dir := t.TempDir()
scenarios := []struct {
name string
configPath string // value to pass as the configPath parameter in LoadConfiguration
pathAndFiles map[string]string // files to create in dir
expectedConfig *Config
expectedError error
}{
{
name: "empty-config-file",
configPath: filepath.Join(dir, "config.yaml"),
pathAndFiles: map[string]string{
"config.yaml": "",
},
expectedError: ErrConfigFileNotFound,
},
{
name: "config-file-that-does-not-exist",
configPath: filepath.Join(dir, "config.yaml"),
expectedError: ErrConfigFileNotFound,
},
{
name: "config-file-with-endpoint-that-has-no-url",
configPath: filepath.Join(dir, "config.yaml"),
pathAndFiles: map[string]string{
"config.yaml": `
endpoints:
- name: website`,
},
expectedError: core.ErrEndpointWithNoURL,
},
{
name: "config-file-with-endpoint-that-has-no-conditions",
configPath: filepath.Join(dir, "config.yaml"),
pathAndFiles: map[string]string{
"config.yaml": `
endpoints:
- name: website
url: https://twin.sh/health`,
},
expectedError: core.ErrEndpointWithNoCondition,
},
{
name: "config-file",
configPath: filepath.Join(dir, "config.yaml"),
pathAndFiles: map[string]string{
"config.yaml": `
endpoints:
- name: website
url: https://twin.sh/health
conditions:
- "[STATUS] == 200"`,
},
expectedConfig: &Config{
Endpoints: []*core.Endpoint{
{
Name: "website",
URL: "https://twin.sh/health",
Conditions: []core.Condition{"[STATUS] == 200"},
},
},
},
},
{
name: "empty-dir",
configPath: dir,
pathAndFiles: map[string]string{},
expectedError: ErrConfigFileNotFound,
},
{
name: "dir-with-empty-config-file",
configPath: dir,
pathAndFiles: map[string]string{
"config.yaml": "",
},
expectedError: ErrNoEndpointInConfig,
},
{
name: "dir-with-two-config-files",
configPath: dir,
pathAndFiles: map[string]string{
"config.yaml": `endpoints:
- name: one
url: https://example.com
conditions:
- "[CONNECTED] == true"
- "[STATUS] == 200"
- name: two
url: https://example.org
conditions:
- "len([BODY]) > 0"`,
"config.yml": `endpoints:
- name: three
url: https://twin.sh/health
conditions:
- "[STATUS] == 200"
- "[BODY].status == UP"`,
},
expectedConfig: &Config{
Endpoints: []*core.Endpoint{
{
Name: "one",
URL: "https://example.com",
Conditions: []core.Condition{"[CONNECTED] == true", "[STATUS] == 200"},
},
{
Name: "two",
URL: "https://example.org",
Conditions: []core.Condition{"len([BODY]) > 0"},
},
{
Name: "three",
URL: "https://twin.sh/health",
Conditions: []core.Condition{"[STATUS] == 200", "[BODY].status == UP"},
},
},
},
},
{
name: "dir-with-2-config-files-deep-merge-with-map-slice-and-primitive",
configPath: dir,
pathAndFiles: map[string]string{
"a.yaml": `
metrics: true
alerting:
slack:
webhook-url: https://hooks.slack.com/services/xxx/yyy/zzz
endpoints:
- name: example
url: https://example.org
interval: 5s
conditions:
- "[STATUS] == 200"`,
"b.yaml": `
debug: true
alerting:
discord:
webhook-url: https://discord.com/api/webhooks/xxx/yyy
endpoints:
- name: frontend
url: https://example.com
conditions:
- "[STATUS] == 200"`,
},
expectedConfig: &Config{
Debug: true,
Metrics: true,
Alerting: &alerting.Config{
Discord: &discord.AlertProvider{WebhookURL: "https://discord.com/api/webhooks/xxx/yyy"},
Slack: &slack.AlertProvider{WebhookURL: "https://hooks.slack.com/services/xxx/yyy/zzz"},
},
Endpoints: []*core.Endpoint{
{
Name: "example",
URL: "https://example.org",
Interval: 5 * time.Second,
Conditions: []core.Condition{"[STATUS] == 200"},
},
{
Name: "frontend",
URL: "https://example.com",
Conditions: []core.Condition{"[STATUS] == 200"},
},
},
},
},
}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
for path, content := range scenario.pathAndFiles {
if err := os.WriteFile(filepath.Join(dir, path), []byte(content), 0644); err != nil {
t.Fatalf("[%s] failed to write file: %v", scenario.name, err)
}
}
defer func(pathAndFiles map[string]string) {
for path := range pathAndFiles {
_ = os.Remove(filepath.Join(dir, path))
}
}(scenario.pathAndFiles)
config, err := LoadConfiguration(scenario.configPath)
if !errors.Is(err, scenario.expectedError) {
t.Errorf("[%s] expected error %v, got %v", scenario.name, scenario.expectedError, err)
return
} else if err != nil && errors.Is(err, scenario.expectedError) {
return
}
// parse the expected output so that expectations are closer to reality (under the right circumstances, even I can be poetic)
expectedConfigAsYAML, _ := yaml.Marshal(scenario.expectedConfig)
expectedConfigAfterBeingParsedAndValidated, err := parseAndValidateConfigBytes(expectedConfigAsYAML)
if err != nil {
t.Fatalf("[%s] failed to parse expected config: %v", scenario.name, err)
}
// Marshal em' before comparing em' so that we don't have to deal with formatting and ordering
actualConfigAsYAML, err := yaml.Marshal(config)
if err != nil {
t.Fatalf("[%s] failed to marshal actual config: %v", scenario.name, err)
}
expectedConfigAfterBeingParsedAndValidatedAsYAML, _ := yaml.Marshal(expectedConfigAfterBeingParsedAndValidated)
if string(actualConfigAsYAML) != string(expectedConfigAfterBeingParsedAndValidatedAsYAML) {
t.Errorf("[%s] expected config %s, got %s", scenario.name, string(expectedConfigAfterBeingParsedAndValidatedAsYAML), string(actualConfigAsYAML))
}
})
}
}
func TestLoadDefaultConfigurationFile(t *testing.T) {
_, err := LoadDefaultConfiguration()
if err == nil {
t.Error("Should've returned an error, because there's no configuration files at the default path nor the default fallback path")
}
func TestConfig_HasLoadedConfigurationBeenModified(t *testing.T) {
t.Parallel()
dir := t.TempDir()
configFilePath := filepath.Join(dir, "config.yaml")
_ = os.WriteFile(configFilePath, []byte(`endpoints:
- name: website
url: https://twin.sh/health
conditions:
- "[STATUS] == 200"
`), 0644)
t.Run("config-file-as-config-path", func(t *testing.T) {
config, err := LoadConfiguration(configFilePath)
if err != nil {
t.Fatalf("failed to load configuration: %v", err)
}
if config.HasLoadedConfigurationBeenModified() {
t.Errorf("expected config.HasLoadedConfigurationBeenModified() to return false because nothing has happened since it was created")
}
time.Sleep(time.Second) // Because the file mod time only has second precision, we have to wait for a second
// Update the config file
if err = os.WriteFile(filepath.Join(dir, "config.yaml"), []byte(`endpoints:
- name: website
url: https://twin.sh/health
conditions:
- "[STATUS] == 200"`), 0644); err != nil {
t.Fatalf("failed to overwrite config file: %v", err)
}
if !config.HasLoadedConfigurationBeenModified() {
t.Errorf("expected config.HasLoadedConfigurationBeenModified() to return true because a new file has been added in the directory")
}
})
t.Run("config-directory-as-config-path", func(t *testing.T) {
config, err := LoadConfiguration(dir)
if err != nil {
t.Fatalf("failed to load configuration: %v", err)
}
if config.HasLoadedConfigurationBeenModified() {
t.Errorf("expected config.HasLoadedConfigurationBeenModified() to return false because nothing has happened since it was created")
}
time.Sleep(time.Second) // Because the file mod time only has second precision, we have to wait for a second
// Update the config file
if err = os.WriteFile(filepath.Join(dir, "metrics.yaml"), []byte(`metrics: true`), 0644); err != nil {
t.Fatalf("failed to overwrite config file: %v", err)
}
if !config.HasLoadedConfigurationBeenModified() {
t.Errorf("expected config.HasLoadedConfigurationBeenModified() to return true because a new file has been added in the directory")
}
})
}
func TestParseAndValidateConfigBytes(t *testing.T) {
@@ -980,11 +1234,8 @@ endpoints:
if config.Alerting == nil {
t.Fatal("config.Alerting shouldn't have been nil")
}
if config.Alerting.PagerDuty == nil {
t.Fatal("PagerDuty alerting config shouldn't have been nil")
}
if config.Alerting.PagerDuty.IsValid() {
t.Fatal("PagerDuty alerting config should've been invalid")
if config.Alerting.PagerDuty != nil {
t.Fatal("PagerDuty alerting config should've been set to nil, because its IsValid() method returned false and therefore alerting.Config.SetAlertingProviderToNil() should've been called")
}
}
@@ -1222,6 +1473,7 @@ func TestGetAlertingProviderByAlertType(t *testing.T) {
Custom: &custom.AlertProvider{},
Discord: &discord.AlertProvider{},
Email: &email.AlertProvider{},
GitHub: &github.AlertProvider{},
GoogleChat: &googlechat.AlertProvider{},
Matrix: &matrix.AlertProvider{},
Mattermost: &mattermost.AlertProvider{},
@@ -1241,6 +1493,7 @@ func TestGetAlertingProviderByAlertType(t *testing.T) {
{alertType: alert.TypeCustom, expected: alertingConfig.Custom},
{alertType: alert.TypeDiscord, expected: alertingConfig.Discord},
{alertType: alert.TypeEmail, expected: alertingConfig.Email},
{alertType: alert.TypeGitHub, expected: alertingConfig.GitHub},
{alertType: alert.TypeGoogleChat, expected: alertingConfig.GoogleChat},
{alertType: alert.TypeMatrix, expected: alertingConfig.Matrix},
{alertType: alert.TypeMattermost, expected: alertingConfig.Mattermost},

View File

@@ -1,14 +1,11 @@
package handler
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strings"
"time"
"github.com/TwiN/gatus/v5/client"
@@ -31,25 +28,14 @@ var (
)
// EndpointStatuses handles requests to retrieve all EndpointStatus
// Due to the size of the response, this function leverages a cache.
// Must not be wrapped by GzipHandler
// Due to how intensive this operation can be on the storage, this function leverages a cache.
func EndpointStatuses(cfg *config.Config) http.HandlerFunc {
return func(writer http.ResponseWriter, r *http.Request) {
page, pageSize := extractPageAndPageSizeFromRequest(r)
gzipped := strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")
var exists bool
var value interface{}
if gzipped {
writer.Header().Set("Content-Encoding", "gzip")
value, exists = cache.Get(fmt.Sprintf("endpoint-status-%d-%d-gzipped", page, pageSize))
} else {
value, exists = cache.Get(fmt.Sprintf("endpoint-status-%d-%d", page, pageSize))
}
value, exists := cache.Get(fmt.Sprintf("endpoint-status-%d-%d", page, pageSize))
var data []byte
if !exists {
var err error
buffer := &bytes.Buffer{}
gzipWriter := gzip.NewWriter(buffer)
endpointStatuses, err := store.Get().GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(page, pageSize))
if err != nil {
log.Printf("[handler][EndpointStatuses] Failed to retrieve endpoint statuses: %s", err.Error())
@@ -69,14 +55,7 @@ func EndpointStatuses(cfg *config.Config) http.HandlerFunc {
http.Error(writer, "unable to marshal object to JSON", http.StatusInternalServerError)
return
}
_, _ = gzipWriter.Write(data)
_ = gzipWriter.Close()
gzippedData := buffer.Bytes()
cache.SetWithTTL(fmt.Sprintf("endpoint-status-%d-%d", page, pageSize), data, cacheTTL)
cache.SetWithTTL(fmt.Sprintf("endpoint-status-%d-%d-gzipped", page, pageSize), gzippedData, cacheTTL)
if gzipped {
data = gzippedData
}
} else {
data = value.([]byte)
}

View File

@@ -8,14 +8,18 @@ import (
static "github.com/TwiN/gatus/v5/web"
"github.com/TwiN/health"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func CreateRouter(cfg *config.Config) *mux.Router {
router := mux.NewRouter()
if cfg.Metrics {
router.Handle("/metrics", promhttp.Handler()).Methods("GET")
router.Handle("/metrics", promhttp.InstrumentMetricHandler(prometheus.DefaultRegisterer, promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{
DisableCompression: true,
}))).Methods("GET")
}
router.Use(GzipHandler)
api := router.PathPrefix("/api").Subrouter()
protected := api.PathPrefix("/").Subrouter()
unprotected := api.PathPrefix("/").Subrouter()
@@ -29,8 +33,8 @@ func CreateRouter(cfg *config.Config) *mux.Router {
}
// Endpoints
unprotected.Handle("/v1/config", ConfigHandler{securityConfig: cfg.Security}).Methods("GET")
protected.HandleFunc("/v1/endpoints/statuses", EndpointStatuses(cfg)).Methods("GET") // No GzipHandler for this one, because we cache the content as Gzipped already
protected.HandleFunc("/v1/endpoints/{key}/statuses", GzipHandlerFunc(EndpointStatus)).Methods("GET")
protected.HandleFunc("/v1/endpoints/statuses", EndpointStatuses(cfg)).Methods("GET")
protected.HandleFunc("/v1/endpoints/{key}/statuses", EndpointStatus).Methods("GET")
unprotected.HandleFunc("/v1/endpoints/{key}/health/badge.svg", HealthBadge).Methods("GET")
unprotected.HandleFunc("/v1/endpoints/{key}/uptimes/{duration}/badge.svg", UptimeBadge).Methods("GET")
unprotected.HandleFunc("/v1/endpoints/{key}/response-times/{duration}/badge.svg", ResponseTimeBadge(cfg)).Methods("GET")
@@ -45,6 +49,6 @@ func CreateRouter(cfg *config.Config) *mux.Router {
if err != nil {
panic(err)
}
router.PathPrefix("/").Handler(GzipHandler(http.FileServer(http.FS(staticFileSystem))))
router.PathPrefix("/").Handler(http.FileServer(http.FS(staticFileSystem)))
return router
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/TwiN/gatus/v5/pattern"
)
// Placeholders
const (
// StatusPlaceholder is a placeholder for a HTTP status.
//
@@ -49,7 +50,10 @@ const (
// DomainExpirationPlaceholder is a placeholder for the duration before the domain expires, in milliseconds.
DomainExpirationPlaceholder = "[DOMAIN_EXPIRATION]"
)
// Functions
const (
// LengthFunctionPrefix is the prefix for the length function
//
// Usage: len([BODY].articles) == 10, len([BODY].name) > 5
@@ -72,7 +76,10 @@ const (
// FunctionSuffix is the suffix for all functions
FunctionSuffix = ")"
)
// Other constants
const (
// InvalidConditionElementSuffix is the suffix that will be appended to an invalid condition
InvalidConditionElementSuffix = "(INVALID)"

View File

@@ -182,6 +182,34 @@ func TestCondition_evaluate(t *testing.T) {
ExpectedSuccess: true,
ExpectedOutput: "[BODY] == test",
},
{
Name: "body-numerical-equal",
Condition: Condition("[BODY] == 123"),
Result: &Result{body: []byte("123")},
ExpectedSuccess: true,
ExpectedOutput: "[BODY] == 123",
},
{
Name: "body-numerical-less-than",
Condition: Condition("[BODY] < 124"),
Result: &Result{body: []byte("123")},
ExpectedSuccess: true,
ExpectedOutput: "[BODY] < 124",
},
{
Name: "body-numerical-greater-than",
Condition: Condition("[BODY] > 122"),
Result: &Result{body: []byte("123")},
ExpectedSuccess: true,
ExpectedOutput: "[BODY] > 122",
},
{
Name: "body-numerical-greater-than-failure",
Condition: Condition("[BODY] > 123"),
Result: &Result{body: []byte("100")},
ExpectedSuccess: false,
ExpectedOutput: "[BODY] (100) > 123",
},
{
Name: "body-jsonpath",
Condition: Condition("[BODY].status == UP"),
@@ -231,6 +259,13 @@ func TestCondition_evaluate(t *testing.T) {
ExpectedSuccess: true,
ExpectedOutput: "[BODY][0].id == 1",
},
{
Name: "body-jsonpath-when-body-is-array-but-actual-body-is-not",
Condition: Condition("[BODY][0].name == test"),
Result: &Result{body: []byte("{\"statusCode\": 500, \"message\": \"Internal Server Error\"}")},
ExpectedSuccess: false,
ExpectedOutput: "[BODY][0].name (INVALID) == test",
},
{
Name: "body-jsonpath-complex-int",
Condition: Condition("[BODY].data.id == 1"),
@@ -561,6 +596,14 @@ func TestCondition_evaluate(t *testing.T) {
ExpectedSuccess: true,
ExpectedOutput: "has([BODY].errors) == false",
},
{
Name: "has-key-of-map",
Condition: Condition("has([BODY].article) == true"),
Result: &Result{body: []byte("{\n \"article\": {\n \"id\": 123,\n \"title\": \"Hello, world!\",\n \"author\": \"John Doe\",\n \"tags\": [\"hello\", \"world\"],\n \"content\": \"I really like Gatus!\"\n }\n}")},
DontResolveFailedConditions: false,
ExpectedSuccess: true,
ExpectedOutput: "has([BODY].article) == true",
},
{
Name: "has-failure",
Condition: Condition("has([BODY].errors) == false"),

30
go.mod
View File

@@ -3,22 +3,25 @@ module github.com/TwiN/gatus/v5
go 1.19
require (
github.com/TwiN/deepmerge v0.2.0
github.com/TwiN/g8 v1.4.0
github.com/TwiN/gocache/v2 v2.2.0
github.com/TwiN/health v1.5.0
github.com/TwiN/health v1.6.0
github.com/TwiN/whois v1.1.0
github.com/coreos/go-oidc/v3 v3.4.0
github.com/coreos/go-oidc/v3 v3.5.0
github.com/go-ping/ping v0.0.0-20210911151512-381826476871
github.com/google/go-github/v48 v48.2.0
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/ishidawataru/sctp v0.0.0-20210707070123-9a39160e9062
github.com/lib/pq v1.10.7
github.com/miekg/dns v1.1.43
github.com/miekg/dns v1.1.50
github.com/prometheus/client_golang v1.14.0
github.com/wcharczuk/go-chart/v2 v2.1.0
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
golang.org/x/oauth2 v0.3.0
gopkg.in/mail.v2 v2.3.1
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.19.5
)
@@ -26,9 +29,10 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/ishidawataru/sctp v0.0.0-20210707070123-9a39160e9062
github.com/google/go-querystring v1.1.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
@@ -37,16 +41,14 @@ require (
github.com/prometheus/procfs v0.8.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
golang.org/x/tools v0.1.7 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.4.0 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect

302
go.sum
View File

@@ -13,36 +13,15 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
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 v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
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.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
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=
@@ -52,17 +31,17 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
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.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
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/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/TwiN/deepmerge v0.2.0 h1:8P1tp2qDXllX6V1Ailg2XA074easAcvLMmW9v1jn0aE=
github.com/TwiN/deepmerge v0.2.0/go.mod h1:cR9OWsvI13y+FxrbnPLuF6BX2tbYeOjkiI6JWjEGqAE=
github.com/TwiN/g8 v1.4.0 h1:RUk5xTtxKCdMo0GGSbBVyjtAAfi2nqVbA9E0C4u5Cxo=
github.com/TwiN/g8 v1.4.0/go.mod h1:ECyGJsoIb99klUfvVQoS1StgRLte9yvvPigGrHdy284=
github.com/TwiN/gocache/v2 v2.2.0 h1:M3B36KyH24BntxLrLaUb2kgTdq8DzCnfod0IekLG57w=
github.com/TwiN/gocache/v2 v2.2.0/go.mod h1:SnUuBsrwGQeNcDG6vhkOMJnqErZM0JGjgIkuKryokYA=
github.com/TwiN/health v1.5.0 h1:ETTtbQfUbiiIiVTSpAiNzesHQvm8qarV/8ctlZsVhwA=
github.com/TwiN/health v1.5.0/go.mod h1:Z6TszwQPMvtSiVx1QMidVRgvVr4KZGfiwqcD7/Z+3iw=
github.com/TwiN/health v1.6.0 h1:L2ks575JhRgQqWWOfKjw9B0ec172hx7GdToqkYUycQM=
github.com/TwiN/health v1.6.0/go.mod h1:Z6TszwQPMvtSiVx1QMidVRgvVr4KZGfiwqcD7/Z+3iw=
github.com/TwiN/whois v1.1.0 h1:lhyrC/9yIXntEnbJ+0IBy9Z5NBcreieYyamlvniwq88=
github.com/TwiN/whois v1.1.0/go.mod h1:9WbCzYlR+r5eq9vbgJVh7A4H2uR2ct4wKEB0/QITJ/c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -70,13 +49,11 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -85,16 +62,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g=
github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw=
github.com/coreos/go-oidc/v3 v3.5.0 h1:VxKtbccHZxs8juq7RdJntSqtXFtde9YpNpGn0yqgEHw=
github.com/coreos/go-oidc/v3 v3.5.0/go.mod h1:ecXRtV4romGPeO6ieExAsUK9cb/3fp9hXNz1tlv8PIM=
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=
@@ -102,17 +71,12 @@ github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/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-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
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=
@@ -138,8 +102,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -155,10 +117,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -169,18 +129,17 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
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-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.2.1/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=
@@ -188,35 +147,18 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
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.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
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/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/ishidawataru/sctp v0.0.0-20210707070123-9a39160e9062 h1:G1+wBT0dwjIrBdLy0MIG0i+E4CQxEnedHXdauJEIH6g=
github.com/ishidawataru/sctp v0.0.0-20210707070123-9a39160e9062/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@@ -246,8 +188,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@@ -288,18 +230,15 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
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/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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -308,23 +247,22 @@ github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN
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.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.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
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-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -350,8 +288,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
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=
@@ -360,11 +296,9 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -393,47 +327,26 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
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-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-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8=
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -442,11 +355,10 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -478,57 +390,33 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
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-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-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-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-20210514084401-e8d321eab015/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-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/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-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
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.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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=
@@ -572,27 +460,13 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
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=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -609,29 +483,6 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
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=
@@ -663,61 +514,12 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
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-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-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-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
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.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -730,26 +532,6 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
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=
@@ -762,7 +544,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
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.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
@@ -776,18 +557,15 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
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.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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -93,7 +93,7 @@ func extractValue(currentKey string, value interface{}) interface{} {
currentKeyWithoutIndex := currentKey[:startOfBracket]
// if currentKeyWithoutIndex contains only an index (i.e. [0] or 0)
if len(currentKeyWithoutIndex) == 0 {
array := value.([]interface{})
array, _ := value.([]interface{})
if len(array) > arrayIndex {
if isNestedArray {
return extractValue(currentKey[endOfBracket+1:], array[arrayIndex])
@@ -106,7 +106,7 @@ func extractValue(currentKey string, value interface{}) interface{} {
return nil
}
// if currentKeyWithoutIndex contains both a key and an index (i.e. data[0])
array := value.(map[string]interface{})[currentKeyWithoutIndex].([]interface{})
array, _ := value.(map[string]interface{})[currentKeyWithoutIndex].([]interface{})
if len(array) > arrayIndex {
if isNestedArray {
return extractValue(currentKey[endOfBracket+1:], array[arrayIndex])

19
main.go
View File

@@ -52,14 +52,15 @@ func save() {
}
}
func loadConfiguration() (cfg *config.Config, err error) {
customConfigFile := os.Getenv("GATUS_CONFIG_FILE")
if len(customConfigFile) > 0 {
cfg, err = config.Load(customConfigFile)
} else {
cfg, err = config.LoadDefaultConfiguration()
func loadConfiguration() (*config.Config, error) {
configPath := os.Getenv("GATUS_CONFIG_PATH")
// Backwards compatibility
if len(configPath) == 0 {
if configPath = os.Getenv("GATUS_CONFIG_FILE"); len(configPath) > 0 {
log.Println("WARNING: GATUS_CONFIG_FILE is deprecated. Please use GATUS_CONFIG_PATH instead.")
}
}
return
return config.LoadConfiguration(configPath)
}
// initializeStorage initializes the storage provider
@@ -79,14 +80,14 @@ func initializeStorage(cfg *config.Config) {
}
numberOfEndpointStatusesDeleted := store.Get().DeleteAllEndpointStatusesNotInKeys(keys)
if numberOfEndpointStatusesDeleted > 0 {
log.Printf("[config][validateStorageConfig] Deleted %d endpoint statuses because their matching endpoints no longer existed", numberOfEndpointStatusesDeleted)
log.Printf("[main][initializeStorage] Deleted %d endpoint statuses because their matching endpoints no longer existed", numberOfEndpointStatusesDeleted)
}
}
func listenToConfigurationFileChanges(cfg *config.Config) {
for {
time.Sleep(30 * time.Second)
if cfg.HasLoadedConfigurationFileBeenModified() {
if cfg.HasLoadedConfigurationBeenModified() {
log.Println("[main][listenToConfigurationFileChanges] Configuration file has been modified")
stop()
time.Sleep(time.Second) // Wait a bit to make sure everything is done.

View File

@@ -1,6 +1,7 @@
package store
import (
"path/filepath"
"testing"
"time"
@@ -566,6 +567,7 @@ func TestGet(t *testing.T) {
}
func TestInitialize(t *testing.T) {
dir := t.TempDir()
type Scenario struct {
Name string
Cfg *storage.Config
@@ -594,7 +596,7 @@ func TestInitialize(t *testing.T) {
},
{
Name: "sqlite-with-path",
Cfg: &storage.Config{Type: storage.TypeSQLite, Path: t.TempDir() + "/TestInitialize_sqlite-with-path.db"},
Cfg: &storage.Config{Type: storage.TypeSQLite, Path: filepath.Join(dir, "TestInitialize_sqlite-with-path.db")},
ExpectedErr: nil,
},
}
@@ -629,7 +631,7 @@ func TestInitialize(t *testing.T) {
}
func TestAutoSave(t *testing.T) {
file := t.TempDir() + "/TestAutoSave.db"
file := filepath.Join(t.TempDir(), "/TestAutoSave.db")
if err := Initialize(&storage.Config{Path: file}); err != nil {
t.Fatal("shouldn't have returned an error")
}

1
vendor/github.com/TwiN/deepmerge/.gitattributes generated vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto eol=lf

13
vendor/github.com/TwiN/deepmerge/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,13 @@
# IDE
*.iml
.idea
.vscode
# OS
.DS_Store
# JS
node_modules
# Go
/vendor

21
vendor/github.com/TwiN/deepmerge/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 TwiN
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

135
vendor/github.com/TwiN/deepmerge/README.md generated vendored Normal file
View File

@@ -0,0 +1,135 @@
# deepmerge
![test](https://github.com/TwiN/deepmerge/workflows/test/badge.svg?branch=master)
Go library for deep merging YAML or JSON files.
## Usage
### YAML
```go
package main
import (
"github.com/TwiN/deepmerge"
)
func main() {
dst := `
debug: true
client:
insecure: true
users:
- id: 1
firstName: John
lastName: Doe
- id: 2
firstName: Jane
lastName: Doe`
src := `
client:
timeout: 5s
users:
- id: 3
firstName: Bob
lastName: Smith`
output, err := deepmerge.YAML([]byte(dst), []byte(src))
if err != nil {
panic(err)
}
println(string(output))
}
```
Output:
```yaml
client:
insecure: true
timeout: 5s
debug: true
users:
- firstName: John
id: 1
lastName: Doe
- firstName: Jane
id: 2
lastName: Doe
- firstName: Bob
id: 3
lastName: Smith
```
### JSON
```go
package main
import (
"github.com/TwiN/deepmerge"
)
func main() {
dst := `{
"debug": true,
"client": {
"insecure": true
},
"users": [
{
"id": 1,
"firstName": "John",
"lastName": "Doe"
},
{
"id": 2,
"firstName": "Jane",
"lastName": "Doe"
}
]
}`
src := `{
"client": {
"timeout": "5s"
},
"users": [
{
"id": 3,
"firstName": "Bob",
"lastName": "Smith"
}
]
}`
output, err := deepmerge.JSON([]byte(dst), []byte(src))
if err != nil {
panic(err)
}
println(string(output))
}
```
Output:
```json
{
"client": {
"insecure": true,
"timeout": "5s"
},
"debug": true,
"users": [
{
"firstName": "John",
"id": 1,
"lastName": "Doe"
},
{
"firstName": "Jane",
"id": 2,
"lastName": "Doe"
},
{
"firstName": "Bob",
"id": 3,
"lastName": "Smith"
}
]
}
```

10
vendor/github.com/TwiN/deepmerge/config.go generated vendored Normal file
View File

@@ -0,0 +1,10 @@
package deepmerge
type Config struct {
// PreventMultipleDefinitionsOfKeysWithPrimitiveValue causes the return of an error if dst and src define
// the same key and if said key has a value with a primitive type
// This does not apply to slices or maps.
//
// Defaults to true
PreventMultipleDefinitionsOfKeysWithPrimitiveValue bool
}

48
vendor/github.com/TwiN/deepmerge/deepmerge.go generated vendored Normal file
View File

@@ -0,0 +1,48 @@
package deepmerge
import (
"errors"
)
var (
ErrKeyWithPrimitiveValueDefinedMoreThanOnce = errors.New("error due to parameter with value of primitive type: only maps and slices/arrays can be merged, which means you cannot have define the same key twice for parameters that are not maps or slices/arrays")
)
func DeepMerge(dst, src map[string]interface{}, config Config) error {
for srcKey, srcValue := range src {
if srcValueAsMap, ok := srcValue.(map[string]interface{}); ok { // handle maps
if dstValue, ok := dst[srcKey]; ok {
if dstValueAsMap, ok := dstValue.(map[string]interface{}); ok {
err := DeepMerge(dstValueAsMap, srcValueAsMap, config)
if err != nil {
return err
}
continue
}
} else {
dst[srcKey] = make(map[string]interface{})
}
err := DeepMerge(dst[srcKey].(map[string]interface{}), srcValueAsMap, config)
if err != nil {
return err
}
} else if srcValueAsSlice, ok := srcValue.([]interface{}); ok { // handle slices
if dstValue, ok := dst[srcKey]; ok {
if dstValueAsSlice, ok := dstValue.([]interface{}); ok {
// If both src and dst are slices, we'll copy the elements from that src slice over to the dst slice
dst[srcKey] = append(dstValueAsSlice, srcValueAsSlice...)
continue
}
}
dst[srcKey] = srcValueAsSlice
} else { // handle primitives
if config.PreventMultipleDefinitionsOfKeysWithPrimitiveValue {
if _, ok := dst[srcKey]; ok {
return ErrKeyWithPrimitiveValueDefinedMoreThanOnce
}
}
dst[srcKey] = srcValue
}
}
return nil
}

31
vendor/github.com/TwiN/deepmerge/json.go generated vendored Normal file
View File

@@ -0,0 +1,31 @@
package deepmerge
import (
"encoding/json"
)
// JSON merges the contents of src into dst
func JSON(dst, src []byte, optionalConfig ...Config) ([]byte, error) {
var cfg Config
if len(optionalConfig) > 0 {
cfg = optionalConfig[0]
} else {
cfg = Config{PreventMultipleDefinitionsOfKeysWithPrimitiveValue: true}
}
var dstMap, srcMap map[string]interface{}
err := json.Unmarshal(dst, &dstMap)
if err != nil {
return nil, err
}
err = json.Unmarshal(src, &srcMap)
if err != nil {
return nil, err
}
if dstMap == nil {
dstMap = make(map[string]interface{})
}
if err = DeepMerge(dstMap, srcMap, cfg); err != nil {
return nil, err
}
return json.Marshal(dstMap)
}

31
vendor/github.com/TwiN/deepmerge/yaml.go generated vendored Normal file
View File

@@ -0,0 +1,31 @@
package deepmerge
import (
"gopkg.in/yaml.v3"
)
// YAML merges the contents of src into dst
func YAML(dst, src []byte, optionalConfig ...Config) ([]byte, error) {
var cfg Config
if len(optionalConfig) > 0 {
cfg = optionalConfig[0]
} else {
cfg = Config{PreventMultipleDefinitionsOfKeysWithPrimitiveValue: true}
}
var dstMap, srcMap map[string]interface{}
err := yaml.Unmarshal(dst, &dstMap)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(src, &srcMap)
if err != nil {
return nil, err
}
if dstMap == nil {
dstMap = make(map[string]interface{})
}
if err = DeepMerge(dstMap, srcMap, cfg); err != nil {
return nil, err
}
return yaml.Marshal(dstMap)
}

View File

@@ -39,10 +39,24 @@ func (h *healthHandler) WithJSON(v bool) *healthHandler {
// ServeHTTP serves the HTTP request for the health handler
func (h *healthHandler) ServeHTTP(writer http.ResponseWriter, _ *http.Request) {
var statusCode int
var body []byte
statusCode, body, useJSON := h.getResponseStatusCodeAndBodyAndWhetherBodyUsesJSON()
if useJSON {
writer.Header().Set("Content-Type", "application/json")
}
writer.WriteHeader(statusCode)
_, _ = writer.Write(body)
}
func (h *healthHandler) GetResponseStatusCodeAndBody() (statusCode int, body []byte) {
statusCode, body, _ = h.getResponseStatusCodeAndBodyAndWhetherBodyUsesJSON()
return statusCode, body
}
func (h *healthHandler) getResponseStatusCodeAndBodyAndWhetherBodyUsesJSON() (statusCode int, body []byte, useJSON bool) {
var status Status
var reason string
h.mutex.RLock()
status, reason, useJSON := h.status, h.reason, h.useJSON
status, reason, useJSON = h.status, h.reason, h.useJSON
h.mutex.RUnlock()
if status == Up {
statusCode = http.StatusOK
@@ -52,7 +66,6 @@ func (h *healthHandler) ServeHTTP(writer http.ResponseWriter, _ *http.Request) {
if useJSON {
// We can safely ignore the error here because we know that both values are strings, therefore are supported encoders.
body, _ = json.Marshal(responseBody{Status: string(status), Reason: reason})
writer.Header().Set("Content-Type", "application/json")
} else {
if len(reason) == 0 {
body = []byte(status)
@@ -60,8 +73,7 @@ func (h *healthHandler) ServeHTTP(writer http.ResponseWriter, _ *http.Request) {
body = []byte(string(status) + ": " + reason)
}
}
writer.WriteHeader(statusCode)
_, _ = writer.Write(body)
return
}
// Handler retrieves the health handler

View File

@@ -12,7 +12,7 @@ import (
"sync"
"time"
jose "gopkg.in/square/go-jose.v2"
jose "github.com/go-jose/go-jose/v3"
)
// StaticKeySet is a verifier that validates JWT against a static set of public keys.

View File

@@ -12,8 +12,8 @@ import (
"strings"
"time"
jose "github.com/go-jose/go-jose/v3"
"golang.org/x/oauth2"
jose "gopkg.in/square/go-jose.v2"
)
const (

View File

@@ -1,8 +1,2 @@
*~
.*.swp
*.out
*.test
*.pem
*.cov
jose-util/jose-util
jose-util.t.err

53
vendor/github.com/go-jose/go-jose/v3/.golangci.yml generated vendored Normal file
View File

@@ -0,0 +1,53 @@
# https://github.com/golangci/golangci-lint
run:
skip-files:
- doc_test.go
modules-download-mode: readonly
linters:
enable-all: true
disable:
- gochecknoglobals
- goconst
- lll
- maligned
- nakedret
- scopelint
- unparam
- funlen # added in 1.18 (requires go-jose changes before it can be enabled)
linters-settings:
gocyclo:
min-complexity: 35
issues:
exclude-rules:
- text: "don't use ALL_CAPS in Go names"
linters:
- golint
- text: "hardcoded credentials"
linters:
- gosec
- text: "weak cryptographic primitive"
linters:
- gosec
- path: json/
linters:
- dupl
- errcheck
- gocritic
- gocyclo
- golint
- govet
- ineffassign
- staticcheck
- structcheck
- stylecheck
- unused
- path: _test\.go
linters:
- scopelint
- path: jwk.go
linters:
- gocyclo

33
vendor/github.com/go-jose/go-jose/v3/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,33 @@
language: go
matrix:
fast_finish: true
allow_failures:
- go: tip
go:
- "1.13.x"
- "1.14.x"
- tip
before_script:
- export PATH=$HOME/.local/bin:$PATH
before_install:
- go get -u github.com/mattn/goveralls github.com/wadey/gocovmerge
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.18.0
- pip install cram --user
script:
- go test -v -covermode=count -coverprofile=profile.cov .
- go test -v -covermode=count -coverprofile=cryptosigner/profile.cov ./cryptosigner
- go test -v -covermode=count -coverprofile=cipher/profile.cov ./cipher
- go test -v -covermode=count -coverprofile=jwt/profile.cov ./jwt
- go test -v ./json # no coverage for forked encoding/json package
- golangci-lint run
- cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t # cram tests jose-util
- cd ..
after_success:
- gocovmerge *.cov */*.cov > merged.coverprofile
- goveralls -coverprofile merged.coverprofile -service=travis-ci

View File

@@ -9,6 +9,7 @@ sure all tests pass by running `go test`, and format your code with `go fmt`.
We also recommend using `golint` and `errcheck`.
Before your code can be accepted into the project you must also sign the
[Individual Contributor License Agreement][1].
Individual Contributor License Agreement. We use [cla-assistant.io][1] and you
will be prompted to sign once a pull request is opened.
[1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
[1]: https://cla-assistant.io/

View File

@@ -1,10 +1,10 @@
# Go JOSE
# Go JOSE
[![godoc](http://img.shields.io/badge/godoc-version_1-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v1)
[![godoc](http://img.shields.io/badge/godoc-version_2-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v2)
[![license](http://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/square/go-jose/master/LICENSE)
[![build](https://travis-ci.org/square/go-jose.svg?branch=v2)](https://travis-ci.org/square/go-jose)
[![coverage](https://coveralls.io/repos/github/square/go-jose/badge.svg?branch=v2)](https://coveralls.io/r/square/go-jose)
[![godoc](http://img.shields.io/badge/godoc-jose_package-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v2)
[![godoc](http://img.shields.io/badge/godoc-jwt_package-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v2/jwt)
[![license](http://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/go-jose/go-jose/master/LICENSE)
[![build](https://travis-ci.org/go-jose/go-jose.svg?branch=master)](https://travis-ci.org/go-jose/go-jose)
[![coverage](https://coveralls.io/repos/github/go-jose/go-jose/badge.svg?branch=master)](https://coveralls.io/r/go-jose/go-jose)
Package jose aims to provide an implementation of the Javascript Object Signing
and Encryption set of standards. This includes support for JSON Web Encryption,
@@ -23,11 +23,11 @@ US maintained blocked list.
The implementation follows the
[JSON Web Encryption](http://dx.doi.org/10.17487/RFC7516) (RFC 7516),
[JSON Web Signature](http://dx.doi.org/10.17487/RFC7515) (RFC 7515), and
[JSON Web Token](http://dx.doi.org/10.17487/RFC7519) (RFC 7519).
[JSON Web Token](http://dx.doi.org/10.17487/RFC7519) (RFC 7519) specifications.
Tables of supported algorithms are shown below. The library supports both
the compact and full serialization formats, and has optional support for
the compact and JWS/JWE JSON Serialization formats, and has optional support for
multiple recipients. It also comes with a small command-line utility
([`jose-util`](https://github.com/square/go-jose/tree/v2/jose-util))
([`jose-util`](https://github.com/go-jose/go-jose/tree/master/jose-util))
for dealing with JOSE messages in a shell.
**Note**: We use a forked version of the `encoding/json` package from the Go
@@ -38,20 +38,24 @@ libraries in other languages.
### Versions
We use [gopkg.in](https://gopkg.in) for versioning.
[Version 2](https://gopkg.in/go-jose/go-jose.v2)
([branch](https://github.com/go-jose/go-jose/tree/v2),
[doc](https://godoc.org/gopkg.in/go-jose/go-jose.v2)) is the current stable version:
[Version 2](https://gopkg.in/square/go-jose.v2)
([branch](https://github.com/square/go-jose/tree/v2),
[doc](https://godoc.org/gopkg.in/square/go-jose.v2)) is the current version:
import "gopkg.in/go-jose/go-jose.v2"
import "gopkg.in/square/go-jose.v2"
[Version 3](https://github.com/go-jose/go-jose)
([branch](https://github.com/go-jose/go-jose/tree/master),
[doc](https://godoc.org/github.com/go-jose/go-jose)) is the under development/unstable version (not released yet):
The old `v1` branch ([go-jose.v1](https://gopkg.in/square/go-jose.v1)) will
still receive backported bug fixes and security fixes, but otherwise
development is frozen. All new feature development takes place on the `v2`
branch. Version 2 also contains additional sub-packages such as the
[jwt](https://godoc.org/gopkg.in/square/go-jose.v2/jwt) implementation
contributed by [@shaxbee](https://github.com/shaxbee).
import "github.com/go-jose/go-jose/v3"
All new feature development takes place on the `master` branch, which we are
preparing to release as version 3 soon. Version 2 will continue to receive
critical bug and security fixes. Note that starting with version 3 we are
using Go modules for versioning instead of `gopkg.in` as before. Version 3 also will require Go version 1.13 or higher.
Version 1 (on the `v1` branch) is frozen and not supported anymore.
### Supported algorithms
@@ -84,7 +88,7 @@ standard where possible. The Godoc reference has a list of constants.
Content encryption | Algorithm identifier(s)
:------------------------- | :------------------------------
AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512
AES-GCM | A128GCM, A192GCM, A256GCM
AES-GCM | A128GCM, A192GCM, A256GCM
Compression | Algorithm identifiers(s)
:------------------------- | -------------------------------
@@ -101,18 +105,18 @@ allows attaching a key id.
:------------------------- | -------------------------------
RSA | *[rsa.PublicKey](http://golang.org/pkg/crypto/rsa/#PublicKey), *[rsa.PrivateKey](http://golang.org/pkg/crypto/rsa/#PrivateKey)
ECDH, ECDSA | *[ecdsa.PublicKey](http://golang.org/pkg/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](http://golang.org/pkg/crypto/ecdsa/#PrivateKey)
EdDSA<sup>1</sup> | [ed25519.PublicKey](https://godoc.org/golang.org/x/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://godoc.org/golang.org/x/crypto/ed25519#PrivateKey)
EdDSA<sup>1</sup> | [ed25519.PublicKey](https://godoc.org/pkg/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://godoc.org/pkg/crypto/ed25519#PrivateKey)
AES, HMAC | []byte
<sup>1. Only available in version 2 of the package</sup>
<sup>1. Only available in version 2 or later of the package</sup>
## Examples
[![godoc](http://img.shields.io/badge/godoc-version_1-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v1)
[![godoc](http://img.shields.io/badge/godoc-version_2-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v2)
[![godoc](http://img.shields.io/badge/godoc-jose_package-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v2)
[![godoc](http://img.shields.io/badge/godoc-jwt_package-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v2/jwt)
Examples can be found in the Godoc
reference for this package. The
[`jose-util`](https://github.com/square/go-jose/tree/v2/jose-util)
[`jose-util`](https://github.com/go-jose/go-jose/tree/master/jose-util)
subdirectory also contains a small command-line utility which might be useful
as an example.
as an example as well.

View File

@@ -20,6 +20,7 @@ import (
"crypto"
"crypto/aes"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
@@ -28,9 +29,8 @@ import (
"fmt"
"math/big"
"golang.org/x/crypto/ed25519"
josecipher "gopkg.in/square/go-jose.v2/cipher"
"gopkg.in/square/go-jose.v2/json"
josecipher "github.com/go-jose/go-jose/v3/cipher"
"github.com/go-jose/go-jose/v3/json"
)
// A generic RSA-based encrypter/verifier
@@ -413,28 +413,28 @@ func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) {
func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
epk, err := headers.getEPK()
if err != nil {
return nil, errors.New("square/go-jose: invalid epk header")
return nil, errors.New("go-jose/go-jose: invalid epk header")
}
if epk == nil {
return nil, errors.New("square/go-jose: missing epk header")
return nil, errors.New("go-jose/go-jose: missing epk header")
}
publicKey, ok := epk.Key.(*ecdsa.PublicKey)
if publicKey == nil || !ok {
return nil, errors.New("square/go-jose: invalid epk header")
return nil, errors.New("go-jose/go-jose: invalid epk header")
}
if !ctx.privateKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) {
return nil, errors.New("square/go-jose: invalid public key in epk header")
return nil, errors.New("go-jose/go-jose: invalid public key in epk header")
}
apuData, err := headers.getAPU()
if err != nil {
return nil, errors.New("square/go-jose: invalid apu header")
return nil, errors.New("go-jose/go-jose: invalid apu header")
}
apvData, err := headers.getAPV()
if err != nil {
return nil, errors.New("square/go-jose: invalid apv header")
return nil, errors.New("go-jose/go-jose: invalid apv header")
}
deriveKey := func(algID string, size int) []byte {
@@ -489,7 +489,7 @@ func (ctx edEncrypterVerifier) verifyPayload(payload []byte, signature []byte, a
}
ok := ed25519.Verify(ctx.publicKey, payload, signature)
if !ok {
return errors.New("square/go-jose: ed25519 signature failed to verify")
return errors.New("go-jose/go-jose: ed25519 signature failed to verify")
}
return nil
}
@@ -513,7 +513,7 @@ func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm)
curveBits := ctx.privateKey.Curve.Params().BitSize
if expectedBitSize != curveBits {
return Signature{}, fmt.Errorf("square/go-jose: expected %d bit key, got %d bits instead", expectedBitSize, curveBits)
return Signature{}, fmt.Errorf("go-jose/go-jose: expected %d bit key, got %d bits instead", expectedBitSize, curveBits)
}
hasher := hash.New()
@@ -571,7 +571,7 @@ func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, a
}
if len(signature) != 2*keySize {
return fmt.Errorf("square/go-jose: invalid signature size, have %d bytes, wanted %d", len(signature), 2*keySize)
return fmt.Errorf("go-jose/go-jose: invalid signature size, have %d bytes, wanted %d", len(signature), 2*keySize)
}
hasher := hash.New()
@@ -585,7 +585,7 @@ func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, a
match := ecdsa.Verify(ctx.publicKey, hashed, r, s)
if !match {
return errors.New("square/go-jose: ecdsa signature failed to verify")
return errors.New("go-jose/go-jose: ecdsa signature failed to verify")
}
return nil

View File

@@ -101,23 +101,23 @@ func (ctx *cbcAEAD) Seal(dst, nonce, plaintext, data []byte) []byte {
// Open decrypts and authenticates the ciphertext.
func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
if len(ciphertext) < ctx.authtagBytes {
return nil, errors.New("square/go-jose: invalid ciphertext (too short)")
return nil, errors.New("go-jose/go-jose: invalid ciphertext (too short)")
}
offset := len(ciphertext) - ctx.authtagBytes
expectedTag := ctx.computeAuthTag(data, nonce, ciphertext[:offset])
match := subtle.ConstantTimeCompare(expectedTag, ciphertext[offset:])
if match != 1 {
return nil, errors.New("square/go-jose: invalid ciphertext (auth tag mismatch)")
return nil, errors.New("go-jose/go-jose: invalid ciphertext (auth tag mismatch)")
}
cbc := cipher.NewCBCDecrypter(ctx.blockCipher, nonce)
// Make copy of ciphertext buffer, don't want to modify in place
buffer := append([]byte{}, []byte(ciphertext[:offset])...)
buffer := append([]byte{}, ciphertext[:offset]...)
if len(buffer)%ctx.blockCipher.BlockSize() > 0 {
return nil, errors.New("square/go-jose: invalid ciphertext (invalid length)")
return nil, errors.New("go-jose/go-jose: invalid ciphertext (invalid length)")
}
cbc.CryptBlocks(buffer, buffer)
@@ -177,19 +177,19 @@ func padBuffer(buffer []byte, blockSize int) []byte {
// Remove padding
func unpadBuffer(buffer []byte, blockSize int) ([]byte, error) {
if len(buffer)%blockSize != 0 {
return nil, errors.New("square/go-jose: invalid padding")
return nil, errors.New("go-jose/go-jose: invalid padding")
}
last := buffer[len(buffer)-1]
count := int(last)
if count == 0 || count > blockSize || count > len(buffer) {
return nil, errors.New("square/go-jose: invalid padding")
return nil, errors.New("go-jose/go-jose: invalid padding")
}
padding := bytes.Repeat([]byte{last}, count)
if !bytes.HasSuffix(buffer, padding) {
return nil, errors.New("square/go-jose: invalid padding")
return nil, errors.New("go-jose/go-jose: invalid padding")
}
return buffer[:len(buffer)-count], nil

View File

@@ -28,7 +28,7 @@ var defaultIV = []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6}
// KeyWrap implements NIST key wrapping; it wraps a content encryption key (cek) with the given block cipher.
func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) {
if len(cek)%8 != 0 {
return nil, errors.New("square/go-jose: key wrap input must be 8 byte blocks")
return nil, errors.New("go-jose/go-jose: key wrap input must be 8 byte blocks")
}
n := len(cek) / 8
@@ -51,7 +51,7 @@ func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) {
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
for i := 0; i < 8; i++ {
buffer[i] = buffer[i] ^ tBytes[i]
buffer[i] ^= tBytes[i]
}
copy(r[t%n], buffer[8:])
}
@@ -68,7 +68,7 @@ func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) {
// KeyUnwrap implements NIST key unwrapping; it unwraps a content encryption key (cek) with the given block cipher.
func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) {
if len(ciphertext)%8 != 0 {
return nil, errors.New("square/go-jose: key wrap input must be 8 byte blocks")
return nil, errors.New("go-jose/go-jose: key wrap input must be 8 byte blocks")
}
n := (len(ciphertext) / 8) - 1
@@ -87,7 +87,7 @@ func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) {
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
for i := 0; i < 8; i++ {
buffer[i] = buffer[i] ^ tBytes[i]
buffer[i] ^= tBytes[i]
}
copy(buffer[8:], r[t%n])
@@ -97,7 +97,7 @@ func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) {
}
if subtle.ConstantTimeCompare(buffer[:8], defaultIV) == 0 {
return nil, errors.New("square/go-jose: failed to unwrap key")
return nil, errors.New("go-jose/go-jose: failed to unwrap key")
}
out := make([]byte, n*8)

View File

@@ -23,7 +23,7 @@ import (
"fmt"
"reflect"
"gopkg.in/square/go-jose.v2/json"
"github.com/go-jose/go-jose/v3/json"
)
// Encrypter represents an encrypter which produces an encrypted JWE object.
@@ -201,8 +201,8 @@ func NewMultiEncrypter(enc ContentEncryption, rcpts []Recipient, opts *Encrypter
if cipher == nil {
return nil, ErrUnsupportedAlgorithm
}
if rcpts == nil || len(rcpts) == 0 {
return nil, fmt.Errorf("square/go-jose: recipients is nil or empty")
if len(rcpts) == 0 {
return nil, fmt.Errorf("go-jose/go-jose: recipients is nil or empty")
}
encrypter := &genericEncrypter{
@@ -234,7 +234,7 @@ func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) {
switch recipient.Algorithm {
case DIRECT, ECDH_ES:
return fmt.Errorf("square/go-jose: key algorithm '%s' not supported in multi-recipient mode", recipient.Algorithm)
return fmt.Errorf("go-jose/go-jose: key algorithm '%s' not supported in multi-recipient mode", recipient.Algorithm)
}
recipientInfo, err = makeJWERecipient(recipient.Algorithm, recipient.Key)
@@ -326,7 +326,7 @@ func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JSONWe
obj.recipients = make([]recipientInfo, len(ctx.recipients))
if len(ctx.recipients) == 0 {
return nil, fmt.Errorf("square/go-jose: no recipients to encrypt to")
return nil, fmt.Errorf("go-jose/go-jose: no recipients to encrypt to")
}
cek, headers, err := ctx.keyGenerator.genKey()
@@ -410,26 +410,27 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
headers := obj.mergedHeaders(nil)
if len(obj.recipients) > 1 {
return nil, errors.New("square/go-jose: too many recipients in payload; expecting only one")
return nil, errors.New("go-jose/go-jose: too many recipients in payload; expecting only one")
}
critical, err := headers.getCritical()
if err != nil {
return nil, fmt.Errorf("square/go-jose: invalid crit header")
return nil, fmt.Errorf("go-jose/go-jose: invalid crit header")
}
if len(critical) > 0 {
return nil, fmt.Errorf("square/go-jose: unsupported crit header")
return nil, fmt.Errorf("go-jose/go-jose: unsupported crit header")
}
decrypter, err := newDecrypter(decryptionKey)
key := tryJWKS(decryptionKey, obj.Header)
decrypter, err := newDecrypter(key)
if err != nil {
return nil, err
}
cipher := getContentCipher(headers.getEncryption())
if cipher == nil {
return nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(headers.getEncryption()))
return nil, fmt.Errorf("go-jose/go-jose: unsupported enc value '%s'", string(headers.getEncryption()))
}
generator := randomKeyGenerator{
@@ -475,14 +476,15 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade
critical, err := globalHeaders.getCritical()
if err != nil {
return -1, Header{}, nil, fmt.Errorf("square/go-jose: invalid crit header")
return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: invalid crit header")
}
if len(critical) > 0 {
return -1, Header{}, nil, fmt.Errorf("square/go-jose: unsupported crit header")
return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: unsupported crit header")
}
decrypter, err := newDecrypter(decryptionKey)
key := tryJWKS(decryptionKey, obj.Header)
decrypter, err := newDecrypter(key)
if err != nil {
return -1, Header{}, nil, err
}
@@ -490,7 +492,7 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade
encryption := globalHeaders.getEncryption()
cipher := getContentCipher(encryption)
if cipher == nil {
return -1, Header{}, nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(encryption))
return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: unsupported enc value '%s'", string(encryption))
}
generator := randomKeyGenerator{
@@ -524,18 +526,18 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade
}
}
if plaintext == nil || err != nil {
if plaintext == nil {
return -1, Header{}, nil, ErrCryptoFailure
}
// The "zip" header parameter may only be present in the protected header.
if comp := obj.protected.getCompression(); comp != "" {
plaintext, err = decompress(comp, plaintext)
plaintext, _ = decompress(comp, plaintext)
}
sanitized, err := headers.sanitized()
if err != nil {
return -1, Header{}, nil, fmt.Errorf("square/go-jose: failed to sanitize header: %v", err)
return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: failed to sanitize header: %v", err)
}
return index, sanitized, plaintext, err

View File

@@ -18,9 +18,9 @@
Package jose aims to provide an implementation of the Javascript Object Signing
and Encryption set of standards. It implements encryption and signing based on
the JSON Web Encryption and JSON Web Signature standards, with optional JSON
Web Token support available in a sub-package. The library supports both the
compact and full serialization formats, and has optional support for multiple
the JSON Web Encryption and JSON Web Signature standards, with optional JSON Web
Token support available in a sub-package. The library supports both the compact
and JWS/JWE JSON Serialization formats, and has optional support for multiple
recipients.
*/

View File

@@ -26,7 +26,7 @@ import (
"strings"
"unicode"
"gopkg.in/square/go-jose.v2/json"
"github.com/go-jose/go-jose/v3/json"
)
// Helper function to serialize known-good objects.
@@ -41,7 +41,7 @@ func mustSerializeJSON(value interface{}) []byte {
// MarshalJSON will happily serialize it as the top-level value "null". If
// that value is then embedded in another operation, for instance by being
// base64-encoded and fed as input to a signing algorithm
// (https://github.com/square/go-jose/issues/22), the result will be
// (https://github.com/go-jose/go-jose/issues/22), the result will be
// incorrect. Because this method is intended for known-good objects, and a nil
// pointer is not a known-good object, we are free to panic in this case.
// Note: It's not possible to directly check whether the data pointed at by an
@@ -127,7 +127,7 @@ func newBuffer(data []byte) *byteBuffer {
func newFixedSizeBuffer(data []byte, length int) *byteBuffer {
if len(data) > length {
panic("square/go-jose: invalid call to newFixedSizeBuffer (len(data) > length)")
panic("go-jose/go-jose: invalid call to newFixedSizeBuffer (len(data) > length)")
}
pad := make([]byte, length-len(data))
return newBuffer(append(pad, data...))
@@ -154,7 +154,7 @@ func (b *byteBuffer) UnmarshalJSON(data []byte) error {
return nil
}
decoded, err := base64.RawURLEncoding.DecodeString(encoded)
decoded, err := base64URLDecode(encoded)
if err != nil {
return err
}
@@ -183,3 +183,9 @@ func (b byteBuffer) bigInt() *big.Int {
func (b byteBuffer) toInt() int {
return int(b.bigInt().Int64())
}
// base64URLDecode is implemented as defined in https://www.rfc-editor.org/rfc/rfc7515.html#appendix-C
func base64URLDecode(value string) ([]byte, error) {
value = strings.TrimRight(value, "=")
return base64.RawURLEncoding.DecodeString(value)
}

View File

@@ -648,7 +648,7 @@ func encodeByteSlice(e *encodeState, v reflect.Value, _ bool) {
// for large buffers, avoid unnecessary extra temporary
// buffer space.
enc := base64.NewEncoder(base64.StdEncoding, e)
enc.Write(s)
_, _ = enc.Write(s)
enc.Close()
}
e.WriteByte('"')

View File

@@ -21,7 +21,7 @@ import (
"fmt"
"strings"
"gopkg.in/square/go-jose.v2/json"
"github.com/go-jose/go-jose/v3/json"
)
// rawJSONWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
@@ -86,11 +86,12 @@ func (obj JSONWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader {
func (obj JSONWebEncryption) computeAuthData() []byte {
var protected string
if obj.original != nil && obj.original.Protected != nil {
switch {
case obj.original != nil && obj.original.Protected != nil:
protected = obj.original.Protected.base64()
} else if obj.protected != nil {
case obj.protected != nil:
protected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON((obj.protected)))
} else {
default:
protected = ""
}
@@ -103,7 +104,7 @@ func (obj JSONWebEncryption) computeAuthData() []byte {
return output
}
// ParseEncrypted parses an encrypted message in compact or full serialization format.
// ParseEncrypted parses an encrypted message in compact or JWE JSON Serialization format.
func ParseEncrypted(input string) (*JSONWebEncryption, error) {
input = stripWhitespace(input)
if strings.HasPrefix(input, "{") {
@@ -146,7 +147,7 @@ func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
err := json.Unmarshal(parsed.Protected.bytes(), &obj.protected)
if err != nil {
return nil, fmt.Errorf("square/go-jose: invalid protected header: %s, %s", err, parsed.Protected.base64())
return nil, fmt.Errorf("go-jose/go-jose: invalid protected header: %s, %s", err, parsed.Protected.base64())
}
}
@@ -156,7 +157,7 @@ func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
mergedHeaders := obj.mergedHeaders(nil)
obj.Header, err = mergedHeaders.sanitized()
if err != nil {
return nil, fmt.Errorf("square/go-jose: cannot sanitize merged headers: %v (%v)", err, mergedHeaders)
return nil, fmt.Errorf("go-jose/go-jose: cannot sanitize merged headers: %v (%v)", err, mergedHeaders)
}
if len(parsed.Recipients) == 0 {
@@ -169,7 +170,7 @@ func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
} else {
obj.recipients = make([]recipientInfo, len(parsed.Recipients))
for r := range parsed.Recipients {
encryptedKey, err := base64.RawURLEncoding.DecodeString(parsed.Recipients[r].EncryptedKey)
encryptedKey, err := base64URLDecode(parsed.Recipients[r].EncryptedKey)
if err != nil {
return nil, err
}
@@ -187,7 +188,7 @@ func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
for _, recipient := range obj.recipients {
headers := obj.mergedHeaders(&recipient)
if headers.getAlgorithm() == "" || headers.getEncryption() == "" {
return nil, fmt.Errorf("square/go-jose: message is missing alg/enc headers")
return nil, fmt.Errorf("go-jose/go-jose: message is missing alg/enc headers")
}
}
@@ -203,30 +204,30 @@ func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
func parseEncryptedCompact(input string) (*JSONWebEncryption, error) {
parts := strings.Split(input, ".")
if len(parts) != 5 {
return nil, fmt.Errorf("square/go-jose: compact JWE format must have five parts")
return nil, fmt.Errorf("go-jose/go-jose: compact JWE format must have five parts")
}
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
rawProtected, err := base64URLDecode(parts[0])
if err != nil {
return nil, err
}
encryptedKey, err := base64.RawURLEncoding.DecodeString(parts[1])
encryptedKey, err := base64URLDecode(parts[1])
if err != nil {
return nil, err
}
iv, err := base64.RawURLEncoding.DecodeString(parts[2])
iv, err := base64URLDecode(parts[2])
if err != nil {
return nil, err
}
ciphertext, err := base64.RawURLEncoding.DecodeString(parts[3])
ciphertext, err := base64URLDecode(parts[3])
if err != nil {
return nil, err
}
tag, err := base64.RawURLEncoding.DecodeString(parts[4])
tag, err := base64URLDecode(parts[4])
if err != nil {
return nil, err
}

View File

@@ -20,6 +20,7 @@ import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
"crypto/sha1"
@@ -34,9 +35,7 @@ import (
"reflect"
"strings"
"golang.org/x/crypto/ed25519"
"gopkg.in/square/go-jose.v2/json"
"github.com/go-jose/go-jose/v3/json"
)
// rawJSONWebKey represents a public or private key in JWK format, used for parsing/serializing.
@@ -63,7 +62,7 @@ type rawJSONWebKey struct {
Qi *byteBuffer `json:"qi,omitempty"`
// Certificates
X5c []string `json:"x5c,omitempty"`
X5u *url.URL `json:"x5u,omitempty"`
X5u string `json:"x5u,omitempty"`
X5tSHA1 string `json:"x5t,omitempty"`
X5tSHA256 string `json:"x5t#S256,omitempty"`
}
@@ -110,7 +109,7 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
case []byte:
raw, err = fromSymmetricKey(key)
default:
return nil, fmt.Errorf("square/go-jose: unknown key type '%s'", reflect.TypeOf(key))
return nil, fmt.Errorf("go-jose/go-jose: unknown key type '%s'", reflect.TypeOf(key))
}
if err != nil {
@@ -129,13 +128,13 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
x5tSHA256Len := len(k.CertificateThumbprintSHA256)
if x5tSHA1Len > 0 {
if x5tSHA1Len != sha1.Size {
return nil, fmt.Errorf("square/go-jose: invalid SHA-1 thumbprint (must be %d bytes, not %d)", sha1.Size, x5tSHA1Len)
return nil, fmt.Errorf("go-jose/go-jose: invalid SHA-1 thumbprint (must be %d bytes, not %d)", sha1.Size, x5tSHA1Len)
}
raw.X5tSHA1 = base64.RawURLEncoding.EncodeToString(k.CertificateThumbprintSHA1)
}
if x5tSHA256Len > 0 {
if x5tSHA256Len != sha256.Size {
return nil, fmt.Errorf("square/go-jose: invalid SHA-256 thumbprint (must be %d bytes, not %d)", sha256.Size, x5tSHA256Len)
return nil, fmt.Errorf("go-jose/go-jose: invalid SHA-256 thumbprint (must be %d bytes, not %d)", sha256.Size, x5tSHA256Len)
}
raw.X5tSHA256 = base64.RawURLEncoding.EncodeToString(k.CertificateThumbprintSHA256)
}
@@ -149,14 +148,16 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
expectedSHA256 := sha256.Sum256(k.Certificates[0].Raw)
if len(k.CertificateThumbprintSHA1) > 0 && !bytes.Equal(k.CertificateThumbprintSHA1, expectedSHA1[:]) {
return nil, errors.New("square/go-jose: invalid SHA-1 thumbprint, does not match cert chain")
return nil, errors.New("go-jose/go-jose: invalid SHA-1 thumbprint, does not match cert chain")
}
if len(k.CertificateThumbprintSHA256) > 0 && !bytes.Equal(k.CertificateThumbprintSHA256, expectedSHA256[:]) {
return nil, errors.New("square/go-jose: invalid or SHA-256 thumbprint, does not match cert chain")
return nil, errors.New("go-jose/go-jose: invalid or SHA-256 thumbprint, does not match cert chain")
}
}
raw.X5u = k.CertificatesURL
if k.CertificatesURL != nil {
raw.X5u = k.CertificatesURL.String()
}
return json.Marshal(raw)
}
@@ -171,7 +172,7 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
certs, err := parseCertificateChain(raw.X5c)
if err != nil {
return fmt.Errorf("square/go-jose: failed to unmarshal x5c field: %s", err)
return fmt.Errorf("go-jose/go-jose: failed to unmarshal x5c field: %s", err)
}
var key interface{}
@@ -211,7 +212,7 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
}
case "oct":
if certPub != nil {
return errors.New("square/go-jose: invalid JWK, found 'oct' (symmetric) key with cert chain")
return errors.New("go-jose/go-jose: invalid JWK, found 'oct' (symmetric) key with cert chain")
}
key, err = raw.symmetricKey()
case "OKP":
@@ -226,10 +227,10 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
keyPub = key
}
} else {
err = fmt.Errorf("square/go-jose: unknown curve %s'", raw.Crv)
err = fmt.Errorf("go-jose/go-jose: unknown curve %s'", raw.Crv)
}
default:
err = fmt.Errorf("square/go-jose: unknown json web key type '%s'", raw.Kty)
err = fmt.Errorf("go-jose/go-jose: unknown json web key type '%s'", raw.Kty)
}
if err != nil {
@@ -238,19 +239,24 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
if certPub != nil && keyPub != nil {
if !reflect.DeepEqual(certPub, keyPub) {
return errors.New("square/go-jose: invalid JWK, public keys in key and x5c fields do not match")
return errors.New("go-jose/go-jose: invalid JWK, public keys in key and x5c fields do not match")
}
}
*k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use, Certificates: certs}
k.CertificatesURL = raw.X5u
if raw.X5u != "" {
k.CertificatesURL, err = url.Parse(raw.X5u)
if err != nil {
return fmt.Errorf("go-jose/go-jose: invalid JWK, x5u header is invalid URL: %w", err)
}
}
// x5t parameters are base64url-encoded SHA thumbprints
// See RFC 7517, Section 4.8, https://tools.ietf.org/html/rfc7517#section-4.8
x5tSHA1bytes, err := base64.RawURLEncoding.DecodeString(raw.X5tSHA1)
x5tSHA1bytes, err := base64URLDecode(raw.X5tSHA1)
if err != nil {
return errors.New("square/go-jose: invalid JWK, x5t header has invalid encoding")
return errors.New("go-jose/go-jose: invalid JWK, x5t header has invalid encoding")
}
// RFC 7517, Section 4.8 is ambiguous as to whether the digest output should be byte or hex,
@@ -260,7 +266,7 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
if len(x5tSHA1bytes) == 2*sha1.Size {
hx, err := hex.DecodeString(string(x5tSHA1bytes))
if err != nil {
return fmt.Errorf("square/go-jose: invalid JWK, unable to hex decode x5t: %v", err)
return fmt.Errorf("go-jose/go-jose: invalid JWK, unable to hex decode x5t: %v", err)
}
x5tSHA1bytes = hx
@@ -268,15 +274,15 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
k.CertificateThumbprintSHA1 = x5tSHA1bytes
x5tSHA256bytes, err := base64.RawURLEncoding.DecodeString(raw.X5tSHA256)
x5tSHA256bytes, err := base64URLDecode(raw.X5tSHA256)
if err != nil {
return errors.New("square/go-jose: invalid JWK, x5t#S256 header has invalid encoding")
return errors.New("go-jose/go-jose: invalid JWK, x5t#S256 header has invalid encoding")
}
if len(x5tSHA256bytes) == 2*sha256.Size {
hx256, err := hex.DecodeString(string(x5tSHA256bytes))
if err != nil {
return fmt.Errorf("square/go-jose: invalid JWK, unable to hex decode x5t#S256: %v", err)
return fmt.Errorf("go-jose/go-jose: invalid JWK, unable to hex decode x5t#S256: %v", err)
}
x5tSHA256bytes = hx256
}
@@ -286,10 +292,10 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
x5tSHA1Len := len(k.CertificateThumbprintSHA1)
x5tSHA256Len := len(k.CertificateThumbprintSHA256)
if x5tSHA1Len > 0 && x5tSHA1Len != sha1.Size {
return errors.New("square/go-jose: invalid JWK, x5t header is of incorrect size")
return errors.New("go-jose/go-jose: invalid JWK, x5t header is of incorrect size")
}
if x5tSHA256Len > 0 && x5tSHA256Len != sha256.Size {
return errors.New("square/go-jose: invalid JWK, x5t#S256 header is of incorrect size")
return errors.New("go-jose/go-jose: invalid JWK, x5t#S256 header is of incorrect size")
}
// If certificate chain *and* thumbprints are set, verify correctness.
@@ -299,11 +305,11 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
sha256sum := sha256.Sum256(leaf.Raw)
if len(k.CertificateThumbprintSHA1) > 0 && !bytes.Equal(sha1sum[:], k.CertificateThumbprintSHA1) {
return errors.New("square/go-jose: invalid JWK, x5c thumbprint does not match x5t value")
return errors.New("go-jose/go-jose: invalid JWK, x5c thumbprint does not match x5t value")
}
if len(k.CertificateThumbprintSHA256) > 0 && !bytes.Equal(sha256sum[:], k.CertificateThumbprintSHA256) {
return errors.New("square/go-jose: invalid JWK, x5c thumbprint does not match x5t#S256 value")
return errors.New("go-jose/go-jose: invalid JWK, x5c thumbprint does not match x5t#S256 value")
}
}
@@ -342,7 +348,7 @@ func ecThumbprintInput(curve elliptic.Curve, x, y *big.Int) (string, error) {
}
if len(x.Bytes()) > coordLength || len(y.Bytes()) > coordLength {
return "", errors.New("square/go-jose: invalid elliptic key (too large)")
return "", errors.New("go-jose/go-jose: invalid elliptic key (too large)")
}
return fmt.Sprintf(ecThumbprintTemplate, crv,
@@ -359,7 +365,7 @@ func rsaThumbprintInput(n *big.Int, e int) (string, error) {
func edThumbprintInput(ed ed25519.PublicKey) (string, error) {
crv := "Ed25519"
if len(ed) > 32 {
return "", errors.New("square/go-jose: invalid elliptic key (too large)")
return "", errors.New("go-jose/go-jose: invalid elliptic key (too large)")
}
return fmt.Sprintf(edThumbprintTemplate, crv,
newFixedSizeBuffer(ed, 32).base64()), nil
@@ -384,7 +390,7 @@ func (k *JSONWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
case ed25519.PrivateKey:
input, err = edThumbprintInput(ed25519.PublicKey(key[32:]))
default:
return nil, fmt.Errorf("square/go-jose: unknown key type '%s'", reflect.TypeOf(key))
return nil, fmt.Errorf("go-jose/go-jose: unknown key type '%s'", reflect.TypeOf(key))
}
if err != nil {
@@ -392,7 +398,7 @@ func (k *JSONWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
}
h := hash.New()
h.Write([]byte(input))
_, _ = h.Write([]byte(input))
return h.Sum(nil), nil
}
@@ -463,7 +469,7 @@ func (k *JSONWebKey) Valid() bool {
func (key rawJSONWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
if key.N == nil || key.E == nil {
return nil, fmt.Errorf("square/go-jose: invalid RSA key, missing n/e values")
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, missing n/e values")
}
return &rsa.PublicKey{
@@ -498,29 +504,29 @@ func (key rawJSONWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
case "P-521":
curve = elliptic.P521()
default:
return nil, fmt.Errorf("square/go-jose: unsupported elliptic curve '%s'", key.Crv)
return nil, fmt.Errorf("go-jose/go-jose: unsupported elliptic curve '%s'", key.Crv)
}
if key.X == nil || key.Y == nil {
return nil, errors.New("square/go-jose: invalid EC key, missing x/y values")
return nil, errors.New("go-jose/go-jose: invalid EC key, missing x/y values")
}
// The length of this octet string MUST be the full size of a coordinate for
// the curve specified in the "crv" parameter.
// https://tools.ietf.org/html/rfc7518#section-6.2.1.2
if curveSize(curve) != len(key.X.data) {
return nil, fmt.Errorf("square/go-jose: invalid EC public key, wrong length for x")
return nil, fmt.Errorf("go-jose/go-jose: invalid EC public key, wrong length for x")
}
if curveSize(curve) != len(key.Y.data) {
return nil, fmt.Errorf("square/go-jose: invalid EC public key, wrong length for y")
return nil, fmt.Errorf("go-jose/go-jose: invalid EC public key, wrong length for y")
}
x := key.X.bigInt()
y := key.Y.bigInt()
if !curve.IsOnCurve(x, y) {
return nil, errors.New("square/go-jose: invalid EC key, X/Y are not on declared curve")
return nil, errors.New("go-jose/go-jose: invalid EC key, X/Y are not on declared curve")
}
return &ecdsa.PublicKey{
@@ -532,7 +538,7 @@ func (key rawJSONWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
func fromEcPublicKey(pub *ecdsa.PublicKey) (*rawJSONWebKey, error) {
if pub == nil || pub.X == nil || pub.Y == nil {
return nil, fmt.Errorf("square/go-jose: invalid EC key (nil, or X/Y missing)")
return nil, fmt.Errorf("go-jose/go-jose: invalid EC key (nil, or X/Y missing)")
}
name, err := curveName(pub.Curve)
@@ -546,7 +552,7 @@ func fromEcPublicKey(pub *ecdsa.PublicKey) (*rawJSONWebKey, error) {
yBytes := pub.Y.Bytes()
if len(xBytes) > size || len(yBytes) > size {
return nil, fmt.Errorf("square/go-jose: invalid EC key (X/Y too large)")
return nil, fmt.Errorf("go-jose/go-jose: invalid EC key (X/Y too large)")
}
key := &rawJSONWebKey{
@@ -569,7 +575,7 @@ func (key rawJSONWebKey) edPrivateKey() (ed25519.PrivateKey, error) {
}
if len(missing) > 0 {
return nil, fmt.Errorf("square/go-jose: invalid Ed25519 private key, missing %s value(s)", strings.Join(missing, ", "))
return nil, fmt.Errorf("go-jose/go-jose: invalid Ed25519 private key, missing %s value(s)", strings.Join(missing, ", "))
}
privateKey := make([]byte, ed25519.PrivateKeySize)
@@ -581,7 +587,7 @@ func (key rawJSONWebKey) edPrivateKey() (ed25519.PrivateKey, error) {
func (key rawJSONWebKey) edPublicKey() (ed25519.PublicKey, error) {
if key.X == nil {
return nil, fmt.Errorf("square/go-jose: invalid Ed key, missing x value")
return nil, fmt.Errorf("go-jose/go-jose: invalid Ed key, missing x value")
}
publicKey := make([]byte, ed25519.PublicKeySize)
copy(publicKey[0:32], key.X.bytes())
@@ -605,7 +611,7 @@ func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
}
if len(missing) > 0 {
return nil, fmt.Errorf("square/go-jose: invalid RSA private key, missing %s value(s)", strings.Join(missing, ", "))
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA private key, missing %s value(s)", strings.Join(missing, ", "))
}
rv := &rsa.PrivateKey{
@@ -675,34 +681,34 @@ func (key rawJSONWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
case "P-521":
curve = elliptic.P521()
default:
return nil, fmt.Errorf("square/go-jose: unsupported elliptic curve '%s'", key.Crv)
return nil, fmt.Errorf("go-jose/go-jose: unsupported elliptic curve '%s'", key.Crv)
}
if key.X == nil || key.Y == nil || key.D == nil {
return nil, fmt.Errorf("square/go-jose: invalid EC private key, missing x/y/d values")
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, missing x/y/d values")
}
// The length of this octet string MUST be the full size of a coordinate for
// the curve specified in the "crv" parameter.
// https://tools.ietf.org/html/rfc7518#section-6.2.1.2
if curveSize(curve) != len(key.X.data) {
return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for x")
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for x")
}
if curveSize(curve) != len(key.Y.data) {
return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for y")
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for y")
}
// https://tools.ietf.org/html/rfc7518#section-6.2.2.1
if dSize(curve) != len(key.D.data) {
return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for d")
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for d")
}
x := key.X.bigInt()
y := key.Y.bigInt()
if !curve.IsOnCurve(x, y) {
return nil, errors.New("square/go-jose: invalid EC key, X/Y are not on declared curve")
return nil, errors.New("go-jose/go-jose: invalid EC key, X/Y are not on declared curve")
}
return &ecdsa.PrivateKey{
@@ -722,7 +728,7 @@ func fromEcPrivateKey(ec *ecdsa.PrivateKey) (*rawJSONWebKey, error) {
}
if ec.D == nil {
return nil, fmt.Errorf("square/go-jose: invalid EC private key")
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key")
}
raw.D = newFixedSizeBuffer(ec.D.Bytes(), dSize(ec.PublicKey.Curve))
@@ -740,7 +746,7 @@ func dSize(curve elliptic.Curve) int {
bitLen := order.BitLen()
size := bitLen / 8
if bitLen%8 != 0 {
size = size + 1
size++
}
return size
}
@@ -754,7 +760,39 @@ func fromSymmetricKey(key []byte) (*rawJSONWebKey, error) {
func (key rawJSONWebKey) symmetricKey() ([]byte, error) {
if key.K == nil {
return nil, fmt.Errorf("square/go-jose: invalid OCT (symmetric) key, missing k value")
return nil, fmt.Errorf("go-jose/go-jose: invalid OCT (symmetric) key, missing k value")
}
return key.K.bytes(), nil
}
func tryJWKS(key interface{}, headers ...Header) interface{} {
var jwks JSONWebKeySet
switch jwksType := key.(type) {
case *JSONWebKeySet:
jwks = *jwksType
case JSONWebKeySet:
jwks = jwksType
default:
return key
}
var kid string
for _, header := range headers {
if header.KeyID != "" {
kid = header.KeyID
break
}
}
if kid == "" {
return key
}
keys := jwks.Key(kid)
if len(keys) == 0 {
return key
}
return keys[0].Key
}

View File

@@ -23,7 +23,7 @@ import (
"fmt"
"strings"
"gopkg.in/square/go-jose.v2/json"
"github.com/go-jose/go-jose/v3/json"
)
// rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
@@ -75,7 +75,7 @@ type Signature struct {
original *rawSignatureInfo
}
// ParseSigned parses a signed message in compact or full serialization format.
// ParseSigned parses a signed message in compact or JWS JSON Serialization format.
func ParseSigned(signature string) (*JSONWebSignature, error) {
signature = stripWhitespace(signature)
if strings.HasPrefix(signature, "{") {
@@ -88,7 +88,7 @@ func ParseSigned(signature string) (*JSONWebSignature, error) {
// ParseDetached parses a signed message in compact serialization format with detached payload.
func ParseDetached(signature string, payload []byte) (*JSONWebSignature, error) {
if payload == nil {
return nil, errors.New("square/go-jose: nil payload")
return nil, errors.New("go-jose/go-jose: nil payload")
}
return parseSignedCompact(stripWhitespace(signature), payload)
}
@@ -151,7 +151,7 @@ func parseSignedFull(input string) (*JSONWebSignature, error) {
// sanitized produces a cleaned-up JWS object from the raw JSON.
func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
if parsed.Payload == nil {
return nil, fmt.Errorf("square/go-jose: missing payload in JWS message")
return nil, fmt.Errorf("go-jose/go-jose: missing payload in JWS message")
}
obj := &JSONWebSignature{
@@ -215,7 +215,7 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
jwk := signature.Header.JSONWebKey
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
return nil, errors.New("go-jose/go-jose: invalid embedded jwk, must be public key")
}
obj.Signatures = append(obj.Signatures, signature)
@@ -260,7 +260,7 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
jwk := obj.Signatures[i].Header.JSONWebKey
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
return nil, errors.New("go-jose/go-jose: invalid embedded jwk, must be public key")
}
// Copy value of sig
@@ -277,26 +277,26 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
func parseSignedCompact(input string, payload []byte) (*JSONWebSignature, error) {
parts := strings.Split(input, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("square/go-jose: compact JWS format must have three parts")
return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts")
}
if parts[1] != "" && payload != nil {
return nil, fmt.Errorf("square/go-jose: payload is not detached")
return nil, fmt.Errorf("go-jose/go-jose: payload is not detached")
}
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
rawProtected, err := base64URLDecode(parts[0])
if err != nil {
return nil, err
}
if payload == nil {
payload, err = base64.RawURLEncoding.DecodeString(parts[1])
payload, err = base64URLDecode(parts[1])
if err != nil {
return nil, err
}
}
signature, err := base64.RawURLEncoding.DecodeString(parts[2])
signature, err := base64URLDecode(parts[2])
if err != nil {
return nil, err
}

View File

@@ -23,7 +23,7 @@ import (
"errors"
"fmt"
"gopkg.in/square/go-jose.v2/json"
"github.com/go-jose/go-jose/v3/json"
)
// KeyAlgorithm represents a key management algorithm.
@@ -45,32 +45,32 @@ var (
// ErrCryptoFailure represents an error in cryptographic primitive. This
// occurs when, for example, a message had an invalid authentication tag or
// could not be decrypted.
ErrCryptoFailure = errors.New("square/go-jose: error in cryptographic primitive")
ErrCryptoFailure = errors.New("go-jose/go-jose: error in cryptographic primitive")
// ErrUnsupportedAlgorithm indicates that a selected algorithm is not
// supported. This occurs when trying to instantiate an encrypter for an
// algorithm that is not yet implemented.
ErrUnsupportedAlgorithm = errors.New("square/go-jose: unknown/unsupported algorithm")
ErrUnsupportedAlgorithm = errors.New("go-jose/go-jose: unknown/unsupported algorithm")
// ErrUnsupportedKeyType indicates that the given key type/format is not
// supported. This occurs when trying to instantiate an encrypter and passing
// it a key of an unrecognized type or with unsupported parameters, such as
// an RSA private key with more than two primes.
ErrUnsupportedKeyType = errors.New("square/go-jose: unsupported key type/format")
ErrUnsupportedKeyType = errors.New("go-jose/go-jose: unsupported key type/format")
// ErrInvalidKeySize indicates that the given key is not the correct size
// for the selected algorithm. This can occur, for example, when trying to
// encrypt with AES-256 but passing only a 128-bit key as input.
ErrInvalidKeySize = errors.New("square/go-jose: invalid key size for algorithm")
ErrInvalidKeySize = errors.New("go-jose/go-jose: invalid key size for algorithm")
// ErrNotSupported serialization of object is not supported. This occurs when
// trying to compact-serialize an object which can't be represented in
// compact form.
ErrNotSupported = errors.New("square/go-jose: compact serialization not supported for object")
ErrNotSupported = errors.New("go-jose/go-jose: compact serialization not supported for object")
// ErrUnprotectedNonce indicates that while parsing a JWS or JWE object, a
// nonce header parameter was included in an unprotected header object.
ErrUnprotectedNonce = errors.New("square/go-jose: Nonce parameter included in unprotected header")
ErrUnprotectedNonce = errors.New("go-jose/go-jose: Nonce parameter included in unprotected header")
)
// Key management algorithms
@@ -133,8 +133,8 @@ const (
type HeaderKey string
const (
HeaderType HeaderKey = "typ" // string
HeaderContentType = "cty" // string
HeaderType = "typ" // string
HeaderContentType = "cty" // string
// These are set by go-jose and shouldn't need to be set by consumers of the
// library.
@@ -194,7 +194,7 @@ type Header struct {
// not be validated with the given verify options.
func (h Header) Certificates(opts x509.VerifyOptions) ([][]*x509.Certificate, error) {
if len(h.certificates) == 0 {
return nil, errors.New("square/go-jose: no x5c header present in message")
return nil, errors.New("go-jose/go-jose: no x5c header present in message")
}
leaf := h.certificates[0]
@@ -452,8 +452,8 @@ func parseCertificateChain(chain []string) ([]*x509.Certificate, error) {
return out, nil
}
func (dst rawHeader) isSet(k HeaderKey) bool {
dvr := dst[k]
func (parsed rawHeader) isSet(k HeaderKey) bool {
dvr := parsed[k]
if dvr == nil {
return false
}
@@ -472,17 +472,17 @@ func (dst rawHeader) isSet(k HeaderKey) bool {
}
// Merge headers from src into dst, giving precedence to headers from l.
func (dst rawHeader) merge(src *rawHeader) {
func (parsed rawHeader) merge(src *rawHeader) {
if src == nil {
return
}
for k, v := range *src {
if dst.isSet(k) {
if parsed.isSet(k) {
continue
}
dst[k] = v
parsed[k] = v
}
}
@@ -496,7 +496,7 @@ func curveName(crv elliptic.Curve) (string, error) {
case elliptic.P521():
return "P-521", nil
default:
return "", fmt.Errorf("square/go-jose: unsupported/unknown elliptic curve")
return "", fmt.Errorf("go-jose/go-jose: unsupported/unknown elliptic curve")
}
}

View File

@@ -19,14 +19,13 @@ package jose
import (
"bytes"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"encoding/base64"
"errors"
"fmt"
"golang.org/x/crypto/ed25519"
"gopkg.in/square/go-jose.v2/json"
"github.com/go-jose/go-jose/v3/json"
)
// NonceSource represents a source of random nonces to go into JWS objects
@@ -227,7 +226,7 @@ func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigIn
// This should be impossible, but let's check anyway.
if !recipient.publicKey().IsPublic() {
return recipientSigInfo{}, errors.New("square/go-jose: public key was unexpectedly not public")
return recipientSigInfo{}, errors.New("go-jose/go-jose: public key was unexpectedly not public")
}
}
return recipient, nil
@@ -251,7 +250,7 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
// result of the JOSE spec. We've decided that this library will only include one or
// the other to avoid this confusion.
//
// See https://github.com/square/go-jose/issues/157 for more context.
// See https://github.com/go-jose/go-jose/issues/157 for more context.
if ctx.embedJWK {
protected[headerJWK] = recipient.publicKey()
} else {
@@ -265,7 +264,7 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
if ctx.nonceSource != nil {
nonce, err := ctx.nonceSource.Nonce()
if err != nil {
return nil, fmt.Errorf("square/go-jose: Error generating nonce: %v", err)
return nil, fmt.Errorf("go-jose/go-jose: Error generating nonce: %v", err)
}
protected[headerNonce] = nonce
}
@@ -279,7 +278,7 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
if b64, ok := protected[headerB64]; ok {
if needsBase64, ok = b64.(bool); !ok {
return nil, errors.New("square/go-jose: Invalid b64 header parameter")
return nil, errors.New("go-jose/go-jose: Invalid b64 header parameter")
}
}
@@ -303,7 +302,7 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
for k, v := range protected {
b, err := json.Marshal(v)
if err != nil {
return nil, fmt.Errorf("square/go-jose: Error marshalling item %#v: %v", k, err)
return nil, fmt.Errorf("go-jose/go-jose: Error marshalling item %#v: %v", k, err)
}
(*signatureInfo.protected)[k] = makeRawMessage(b)
}
@@ -348,13 +347,14 @@ func (obj JSONWebSignature) UnsafePayloadWithoutVerification() []byte {
// is only useful if you have a payload and signature that are separated from
// each other.
func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey interface{}) error {
verifier, err := newVerifier(verificationKey)
key := tryJWKS(verificationKey, obj.headers()...)
verifier, err := newVerifier(key)
if err != nil {
return err
}
if len(obj.Signatures) > 1 {
return errors.New("square/go-jose: too many signatures in payload; expecting only one")
return errors.New("go-jose/go-jose: too many signatures in payload; expecting only one")
}
signature := obj.Signatures[0]
@@ -406,7 +406,8 @@ func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signa
// separated from each other, and the signature can have multiple signers at the
// same time.
func (obj JSONWebSignature) DetachedVerifyMulti(payload []byte, verificationKey interface{}) (int, Signature, error) {
verifier, err := newVerifier(verificationKey)
key := tryJWKS(verificationKey, obj.headers()...)
verifier, err := newVerifier(key)
if err != nil {
return -1, Signature{}, err
}
@@ -439,3 +440,11 @@ outer:
return -1, Signature{}, ErrCryptoFailure
}
func (obj JSONWebSignature) headers() []Header {
headers := make([]Header, len(obj.Signatures))
for i, sig := range obj.Signatures {
headers[i] = sig.Header
}
return headers
}

View File

@@ -31,10 +31,11 @@ import (
"io"
"golang.org/x/crypto/pbkdf2"
"gopkg.in/square/go-jose.v2/cipher"
josecipher "github.com/go-jose/go-jose/v3/cipher"
)
// Random reader (stubbed out in tests)
// RandReader is a cryptographically secure random number generator (stubbed out in tests).
var RandReader = rand.Reader
const (
@@ -278,8 +279,14 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
}
header := &rawHeader{}
header.set(headerIV, newBuffer(parts.iv))
header.set(headerTag, newBuffer(parts.tag))
if err = header.set(headerIV, newBuffer(parts.iv)); err != nil {
return recipientInfo{}, err
}
if err = header.set(headerTag, newBuffer(parts.tag)); err != nil {
return recipientInfo{}, err
}
return recipientInfo{
header: header,
@@ -332,8 +339,14 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
}
header := &rawHeader{}
header.set(headerP2C, ctx.p2c)
header.set(headerP2S, newBuffer(ctx.p2s))
if err = header.set(headerP2C, ctx.p2c); err != nil {
return recipientInfo{}, err
}
if err = header.set(headerP2S, newBuffer(ctx.p2s)); err != nil {
return recipientInfo{}, err
}
return recipientInfo{
encryptedKey: jek,
@@ -356,11 +369,11 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
iv, err := headers.getIV()
if err != nil {
return nil, fmt.Errorf("square/go-jose: invalid IV: %v", err)
return nil, fmt.Errorf("go-jose/go-jose: invalid IV: %v", err)
}
tag, err := headers.getTag()
if err != nil {
return nil, fmt.Errorf("square/go-jose: invalid tag: %v", err)
return nil, fmt.Errorf("go-jose/go-jose: invalid tag: %v", err)
}
parts := &aeadParts{
@@ -389,18 +402,18 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW:
p2s, err := headers.getP2S()
if err != nil {
return nil, fmt.Errorf("square/go-jose: invalid P2S: %v", err)
return nil, fmt.Errorf("go-jose/go-jose: invalid P2S: %v", err)
}
if p2s == nil || len(p2s.data) == 0 {
return nil, fmt.Errorf("square/go-jose: invalid P2S: must be present")
return nil, fmt.Errorf("go-jose/go-jose: invalid P2S: must be present")
}
p2c, err := headers.getP2C()
if err != nil {
return nil, fmt.Errorf("square/go-jose: invalid P2C: %v", err)
return nil, fmt.Errorf("go-jose/go-jose: invalid P2C: %v", err)
}
if p2c <= 0 {
return nil, fmt.Errorf("square/go-jose: invalid P2C: must be a positive integer")
return nil, fmt.Errorf("go-jose/go-jose: invalid P2C: must be a positive integer")
}
// salt is UTF8(Alg) || 0x00 || Salt Input
@@ -431,7 +444,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
func (ctx symmetricMac) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
mac, err := ctx.hmac(payload, alg)
if err != nil {
return Signature{}, errors.New("square/go-jose: failed to compute hmac")
return Signature{}, errors.New("go-jose/go-jose: failed to compute hmac")
}
return Signature{
@@ -444,16 +457,16 @@ func (ctx symmetricMac) signPayload(payload []byte, alg SignatureAlgorithm) (Sig
func (ctx symmetricMac) verifyPayload(payload []byte, mac []byte, alg SignatureAlgorithm) error {
expected, err := ctx.hmac(payload, alg)
if err != nil {
return errors.New("square/go-jose: failed to compute hmac")
return errors.New("go-jose/go-jose: failed to compute hmac")
}
if len(mac) != len(expected) {
return errors.New("square/go-jose: invalid hmac")
return errors.New("go-jose/go-jose: invalid hmac")
}
match := subtle.ConstantTimeCompare(mac, expected)
if match != 1 {
return errors.New("square/go-jose: invalid hmac")
return errors.New("go-jose/go-jose: invalid hmac")
}
return nil

433
vendor/github.com/google/go-github/v48/AUTHORS generated vendored Normal file
View File

@@ -0,0 +1,433 @@
# This is the official list of go-github authors for copyright purposes.
#
# This does not necessarily list everyone who has contributed code, since in
# some cases, their employer may be the copyright holder. To see the full list
# of contributors, see the revision history in source control or
# https://github.com/google/go-github/graphs/contributors.
#
# Authors who wish to be recognized in this file should add themselves (or
# their employer, as appropriate).
178inaba <masahiro.furudate@gmail.com>
2BFL <imqksl@gmail.com>
413x <dedifferentiator@gmail.com>
Abed Kibbe <abed.kibbe@gmail.com>
Abhinav Gupta <mail@abhinavg.net>
Abhishek Veeramalla <abhishek.veeramalla@gmail.com>
aboy <b1011211@gmail.com>
adrienzieba <adrien.zieba@appdirect.com>
afdesk <work@afdesk.com>
Ahmed Hagy <a.akram93@gmail.com>
Aidan Steele <aidan.steele@glassechidna.com.au>
Ainsley Chong <ainsley.chong@gmail.com>
ajz01 <azdenek@yahoo.com>
Akeda Bagus <akeda@x-team.com>
Akhil Mohan <akhilerm@gmail.com>
Alec Thomas <alec@swapoff.org>
Aleks Clark <aleks.clark@gmail.com>
Alex Bramley <a.bramley@gmail.com>
Alex Orr <Alexorr.CSE@gmail.com>
Alex Su <alexsu@17.media>
Alex Unger <zyxancf@gmail.com>
Alexander Harkness <me@bearbin.net>
Alexis Gauthiez <alexis.gauthiez@gmail.com>
Ali Farooq <ali.farooq0@pm.me>
Allan Guwatudde <guwats10@gmail.com>
Allen Sun <shlallen1990@gmail.com>
Amey Sakhadeo <me@ameyms.com>
Anders Janmyr <anders@janmyr.com>
Andreas Garnæs <https://github.com/andreas>
Andrew Ryabchun <aryabchun@mail.ua>
Andrew Svoboda <svoboda.andrew@gmail.com>
Andy Grunwald <andygrunwald@gmail.com>
Andy Hume <andyhume@gmail.com>
Andy Lindeman <andy@lindeman.io>
angie pinilla <angelinepinilla@gmail.com>
anjanashenoy <anjanashenoy1@gmail.com>
Anshuman Bhartiya <anshuman.bhartiya@gmail.com>
Antoine <antoine.tu@mail.mcgill.ca>
Antoine Pelisse <apelisse@gmail.com>
Anton Nguyen <afnguyen85@gmail.com>
Anubha Kushwaha <anubha_bt2k14@dtu.ac.in>
appilon <apilon@hashicorp.com>
aprp <doelaudi@gmail.com>
Aravind <aravindkp@outlook.in>
Arda Kuyumcu <kuyumcuarda@gmail.com>
Arıl Bozoluk <arilbozoluk@hotmail.com>
Asier Marruedo <asiermarruedo@gmail.com>
Austin Burdine <acburdine@gmail.com>
Austin Dizzy <dizzy@wow.com>
Azuka Okuleye <azuka@zatechcorp.com>
Ben Batha <bhbatha@gmail.com>
Benjamen Keroack <benjamen@dollarshaveclub.com>
Beshr Kayali <beshrkayali@gmail.com>
Beyang Liu <beyang.liu@gmail.com>
Billy Keyes <bluekeyes@gmail.com>
Billy Lynch <wlynch92@gmail.com>
Bjorn Neergaard <bjorn@neersighted.com>
Björn Häuser <b.haeuser@rebuy.de>
boljen <bol.christophe@gmail.com>
Bracken <abdawson@gmail.com>
Brad Harris <bmharris@gmail.com>
Brad Moylan <moylan.brad@gmail.com>
Bradley Falzon <brad@teambrad.net>
Bradley McAllister <brad.mcallister@hotmail.com>
Brandon Butler <b.butler@chia.net>
Brandon Cook <phylake@gmail.com>
Brett Kuhlman <kuhlman-labs@github.com>
Brett Logan <lindluni@github.com>
Brian Egizi <brian@mojotech.com>
Bryan Boreham <bryan@weave.works>
Bryan Peterson <Lazyshot@gmail.com>
Cami Diez <diezcami@gmail.com>
Carl Johnson <me@carlmjohnson.net>
Carlos Alexandro Becker <caarlos0@gmail.com>
Carlos Tadeu Panato Junior <ctadeu@gmail.com>
ChandanChainani <chainanichan@gmail.com>
chandresh-pancholi <chandreshpancholi007@gmail.com>
Charles Fenwick Elliott <Charles@FenwickElliott.io>
Charlie Yan <charlieyan08@gmail.com>
Chmouel Boudjnah <chmouel@chmouel.com>
Chris King <chriskingnet@gmail.com>
Chris Mc <prince.chrismc@gmail.com>
Chris Raborg <craborg57@gmail.com>
Chris Roche <chris@vsco.co>
Chris Schaefer <chris@dtzq.com>
chrisforrette <chris@chrisforrette.com>
Christian Bargmann <chris@cbrgm.net>
Christian Muehlhaeuser <muesli@gmail.com>
Christoph Sassenberg <defsprite@gmail.com>
CI Monk <ci-monk@protonmail.com>
Colin Misare <github.com/cmisare>
Craig Gumbley <craiggumbley@gmail.com>
Craig Peterson <cpeterson@stackoverflow.com>
Cristian Maglie <c.maglie@bug.st>
Cyb3r Jak3 <jake@jwhite.network>
Daehyeok Mun <daehyeok@gmail.com>
Dalton Hubble <dghubble@gmail.com>
Daniel Lanner <lanner.dan@gmail.com>
Daniel Leavitt <daniel.leavitt@gmail.com>
Daniel Nilsson <daniel.nilsson1989@gmail.com>
Daoq <masseto2002@gmail.com>
Dave Du Cros <davidducros@gmail.com>
Dave Henderson <dhenderson@gmail.com>
Dave Perrett <hello@daveperrett.com>
Dave Protasowski <dprotaso@gmail.com>
David Deng <daviddengcn@gmail.com>
David Gamba <davidgamba@gmail.com>
David J. M. Karlsen <david@davidkarlsen.com>
David Jannotta <djannotta@gmail.com>
David Ji <github.com/davidji99>
David Lopez Reyes <davidlopezre@gmail.com>
Davide Zipeto <dawez1@gmail.com>
Dennis Webb <dennis@bluesentryit.com>
Derek Jobst <derekjobst@gmail.com>
DeviousLab <deviouslab@gmail.com>
Dhi Aurrahman <diorahman@rockybars.com>
Diego Lapiduz <diego.lapiduz@cfpb.gov>
Dmitri Shuralyov <shurcooL@gmail.com>
dmnlk <seikima2demon@gmail.com>
Don Petersen <don@donpetersen.net>
Doug Turner <doug.turner@gmail.com>
Drew Fradette <drew.fradette@gmail.com>
Dustin Deus <deusdustin@gmail.com>
Eivind <eivindkn@gmail.com>
Eli Uriegas <seemethere101@gmail.com>
Elliott Beach <elliott2.71828@gmail.com>
Emerson Wood <emersonwood94@gmail.com>
Emil V <emil.vaagland@schibsted.com>
Eng Zer Jun <engzerjun@gmail.com>
eperm <staffordworrell@gmail.com>
Erick Fejta <erick@fejta.com>
Erik Nobel <hendrik.nobel@transferwise.com>
erwinvaneyk <erwinvaneyk@gmail.com>
Evan Elias <evanjelias@gmail.com>
Fabian Holler <fabian.holler@simplesurance.de>
Fabrice <fabrice.vaillant@student.ecp.fr>
Fatema-Moaiyadi <fatema.i.moaiyadi@gmail.com>
Felix Geisendörfer <felix@debuggable.com>
Filippo Valsorda <hi@filippo.io>
Florian Forster <ff@octo.it>
Florian Wagner <h2floh@github.com>
Francesc Gil <xescugil@gmail.com>
Francis <hello@francismakes.com>
Francisco Guimarães <francisco.cpg@gmail.com>
François de Metz <francois@2metz.fr>
Fredrik Jönsson <fredrik.jonsson@izettle.com>
Gabriel <samfiragabriel@gmail.com>
Garrett Squire <garrettsquire@gmail.com>
George Kontridze <george.kontridze@gmail.com>
Georgy Buranov <gburanov@gmail.com>
Glen Mailer <glenjamin@gmail.com>
Gnahz <p@oath.pl>
Google Inc.
Grachev Mikhail <work@mgrachev.com>
griffin_stewie <panterathefamilyguy@gmail.com>
Guillaume Jacquet <guillaume.jacquet@gmail.com>
Guz Alexander <kalimatas@gmail.com>
Guðmundur Bjarni Ólafsson <gudmundur@github.com>
Hanno Hecker <hanno.hecker@zalando.de>
Hari haran <hariharan.uno@gmail.com>
Harikesh00 <prajapatiharikesh16@gmail.com>
haya14busa <haya14busa@gmail.com>
haya14busa <hayabusa1419@gmail.com>
Hiroki Ito <hiroki.ito.dev@gmail.com>
Hubot Jr <rreichel3@github.com>
Huy Tr <kingbazoka@gmail.com>
huydx <doxuanhuy@gmail.com>
i2bskn <i2bskn@gmail.com>
Iain Steers <iainsteers@gmail.com>
Ikko Ashimine <eltociear@gmail.com>
Ioannis Georgoulas <igeorgoulas21@gmail.com>
Isao Jonas <isao.jonas@gmail.com>
ishan upadhyay <ishanupadhyay412@gmail.com>
isqua <isqua@isqua.ru>
Jacob Valdemar <jan@lunar.app>
Jake Krammer <jake.krammer1@gmail.com>
Jake White <jake@jwhite.network>
Jameel Haffejee <RC1140@republiccommandos.co.za>
James Bowes <jbowes@repl.ca>
James Cockbain <james.cockbain@ibm.com>
James Loh <github@jloh.co>
Jamie West <jamieianwest@hotmail.com>
Jan Kosecki <jan.kosecki91@gmail.com>
Jan Švábík <jansvabik@jansvabik.cz>
Javier Campanini <jcampanini@palantir.com>
Jef LeCompte <jeffreylec@gmail.com>
Jens Rantil <jens.rantil@gmail.com>
Jeremy Morris <jeremylevanmorris@gmail.com>
Jesse Haka <haka.jesse@gmail.com>
Jesse Newland <jesse@jnewland.com>
Jihoon Chung <j.c@navercorp.com>
Jille Timmermans <jille@quis.cx>
Jimmi Dyson <jimmidyson@gmail.com>
Joan Saum <joan.saum@epitech.eu>
Joe Tsai <joetsai@digital-static.net>
John Barton <jrbarton@gmail.com>
John Engelman <john.r.engelman@gmail.com>
John Jones <john@exthilion.org>
John Liu <john.liu@mongodb.com>
Jordan Brockopp <jdbro94@gmail.com>
Jordan Sussman <jordansail22@gmail.com>
Joshua Bezaleel Abednego <joshua.bezaleel@gmail.com>
João Cerqueira <joao@cerqueira.io>
JP Phillips <jonphill9@gmail.com>
jpbelanger-mtl <jp.belanger@gmail.com>
Juan <juan.rios.28@gmail.com>
Juan Basso <jrbasso@gmail.com>
Julien Garcia Gonzalez <garciagonzalez.julien@gmail.com>
Julien Rostand <jrostand@users.noreply.github.com>
Junya Kono <junya03dance@gmail.com>
Justin Abrahms <justin@abrah.ms>
Justin Toh <tohjustin@hotmail.com>
Jusung Lee <e.jusunglee@gmail.com>
jzhoucliqr <jzhou@cliqr.com>
k1rnt <eru08tera15mora@gmail.com>
kadern0 <kaderno@gmail.com>
Katrina Owen <kytrinyx@github.com>
Kautilya Tripathi <tripathi.kautilya@gmail.com>
Keita Urashima <ursm@ursm.jp>
Kevin Burke <kev@inburke.com>
Kevin Wang <kwangsan@gmail.com>
Kevin Zhao <kzhao@lyft.com>
Kirill <g4s8.public@gmail.com>
Konrad Malawski <konrad.malawski@project13.pl>
Kookheon Kwon <kucuny@gmail.com>
Krishna Indani <indanikrishna@gmail.com>
Krzysztof Kowalczyk <kkowalczyk@gmail.com>
Kshitij Saraogi <KshitijSaraogi@gmail.com>
Kumar Saurabh <itsksaurabh@gmail.com>
Kyle Kurz <kyle@doublekaudio.com>
kyokomi <kyoko1220adword@gmail.com>
Laurent Verdoïa <verdoialaurent@gmail.com>
leopoldwang <leopold.wang@gmail.com>
Liam Galvin <liam@liam-galvin.co.uk>
Lluis Campos <lluis.campos@northern.tech>
Lovro Mažgon <lovro.mazgon@gmail.com>
Luca Campese <me@campesel.net>
Lucas Alcantara <lucasalcantaraf@gmail.com>
Luis Davim <luis.davim@sendoso.com>
Luke Evers <me@lukevers.com>
Luke Kysow <lkysow@gmail.com>
Luke Roberts <email@luke-roberts.co.uk>
Luke Young <luke@hydrantlabs.org>
lynn [they] <lynncyrin@gmail.com>
Maksim Zhylinski <uzzable@gmail.com>
Marc Binder <marcandrebinder@gmail.com>
Marcelo Carlos <marcelo@permutive.com>
Mark Tareshawty <tarebyte@github.com>
Martin Holman <me@martinholman.co.nz>
Martin-Louis Bright <mlbright@gmail.com>
Martins Sipenko <martins.sipenko@gmail.com>
Marwan Sulaiman <marwan.sameer@gmail.com>
Masayuki Izumi <m@izum.in>
Mat Geist <matgeist@gmail.com>
Matija Horvat <horvat2112@gmail.com>
Matin Rahmanian <itsmatinx@gmail.com>
Matt <alpmatthew@gmail.com>
Matt Brender <mjbrender@gmail.com>
Matt Gaunt <matt@gauntface.co.uk>
Matt Landis <landis.matt@gmail.com>
Matt Moore <mattmoor@vmware.com>
Maxime Bury <maxime.bury@gmail.com>
Michael Meng <mmeng@lyft.com>
Michael Spiegel <michael.m.spiegel@gmail.com>
Michael Tiller <michael.tiller@gmail.com>
Michał Glapa <michal.glapa@gmail.com>
Michelangelo Morrillo <michelangelo@morrillo.it>
Miguel Elias dos Santos <migueleliasweb@gmail.com>
Mike Chen <mchen300@gmail.com>
Mohammed AlDujaili <avainer11@gmail.com>
Mukundan Senthil <mukundan314@gmail.com>
Munia Balayil <munia.247@gmail.com>
Mustafa Abban <mustafaabban@utexas.edu>
Nadav Kaner <nadavkaner1@gmail.com>
Nathan VanBenschoten <nvanbenschoten@gmail.com>
Navaneeth Suresh <navaneeths1998@gmail.com>
Neil O'Toole <neilotoole@apache.org>
Nick Miyake <nmiyake@palantir.com>
Nick Platt <hello@nickplatt.co.uk>
Nick Spragg <nick.spragg@bbc.co.uk>
Nikhita Raghunath <nikitaraghunath@gmail.com>
Nilesh Singh <nilesh.singh24@outlook.com>
Noah Hanjun Lee <noah.lee@buzzvil.com>
Noah Zoschke <noah+sso2@convox.com>
ns-cweber <cweber@narrativescience.com>
Ole Orhagen <ole.orhagen@northern.tech>
Oleg Kovalov <iamolegkovalov@gmail.com>
Ondřej Kupka <ondra.cap@gmail.com>
Ori Talmor <talmorori@gmail.com>
Pablo Pérez Schröder <pablo.perezschroder@wetransfer.com>
Palash Nigam <npalash25@gmail.com>
Panagiotis Moustafellos <pmoust@gmail.com>
Parham Alvani <parham.alvani@gmail.com>
pari-27 <sonamajumdar2012@gmail.com>
Parker Moore <parkrmoore@gmail.com>
parkhyukjun89 <park.hyukjun89@gmail.com>
Pat Alwell <pat.alwell@gmail.com>
Patrick DeVivo <patrick.devivo@gmail.com>
Patrick Marabeas <patrick@marabeas.io>
Pavel Dvoinos <pavel.dvoinos@transferwise.com>
Pavel Shtanko <pavel.shtanko@gmail.com>
Pete Wagner <thepwagner@github.com>
Petr Shevtsov <petr.shevtsov@gmail.com>
Pierce McEntagart <pierce@nightfall.ai>
Pierre Carrier <pierre@meteor.com>
Piotr Zurek <p.zurek@gmail.com>
Piyush Chugh <piyushchugh1993@gmail.com>
Pratik Mallya <pratik.mallya@gmail.com>
Qais Patankar <qaisjp@gmail.com>
Quang Le Hong <iamquang95@gmail.com>
Quentin Leffray <fiahil@gmail.com>
Quinn Slack <qslack@qslack.com>
Rackspace US, Inc.
Radek Simko <radek.simko@gmail.com>
Radliński Ignacy <radlinsk@student.agh.edu.pl>
Rajat Jindal <rajatjindal83@gmail.com>
Rajendra arora <rajendraarora16@yahoo.com>
Rajkumar <princegosavi12@gmail.com>
Ranbir Singh <binkkatal.r@gmail.com>
Ravi Shekhar Jethani <rsjethani@gmail.com>
RaviTeja Pothana <ravi-teja@live.com>
rc1140 <jameel@republiccommandos.co.za>
Red Hat, Inc.
Reetuparna Mukherjee <reetuparna.1988@gmail.com>
reeves122 <reeves122@gmail.com>
Reinier Timmer <reinier.timmer@ah.nl>
Renjith R <renjithr201097@gmail.com>
Ricco Førgaard <ricco@fiskeben.dk>
Richard de Vries <richard.de.vries@topicus.nl>
Rob Figueiredo <robfig@yext.com>
Rohit Upadhyay <urohit011@gmail.com>
Rojan Dinc <rojand94@gmail.com>
Ronak Jain <ronakjain@outlook.in>
Ronan Pelliard <ronan.pelliard@datadoghq.com>
Ross Gustafson <srgustafson8@icloud.com>
Ruben Vereecken <rubenvereecken@gmail.com>
Russell Boley <raboley@gmail.com>
Ryan Leung <rleungx@gmail.com>
Ryan Lower <rpjlower@gmail.com>
Ryo Nakao <nakabonne@gmail.com>
Saaarah <sarah.liusy@gmail.com>
Safwan Olaimat <safwan.olaimat@gmail.com>
Sahil Dua <sahildua2305@gmail.com>
saisi <saisi@users.noreply.github.com>
Sam Minnée <sam@silverstripe.com>
Sandeep Sukhani <sandeep.d.sukhani@gmail.com>
Sander Knape <s.knape88@gmail.com>
Sander van Harmelen <svanharmelen@schubergphilis.com>
Sanket Payghan <sanket.payghan8@gmail.com>
Sarah Funkhouser <sarah.k.funkhouser@gmail.com>
Sarasa Kisaragi <lingsamuelgrace@gmail.com>
Sasha Melentyev <sasha@melentyev.io>
Sean Wang <sean@decrypted.org>
Sebastian Mandrean <sebastian.mandrean@gmail.com>
Sebastian Mæland Pedersen <sem.pedersen@stud.uis.no>
Sergei Popinevskii <gurza000@gmail.com>
Sergey Romanov <xxsmotur@gmail.com>
Sergio Garcia <sergio.garcia@gmail.com>
Seth Vargo <seth@sethvargo.com>
Sevki <s@sevki.org>
Shagun Khemka <shagun.khemka60@gmail.com>
shakeelrao <shakeelrao79@gmail.com>
Shawn Catanzarite <me@shawncatz.com>
Shawn Smith <shawnpsmith@gmail.com>
Shibasis Patel <patelshibasis@gmail.com>
Sho Okada <shokada3@gmail.com>
Shrikrishna Singh <krishnasingh.ss30@gmail.com>
Simon Davis <sdavis@hashicorp.com>
sona-tar <sona.zip@gmail.com>
SoundCloud, Ltd.
Sridhar Mocherla <srmocher@microsoft.com>
SriVignessh Pss <sriknowledge@gmail.com>
Stefan Sedich <stefan.sedich@gmail.com>
Steve Teuber <github@steveteuber.com>
Stian Eikeland <stian@eikeland.se>
Suhaib Mujahid <suhaibmujahid@gmail.com>
sushmita wable <sw09007@gmail.com>
Szymon Kodrebski <simonkey007@gmail.com>
Søren Hansen <soren@dinero.dk>
Takashi Yoneuchi <takashi.yoneuchi@shift-js.info>
Takayuki Watanabe <takanabe.w@gmail.com>
Taketoshi Fujiwara <fujiwara@leapmind.io>
Taketoshi Fujiwara <taketoshi.fujiwara@gmail.com>
Takuma Kajikawa <kj1ktk@gmail.com>
Tasya Aditya Rukmana <tadityar@gmail.com>
Theo Henson <theodorehenson@protonmail.com>
Theofilos Petsios <theofilos.pe@gmail.com>
Thomas Aidan Curran <thomas@ory.sh>
Thomas Bruyelle <thomas.bruyelle@gmail.com>
Tim Rogers <timrogers@github.com>
Timothée Peignier <timothee.peignier@tryphon.org>
Tingluo Huang <tingluohuang@github.com>
tkhandel <tarunkhandelwal.iitr@gmail.com>
Tobias Gesellchen <tobias@gesellix.de>
Tom Payne <twpayne@gmail.com>
Trey Tacon <ttacon@gmail.com>
tsbkw <tsbkw0@gmail.com>
ttacon <ttacon@gmail.com>
Vaibhav Singh <vaibhav.singh.14cse@bml.edu.in>
Varadarajan Aravamudhan <varadaraajan@gmail.com>
Victor Castell <victor@victorcastell.com>
Victor Vrantchan <vrancean+github@gmail.com>
vikkyomkar <vikky.omkar@samsung.com>
Vlad Ungureanu <vladu@palantir.com>
Wasim Thabraze <wasim@thabraze.me>
Weslei Juan Moser Pereira <wesleimsr@gmail.com>
Will Maier <wcmaier@gmail.com>
Willem D'Haeseleer <dhwillem@gmail.com>
William Bailey <mail@williambailey.org.uk>
William Cooke <pipeston@gmail.com>
Xabi <xmartinez1702@gmail.com>
xibz <impactbchang@gmail.com>
Yann Malet <yann.malet@gmail.com>
Yannick Utard <yannickutard@gmail.com>
Yicheng Qin <qycqycqycqycqyc@gmail.com>
Yosuke Akatsuka <yosuke.akatsuka@access-company.com>
Yumikiyo Osanai <yumios.art@gmail.com>
Yusef Mohamadi <yuseferi@gmail.com>
Yusuke Kuoka <ykuoka@gmail.com>
Zach Latta <zach@zachlatta.com>
zhouhaibing089 <zhouhaibing089@gmail.com>
六开箱 <lkxed@outlook.com>
缘生 <i@ysicing.me>

27
vendor/github.com/google/go-github/v48/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2013 The go-github AUTHORS. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,12 @@
// Copyright 2020 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
// ActionsService handles communication with the actions related
// methods of the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/
type ActionsService service

View File

@@ -0,0 +1,143 @@
// Copyright 2020 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
"net/http"
"net/url"
)
// Artifact reprents a GitHub artifact. Artifacts allow sharing
// data between jobs in a workflow and provide storage for data
// once a workflow is complete.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/artifacts
type Artifact struct {
ID *int64 `json:"id,omitempty"`
NodeID *string `json:"node_id,omitempty"`
Name *string `json:"name,omitempty"`
SizeInBytes *int64 `json:"size_in_bytes,omitempty"`
ArchiveDownloadURL *string `json:"archive_download_url,omitempty"`
Expired *bool `json:"expired,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
ExpiresAt *Timestamp `json:"expires_at,omitempty"`
}
// ArtifactList represents a list of GitHub artifacts.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/artifacts#artifacts
type ArtifactList struct {
TotalCount *int64 `json:"total_count,omitempty"`
Artifacts []*Artifact `json:"artifacts,omitempty"`
}
// ListArtifacts lists all artifacts that belong to a repository.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/artifacts#list-artifacts-for-a-repository
func (s *ActionsService) ListArtifacts(ctx context.Context, owner, repo string, opts *ListOptions) (*ArtifactList, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/artifacts", owner, repo)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
artifactList := new(ArtifactList)
resp, err := s.client.Do(ctx, req, artifactList)
if err != nil {
return nil, resp, err
}
return artifactList, resp, nil
}
// ListWorkflowRunArtifacts lists all artifacts that belong to a workflow run.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/artifacts#list-workflow-run-artifacts
func (s *ActionsService) ListWorkflowRunArtifacts(ctx context.Context, owner, repo string, runID int64, opts *ListOptions) (*ArtifactList, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/artifacts", owner, repo, runID)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
artifactList := new(ArtifactList)
resp, err := s.client.Do(ctx, req, artifactList)
if err != nil {
return nil, resp, err
}
return artifactList, resp, nil
}
// GetArtifact gets a specific artifact for a workflow run.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/artifacts#get-an-artifact
func (s *ActionsService) GetArtifact(ctx context.Context, owner, repo string, artifactID int64) (*Artifact, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/artifacts/%v", owner, repo, artifactID)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
artifact := new(Artifact)
resp, err := s.client.Do(ctx, req, artifact)
if err != nil {
return nil, resp, err
}
return artifact, resp, nil
}
// DownloadArtifact gets a redirect URL to download an archive for a repository.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/artifacts#download-an-artifact
func (s *ActionsService) DownloadArtifact(ctx context.Context, owner, repo string, artifactID int64, followRedirects bool) (*url.URL, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/artifacts/%v/zip", owner, repo, artifactID)
resp, err := s.client.roundTripWithOptionalFollowRedirect(ctx, u, followRedirects)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusFound {
return nil, newResponse(resp), fmt.Errorf("unexpected status code: %s", resp.Status)
}
parsedURL, err := url.Parse(resp.Header.Get("Location"))
if err != nil {
return nil, newResponse(resp), err
}
return parsedURL, newResponse(resp), nil
}
// DeleteArtifact deletes a workflow run artifact.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/artifacts#delete-an-artifact
func (s *ActionsService) DeleteArtifact(ctx context.Context, owner, repo string, artifactID int64) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/artifacts/%v", owner, repo, artifactID)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}

View File

@@ -0,0 +1,311 @@
// Copyright 2021 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// RunnerGroup represents a self-hosted runner group configured in an organization.
type RunnerGroup struct {
ID *int64 `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Visibility *string `json:"visibility,omitempty"`
Default *bool `json:"default,omitempty"`
SelectedRepositoriesURL *string `json:"selected_repositories_url,omitempty"`
RunnersURL *string `json:"runners_url,omitempty"`
Inherited *bool `json:"inherited,omitempty"`
AllowsPublicRepositories *bool `json:"allows_public_repositories,omitempty"`
RestrictedToWorkflows *bool `json:"restricted_to_workflows,omitempty"`
SelectedWorkflows []string `json:"selected_workflows,omitempty"`
WorkflowRestrictionsReadOnly *bool `json:"workflow_restrictions_read_only,omitempty"`
}
// RunnerGroups represents a collection of self-hosted runner groups configured for an organization.
type RunnerGroups struct {
TotalCount int `json:"total_count"`
RunnerGroups []*RunnerGroup `json:"runner_groups"`
}
// CreateRunnerGroupRequest represents a request to create a Runner group for an organization.
type CreateRunnerGroupRequest struct {
Name *string `json:"name,omitempty"`
Visibility *string `json:"visibility,omitempty"`
// List of repository IDs that can access the runner group.
SelectedRepositoryIDs []int64 `json:"selected_repository_ids,omitempty"`
// Runners represent a list of runner IDs to add to the runner group.
Runners []int64 `json:"runners,omitempty"`
// If set to True, public repos can use this runner group
AllowsPublicRepositories *bool `json:"allows_public_repositories,omitempty"`
// If true, the runner group will be restricted to running only the workflows specified in the SelectedWorkflows slice.
RestrictedToWorkflows *bool `json:"restricted_to_workflows,omitempty"`
// List of workflows the runner group should be allowed to run. This setting will be ignored unless RestrictedToWorkflows is set to true.
SelectedWorkflows []string `json:"selected_workflows,omitempty"`
}
// UpdateRunnerGroupRequest represents a request to update a Runner group for an organization.
type UpdateRunnerGroupRequest struct {
Name *string `json:"name,omitempty"`
Visibility *string `json:"visibility,omitempty"`
AllowsPublicRepositories *bool `json:"allows_public_repositories,omitempty"`
RestrictedToWorkflows *bool `json:"restricted_to_workflows,omitempty"`
SelectedWorkflows []string `json:"selected_workflows,omitempty"`
}
// SetRepoAccessRunnerGroupRequest represents a request to replace the list of repositories
// that can access a self-hosted runner group configured in an organization.
type SetRepoAccessRunnerGroupRequest struct {
// Updated list of repository IDs that should be given access to the runner group.
SelectedRepositoryIDs []int64 `json:"selected_repository_ids"`
}
// SetRunnerGroupRunnersRequest represents a request to replace the list of
// self-hosted runners that are part of an organization runner group.
type SetRunnerGroupRunnersRequest struct {
// Updated list of runner IDs that should be given access to the runner group.
Runners []int64 `json:"runners"`
}
// ListOrgRunnerGroupOptions extend ListOptions to have the optional parameters VisibleToRepository.
type ListOrgRunnerGroupOptions struct {
ListOptions
// Only return runner groups that are allowed to be used by this repository.
VisibleToRepository string `url:"visible_to_repository,omitempty"`
}
// ListOrganizationRunnerGroups lists all self-hosted runner groups configured in an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runner-groups#list-self-hosted-runner-groups-for-an-organization
func (s *ActionsService) ListOrganizationRunnerGroups(ctx context.Context, org string, opts *ListOrgRunnerGroupOptions) (*RunnerGroups, *Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runner-groups", org)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
groups := &RunnerGroups{}
resp, err := s.client.Do(ctx, req, &groups)
if err != nil {
return nil, resp, err
}
return groups, resp, nil
}
// GetOrganizationRunnerGroup gets a specific self-hosted runner group for an organization using its RunnerGroup ID.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runner-groups#get-a-self-hosted-runner-group-for-an-organization
func (s *ActionsService) GetOrganizationRunnerGroup(ctx context.Context, org string, groupID int64) (*RunnerGroup, *Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runner-groups/%v", org, groupID)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
runnerGroup := new(RunnerGroup)
resp, err := s.client.Do(ctx, req, runnerGroup)
if err != nil {
return nil, resp, err
}
return runnerGroup, resp, nil
}
// DeleteOrganizationRunnerGroup deletes a self-hosted runner group from an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runner-groups#delete-a-self-hosted-runner-group-from-an-organization
func (s *ActionsService) DeleteOrganizationRunnerGroup(ctx context.Context, org string, groupID int64) (*Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runner-groups/%v", org, groupID)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// CreateOrganizationRunnerGroup creates a new self-hosted runner group for an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runner-groups#create-a-self-hosted-runner-group-for-an-organization
func (s *ActionsService) CreateOrganizationRunnerGroup(ctx context.Context, org string, createReq CreateRunnerGroupRequest) (*RunnerGroup, *Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runner-groups", org)
req, err := s.client.NewRequest("POST", u, createReq)
if err != nil {
return nil, nil, err
}
runnerGroup := new(RunnerGroup)
resp, err := s.client.Do(ctx, req, runnerGroup)
if err != nil {
return nil, resp, err
}
return runnerGroup, resp, nil
}
// UpdateOrganizationRunnerGroup updates a self-hosted runner group for an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runner-groups#update-a-self-hosted-runner-group-for-an-organization
func (s *ActionsService) UpdateOrganizationRunnerGroup(ctx context.Context, org string, groupID int64, updateReq UpdateRunnerGroupRequest) (*RunnerGroup, *Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runner-groups/%v", org, groupID)
req, err := s.client.NewRequest("PATCH", u, updateReq)
if err != nil {
return nil, nil, err
}
runnerGroup := new(RunnerGroup)
resp, err := s.client.Do(ctx, req, runnerGroup)
if err != nil {
return nil, resp, err
}
return runnerGroup, resp, nil
}
// ListRepositoryAccessRunnerGroup lists the repositories with access to a self-hosted runner group configured in an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runner-groups#list-repository-access-to-a-self-hosted-runner-group-in-an-organization
func (s *ActionsService) ListRepositoryAccessRunnerGroup(ctx context.Context, org string, groupID int64, opts *ListOptions) (*ListRepositories, *Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runner-groups/%v/repositories", org, groupID)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
repos := &ListRepositories{}
resp, err := s.client.Do(ctx, req, &repos)
if err != nil {
return nil, resp, err
}
return repos, resp, nil
}
// SetRepositoryAccessRunnerGroup replaces the list of repositories that have access to a self-hosted runner group configured in an organization
// with a new List of repositories.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runner-groups#set-repository-access-for-a-self-hosted-runner-group-in-an-organization
func (s *ActionsService) SetRepositoryAccessRunnerGroup(ctx context.Context, org string, groupID int64, ids SetRepoAccessRunnerGroupRequest) (*Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runner-groups/%v/repositories", org, groupID)
req, err := s.client.NewRequest("PUT", u, ids)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// AddRepositoryAccessRunnerGroup adds a repository to the list of selected repositories that can access a self-hosted runner group.
// The runner group must have visibility set to 'selected'.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runner-groups#add-repository-access-to-a-self-hosted-runner-group-in-an-organization
func (s *ActionsService) AddRepositoryAccessRunnerGroup(ctx context.Context, org string, groupID, repoID int64) (*Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runner-groups/%v/repositories/%v", org, groupID, repoID)
req, err := s.client.NewRequest("PUT", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// RemoveRepositoryAccessRunnerGroup removes a repository from the list of selected repositories that can access a self-hosted runner group.
// The runner group must have visibility set to 'selected'.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runner-groups#remove-repository-access-to-a-self-hosted-runner-group-in-an-organization
func (s *ActionsService) RemoveRepositoryAccessRunnerGroup(ctx context.Context, org string, groupID, repoID int64) (*Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runner-groups/%v/repositories/%v", org, groupID, repoID)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// ListRunnerGroupRunners lists self-hosted runners that are in a specific organization group.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runner-groups#list-self-hosted-runners-in-a-group-for-an-organization
func (s *ActionsService) ListRunnerGroupRunners(ctx context.Context, org string, groupID int64, opts *ListOptions) (*Runners, *Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runner-groups/%v/runners", org, groupID)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
runners := &Runners{}
resp, err := s.client.Do(ctx, req, &runners)
if err != nil {
return nil, resp, err
}
return runners, resp, nil
}
// SetRunnerGroupRunners replaces the list of self-hosted runners that are part of an organization runner group
// with a new list of runners.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runner-groups#set-self-hosted-runners-in-a-group-for-an-organization
func (s *ActionsService) SetRunnerGroupRunners(ctx context.Context, org string, groupID int64, ids SetRunnerGroupRunnersRequest) (*Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runner-groups/%v/runners", org, groupID)
req, err := s.client.NewRequest("PUT", u, ids)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// AddRunnerGroupRunners adds a self-hosted runner to a runner group configured in an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runner-groups#add-a-self-hosted-runner-to-a-group-for-an-organization
func (s *ActionsService) AddRunnerGroupRunners(ctx context.Context, org string, groupID, runnerID int64) (*Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runner-groups/%v/runners/%v", org, groupID, runnerID)
req, err := s.client.NewRequest("PUT", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// RemoveRunnerGroupRunners removes a self-hosted runner from a group configured in an organization.
// The runner is then returned to the default group.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runner-groups#remove-a-self-hosted-runner-from-a-group-for-an-organization
func (s *ActionsService) RemoveRunnerGroupRunners(ctx context.Context, org string, groupID, runnerID int64) (*Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runner-groups/%v/runners/%v", org, groupID, runnerID)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}

View File

@@ -0,0 +1,377 @@
// Copyright 2020 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// RunnerApplicationDownload represents a binary for the self-hosted runner application that can be downloaded.
type RunnerApplicationDownload struct {
OS *string `json:"os,omitempty"`
Architecture *string `json:"architecture,omitempty"`
DownloadURL *string `json:"download_url,omitempty"`
Filename *string `json:"filename,omitempty"`
TempDownloadToken *string `json:"temp_download_token,omitempty"`
SHA256Checksum *string `json:"sha256_checksum,omitempty"`
}
// ActionsEnabledOnOrgRepos represents all the repositories in an organization for which Actions is enabled.
type ActionsEnabledOnOrgRepos struct {
TotalCount int `json:"total_count"`
Repositories []*Repository `json:"repositories"`
}
// ListRunnerApplicationDownloads lists self-hosted runner application binaries that can be downloaded and run.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners#list-runner-applications-for-a-repository
func (s *ActionsService) ListRunnerApplicationDownloads(ctx context.Context, owner, repo string) ([]*RunnerApplicationDownload, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runners/downloads", owner, repo)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var rads []*RunnerApplicationDownload
resp, err := s.client.Do(ctx, req, &rads)
if err != nil {
return nil, resp, err
}
return rads, resp, nil
}
// RegistrationToken represents a token that can be used to add a self-hosted runner to a repository.
type RegistrationToken struct {
Token *string `json:"token,omitempty"`
ExpiresAt *Timestamp `json:"expires_at,omitempty"`
}
// CreateRegistrationToken creates a token that can be used to add a self-hosted runner.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners#create-a-registration-token-for-a-repository
func (s *ActionsService) CreateRegistrationToken(ctx context.Context, owner, repo string) (*RegistrationToken, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runners/registration-token", owner, repo)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, nil, err
}
registrationToken := new(RegistrationToken)
resp, err := s.client.Do(ctx, req, registrationToken)
if err != nil {
return nil, resp, err
}
return registrationToken, resp, nil
}
// Runner represents a self-hosted runner registered with a repository.
type Runner struct {
ID *int64 `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
OS *string `json:"os,omitempty"`
Status *string `json:"status,omitempty"`
Busy *bool `json:"busy,omitempty"`
Labels []*RunnerLabels `json:"labels,omitempty"`
}
// RunnerLabels represents a collection of labels attached to each runner.
type RunnerLabels struct {
ID *int64 `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Type *string `json:"type,omitempty"`
}
// Runners represents a collection of self-hosted runners for a repository.
type Runners struct {
TotalCount int `json:"total_count"`
Runners []*Runner `json:"runners"`
}
// ListRunners lists all the self-hosted runners for a repository.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners#list-self-hosted-runners-for-a-repository
func (s *ActionsService) ListRunners(ctx context.Context, owner, repo string, opts *ListOptions) (*Runners, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runners", owner, repo)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
runners := &Runners{}
resp, err := s.client.Do(ctx, req, &runners)
if err != nil {
return nil, resp, err
}
return runners, resp, nil
}
// GetRunner gets a specific self-hosted runner for a repository using its runner ID.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners#get-a-self-hosted-runner-for-a-repository
func (s *ActionsService) GetRunner(ctx context.Context, owner, repo string, runnerID int64) (*Runner, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runners/%v", owner, repo, runnerID)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
runner := new(Runner)
resp, err := s.client.Do(ctx, req, runner)
if err != nil {
return nil, resp, err
}
return runner, resp, nil
}
// RemoveToken represents a token that can be used to remove a self-hosted runner from a repository.
type RemoveToken struct {
Token *string `json:"token,omitempty"`
ExpiresAt *Timestamp `json:"expires_at,omitempty"`
}
// CreateRemoveToken creates a token that can be used to remove a self-hosted runner from a repository.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners#create-a-remove-token-for-a-repository
func (s *ActionsService) CreateRemoveToken(ctx context.Context, owner, repo string) (*RemoveToken, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runners/remove-token", owner, repo)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, nil, err
}
removeToken := new(RemoveToken)
resp, err := s.client.Do(ctx, req, removeToken)
if err != nil {
return nil, resp, err
}
return removeToken, resp, nil
}
// RemoveRunner forces the removal of a self-hosted runner in a repository using the runner id.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners#delete-a-self-hosted-runner-from-a-repository
func (s *ActionsService) RemoveRunner(ctx context.Context, owner, repo string, runnerID int64) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runners/%v", owner, repo, runnerID)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// ListOrganizationRunnerApplicationDownloads lists self-hosted runner application binaries that can be downloaded and run.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners#list-runner-applications-for-an-organization
func (s *ActionsService) ListOrganizationRunnerApplicationDownloads(ctx context.Context, owner string) ([]*RunnerApplicationDownload, *Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runners/downloads", owner)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var rads []*RunnerApplicationDownload
resp, err := s.client.Do(ctx, req, &rads)
if err != nil {
return nil, resp, err
}
return rads, resp, nil
}
// CreateOrganizationRegistrationToken creates a token that can be used to add a self-hosted runner to an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners#create-a-registration-token-for-an-organization
func (s *ActionsService) CreateOrganizationRegistrationToken(ctx context.Context, owner string) (*RegistrationToken, *Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runners/registration-token", owner)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, nil, err
}
registrationToken := new(RegistrationToken)
resp, err := s.client.Do(ctx, req, registrationToken)
if err != nil {
return nil, resp, err
}
return registrationToken, resp, nil
}
// ListOrganizationRunners lists all the self-hosted runners for an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners#list-self-hosted-runners-for-an-organization
func (s *ActionsService) ListOrganizationRunners(ctx context.Context, owner string, opts *ListOptions) (*Runners, *Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runners", owner)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
runners := &Runners{}
resp, err := s.client.Do(ctx, req, &runners)
if err != nil {
return nil, resp, err
}
return runners, resp, nil
}
// ListEnabledReposInOrg lists the selected repositories that are enabled for GitHub Actions in an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/permissions#list-selected-repositories-enabled-for-github-actions-in-an-organization
func (s *ActionsService) ListEnabledReposInOrg(ctx context.Context, owner string, opts *ListOptions) (*ActionsEnabledOnOrgRepos, *Response, error) {
u := fmt.Sprintf("orgs/%v/actions/permissions/repositories", owner)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
repos := &ActionsEnabledOnOrgRepos{}
resp, err := s.client.Do(ctx, req, repos)
if err != nil {
return nil, resp, err
}
return repos, resp, nil
}
// SetEnabledReposInOrg replaces the list of selected repositories that are enabled for GitHub Actions in an organization..
//
// GitHub API docs: https://docs.github.com/en/rest/actions/permissions#set-selected-repositories-enabled-for-github-actions-in-an-organization
func (s *ActionsService) SetEnabledReposInOrg(ctx context.Context, owner string, repositoryIDs []int64) (*Response, error) {
u := fmt.Sprintf("orgs/%v/actions/permissions/repositories", owner)
req, err := s.client.NewRequest("PUT", u, struct {
IDs []int64 `json:"selected_repository_ids"`
}{IDs: repositoryIDs})
if err != nil {
return nil, err
}
resp, err := s.client.Do(ctx, req, nil)
if err != nil {
return resp, err
}
return resp, nil
}
// AddEnabledReposInOrg adds a repository to the list of selected repositories that are enabled for GitHub Actions in an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/permissions#enable-a-selected-repository-for-github-actions-in-an-organization
func (s *ActionsService) AddEnabledReposInOrg(ctx context.Context, owner string, repositoryID int64) (*Response, error) {
u := fmt.Sprintf("orgs/%v/actions/permissions/repositories/%v", owner, repositoryID)
req, err := s.client.NewRequest("PUT", u, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(ctx, req, nil)
if err != nil {
return resp, err
}
return resp, nil
}
// RemoveEnabledRepoInOrg removes a single repository from the list of enabled repos for GitHub Actions in an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/permissions#disable-a-selected-repository-for-github-actions-in-an-organization
func (s *ActionsService) RemoveEnabledRepoInOrg(ctx context.Context, owner string, repositoryID int64) (*Response, error) {
u := fmt.Sprintf("orgs/%v/actions/permissions/repositories/%v", owner, repositoryID)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(ctx, req, nil)
if err != nil {
return resp, err
}
return resp, nil
}
// GetOrganizationRunner gets a specific self-hosted runner for an organization using its runner ID.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners#get-a-self-hosted-runner-for-an-organization
func (s *ActionsService) GetOrganizationRunner(ctx context.Context, owner string, runnerID int64) (*Runner, *Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runners/%v", owner, runnerID)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
runner := new(Runner)
resp, err := s.client.Do(ctx, req, runner)
if err != nil {
return nil, resp, err
}
return runner, resp, nil
}
// CreateOrganizationRemoveToken creates a token that can be used to remove a self-hosted runner from an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners#create-a-remove-token-for-an-organization
func (s *ActionsService) CreateOrganizationRemoveToken(ctx context.Context, owner string) (*RemoveToken, *Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runners/remove-token", owner)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, nil, err
}
removeToken := new(RemoveToken)
resp, err := s.client.Do(ctx, req, removeToken)
if err != nil {
return nil, resp, err
}
return removeToken, resp, nil
}
// RemoveOrganizationRunner forces the removal of a self-hosted runner from an organization using the runner id.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners#delete-a-self-hosted-runner-from-an-organization
func (s *ActionsService) RemoveOrganizationRunner(ctx context.Context, owner string, runnerID int64) (*Response, error) {
u := fmt.Sprintf("orgs/%v/actions/runners/%v", owner, runnerID)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}

View File

@@ -0,0 +1,358 @@
// Copyright 2020 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"encoding/json"
"fmt"
"strconv"
)
// PublicKey represents the public key that should be used to encrypt secrets.
type PublicKey struct {
KeyID *string `json:"key_id"`
Key *string `json:"key"`
}
// UnmarshalJSON implements the json.Unmarshaler interface.
// This ensures GitHub Enterprise versions which return a numeric key id
// do not error out when unmarshaling.
func (p *PublicKey) UnmarshalJSON(data []byte) error {
var pk struct {
KeyID interface{} `json:"key_id,string"`
Key *string `json:"key"`
}
if err := json.Unmarshal(data, &pk); err != nil {
return err
}
p.Key = pk.Key
switch v := pk.KeyID.(type) {
case nil:
return nil
case string:
p.KeyID = &v
case float64:
p.KeyID = String(strconv.FormatFloat(v, 'f', -1, 64))
default:
return fmt.Errorf("unable to unmarshal %T as a string", v)
}
return nil
}
func (s *ActionsService) getPublicKey(ctx context.Context, url string) (*PublicKey, *Response, error) {
req, err := s.client.NewRequest("GET", url, nil)
if err != nil {
return nil, nil, err
}
pubKey := new(PublicKey)
resp, err := s.client.Do(ctx, req, pubKey)
if err != nil {
return nil, resp, err
}
return pubKey, resp, nil
}
// GetRepoPublicKey gets a public key that should be used for secret encryption.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#get-a-repository-public-key
func (s *ActionsService) GetRepoPublicKey(ctx context.Context, owner, repo string) (*PublicKey, *Response, error) {
url := fmt.Sprintf("repos/%v/%v/actions/secrets/public-key", owner, repo)
return s.getPublicKey(ctx, url)
}
// GetOrgPublicKey gets a public key that should be used for secret encryption.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#get-an-organization-public-key
func (s *ActionsService) GetOrgPublicKey(ctx context.Context, org string) (*PublicKey, *Response, error) {
url := fmt.Sprintf("orgs/%v/actions/secrets/public-key", org)
return s.getPublicKey(ctx, url)
}
// GetEnvPublicKey gets a public key that should be used for secret encryption.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#get-an-environment-public-key
func (s *ActionsService) GetEnvPublicKey(ctx context.Context, repoID int, env string) (*PublicKey, *Response, error) {
url := fmt.Sprintf("repositories/%v/environments/%v/secrets/public-key", repoID, env)
return s.getPublicKey(ctx, url)
}
// Secret represents a repository action secret.
type Secret struct {
Name string `json:"name"`
CreatedAt Timestamp `json:"created_at"`
UpdatedAt Timestamp `json:"updated_at"`
Visibility string `json:"visibility,omitempty"`
SelectedRepositoriesURL string `json:"selected_repositories_url,omitempty"`
}
// Secrets represents one item from the ListSecrets response.
type Secrets struct {
TotalCount int `json:"total_count"`
Secrets []*Secret `json:"secrets"`
}
func (s *ActionsService) listSecrets(ctx context.Context, url string, opts *ListOptions) (*Secrets, *Response, error) {
u, err := addOptions(url, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
secrets := new(Secrets)
resp, err := s.client.Do(ctx, req, &secrets)
if err != nil {
return nil, resp, err
}
return secrets, resp, nil
}
// ListRepoSecrets lists all secrets available in a repository
// without revealing their encrypted values.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#list-repository-secrets
func (s *ActionsService) ListRepoSecrets(ctx context.Context, owner, repo string, opts *ListOptions) (*Secrets, *Response, error) {
url := fmt.Sprintf("repos/%v/%v/actions/secrets", owner, repo)
return s.listSecrets(ctx, url, opts)
}
// ListOrgSecrets lists all secrets available in an organization
// without revealing their encrypted values.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#list-organization-secrets
func (s *ActionsService) ListOrgSecrets(ctx context.Context, org string, opts *ListOptions) (*Secrets, *Response, error) {
url := fmt.Sprintf("orgs/%v/actions/secrets", org)
return s.listSecrets(ctx, url, opts)
}
// ListEnvSecrets lists all secrets available in an environment.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#list-environment-secrets
func (s *ActionsService) ListEnvSecrets(ctx context.Context, repoID int, env string, opts *ListOptions) (*Secrets, *Response, error) {
url := fmt.Sprintf("repositories/%v/environments/%v/secrets", repoID, env)
return s.listSecrets(ctx, url, opts)
}
func (s *ActionsService) getSecret(ctx context.Context, url string) (*Secret, *Response, error) {
req, err := s.client.NewRequest("GET", url, nil)
if err != nil {
return nil, nil, err
}
secret := new(Secret)
resp, err := s.client.Do(ctx, req, secret)
if err != nil {
return nil, resp, err
}
return secret, resp, nil
}
// GetRepoSecret gets a single repository secret without revealing its encrypted value.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#get-a-repository-secret
func (s *ActionsService) GetRepoSecret(ctx context.Context, owner, repo, name string) (*Secret, *Response, error) {
url := fmt.Sprintf("repos/%v/%v/actions/secrets/%v", owner, repo, name)
return s.getSecret(ctx, url)
}
// GetOrgSecret gets a single organization secret without revealing its encrypted value.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#get-an-organization-secret
func (s *ActionsService) GetOrgSecret(ctx context.Context, org, name string) (*Secret, *Response, error) {
url := fmt.Sprintf("orgs/%v/actions/secrets/%v", org, name)
return s.getSecret(ctx, url)
}
// GetEnvSecret gets a single environment secret without revealing its encrypted value.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#get-an-environment-secret
func (s *ActionsService) GetEnvSecret(ctx context.Context, repoID int, env, secretName string) (*Secret, *Response, error) {
url := fmt.Sprintf("repositories/%v/environments/%v/secrets/%v", repoID, env, secretName)
return s.getSecret(ctx, url)
}
// SelectedRepoIDs are the repository IDs that have access to the actions secrets.
type SelectedRepoIDs []int64
// EncryptedSecret represents a secret that is encrypted using a public key.
//
// The value of EncryptedValue must be your secret, encrypted with
// LibSodium (see documentation here: https://libsodium.gitbook.io/doc/bindings_for_other_languages)
// using the public key retrieved using the GetPublicKey method.
type EncryptedSecret struct {
Name string `json:"-"`
KeyID string `json:"key_id"`
EncryptedValue string `json:"encrypted_value"`
Visibility string `json:"visibility,omitempty"`
SelectedRepositoryIDs SelectedRepoIDs `json:"selected_repository_ids,omitempty"`
}
func (s *ActionsService) putSecret(ctx context.Context, url string, eSecret *EncryptedSecret) (*Response, error) {
req, err := s.client.NewRequest("PUT", url, eSecret)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// CreateOrUpdateRepoSecret creates or updates a repository secret with an encrypted value.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#create-or-update-a-repository-secret
func (s *ActionsService) CreateOrUpdateRepoSecret(ctx context.Context, owner, repo string, eSecret *EncryptedSecret) (*Response, error) {
url := fmt.Sprintf("repos/%v/%v/actions/secrets/%v", owner, repo, eSecret.Name)
return s.putSecret(ctx, url, eSecret)
}
// CreateOrUpdateOrgSecret creates or updates an organization secret with an encrypted value.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#create-or-update-an-organization-secret
func (s *ActionsService) CreateOrUpdateOrgSecret(ctx context.Context, org string, eSecret *EncryptedSecret) (*Response, error) {
url := fmt.Sprintf("orgs/%v/actions/secrets/%v", org, eSecret.Name)
return s.putSecret(ctx, url, eSecret)
}
// CreateOrUpdateEnvSecret creates or updates a single environment secret with an encrypted value.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#create-or-update-an-environment-secret
func (s *ActionsService) CreateOrUpdateEnvSecret(ctx context.Context, repoID int, env string, eSecret *EncryptedSecret) (*Response, error) {
url := fmt.Sprintf("repositories/%v/environments/%v/secrets/%v", repoID, env, eSecret.Name)
return s.putSecret(ctx, url, eSecret)
}
func (s *ActionsService) deleteSecret(ctx context.Context, url string) (*Response, error) {
req, err := s.client.NewRequest("DELETE", url, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// DeleteRepoSecret deletes a secret in a repository using the secret name.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#delete-a-repository-secret
func (s *ActionsService) DeleteRepoSecret(ctx context.Context, owner, repo, name string) (*Response, error) {
url := fmt.Sprintf("repos/%v/%v/actions/secrets/%v", owner, repo, name)
return s.deleteSecret(ctx, url)
}
// DeleteOrgSecret deletes a secret in an organization using the secret name.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#delete-an-organization-secret
func (s *ActionsService) DeleteOrgSecret(ctx context.Context, org, name string) (*Response, error) {
url := fmt.Sprintf("orgs/%v/actions/secrets/%v", org, name)
return s.deleteSecret(ctx, url)
}
// DeleteEnvSecret deletes a secret in an environment using the secret name.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#delete-an-environment-secret
func (s *ActionsService) DeleteEnvSecret(ctx context.Context, repoID int, env, secretName string) (*Response, error) {
url := fmt.Sprintf("repositories/%v/environments/%v/secrets/%v", repoID, env, secretName)
return s.deleteSecret(ctx, url)
}
// SelectedReposList represents the list of repositories selected for an organization secret.
type SelectedReposList struct {
TotalCount *int `json:"total_count,omitempty"`
Repositories []*Repository `json:"repositories,omitempty"`
}
func (s *ActionsService) listSelectedReposForSecret(ctx context.Context, url string, opts *ListOptions) (*SelectedReposList, *Response, error) {
u, err := addOptions(url, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
result := new(SelectedReposList)
resp, err := s.client.Do(ctx, req, result)
if err != nil {
return nil, resp, err
}
return result, resp, nil
}
// ListSelectedReposForOrgSecret lists all repositories that have access to a secret.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#list-selected-repositories-for-an-organization-secret
func (s *ActionsService) ListSelectedReposForOrgSecret(ctx context.Context, org, name string, opts *ListOptions) (*SelectedReposList, *Response, error) {
url := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories", org, name)
return s.listSelectedReposForSecret(ctx, url, opts)
}
func (s *ActionsService) setSelectedReposForSecret(ctx context.Context, url string, ids SelectedRepoIDs) (*Response, error) {
type repoIDs struct {
SelectedIDs SelectedRepoIDs `json:"selected_repository_ids"`
}
req, err := s.client.NewRequest("PUT", url, repoIDs{SelectedIDs: ids})
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// SetSelectedReposForOrgSecret sets the repositories that have access to a secret.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#set-selected-repositories-for-an-organization-secret
func (s *ActionsService) SetSelectedReposForOrgSecret(ctx context.Context, org, name string, ids SelectedRepoIDs) (*Response, error) {
url := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories", org, name)
return s.setSelectedReposForSecret(ctx, url, ids)
}
func (s *ActionsService) addSelectedRepoToSecret(ctx context.Context, url string) (*Response, error) {
req, err := s.client.NewRequest("PUT", url, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// AddSelectedRepoToOrgSecret adds a repository to an organization secret.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#add-selected-repository-to-an-organization-secret
func (s *ActionsService) AddSelectedRepoToOrgSecret(ctx context.Context, org, name string, repo *Repository) (*Response, error) {
url := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories/%v", org, name, *repo.ID)
return s.addSelectedRepoToSecret(ctx, url)
}
func (s *ActionsService) removeSelectedRepoFromSecret(ctx context.Context, url string) (*Response, error) {
req, err := s.client.NewRequest("DELETE", url, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// RemoveSelectedRepoFromOrgSecret removes a repository from an organization secret.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/secrets#remove-selected-repository-from-an-organization-secret
func (s *ActionsService) RemoveSelectedRepoFromOrgSecret(ctx context.Context, org, name string, repo *Repository) (*Response, error) {
url := fmt.Sprintf("orgs/%v/actions/secrets/%v/repositories/%v", org, name, *repo.ID)
return s.removeSelectedRepoFromSecret(ctx, url)
}

View File

@@ -0,0 +1,130 @@
// Copyright 2020 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
"net/http"
"net/url"
)
// TaskStep represents a single task step from a sequence of tasks of a job.
type TaskStep struct {
Name *string `json:"name,omitempty"`
Status *string `json:"status,omitempty"`
Conclusion *string `json:"conclusion,omitempty"`
Number *int64 `json:"number,omitempty"`
StartedAt *Timestamp `json:"started_at,omitempty"`
CompletedAt *Timestamp `json:"completed_at,omitempty"`
}
// WorkflowJob represents a repository action workflow job.
type WorkflowJob struct {
ID *int64 `json:"id,omitempty"`
RunID *int64 `json:"run_id,omitempty"`
RunURL *string `json:"run_url,omitempty"`
NodeID *string `json:"node_id,omitempty"`
HeadSHA *string `json:"head_sha,omitempty"`
URL *string `json:"url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
Status *string `json:"status,omitempty"`
Conclusion *string `json:"conclusion,omitempty"`
StartedAt *Timestamp `json:"started_at,omitempty"`
CompletedAt *Timestamp `json:"completed_at,omitempty"`
Name *string `json:"name,omitempty"`
Steps []*TaskStep `json:"steps,omitempty"`
CheckRunURL *string `json:"check_run_url,omitempty"`
// Labels represents runner labels from the `runs-on:` key from a GitHub Actions workflow.
Labels []string `json:"labels,omitempty"`
RunnerID *int64 `json:"runner_id,omitempty"`
RunnerName *string `json:"runner_name,omitempty"`
RunnerGroupID *int64 `json:"runner_group_id,omitempty"`
RunnerGroupName *string `json:"runner_group_name,omitempty"`
RunAttempt *int64 `json:"run_attempt,omitempty"`
}
// Jobs represents a slice of repository action workflow job.
type Jobs struct {
TotalCount *int `json:"total_count,omitempty"`
Jobs []*WorkflowJob `json:"jobs,omitempty"`
}
// ListWorkflowJobsOptions specifies optional parameters to ListWorkflowJobs.
type ListWorkflowJobsOptions struct {
// Filter specifies how jobs should be filtered by their completed_at timestamp.
// Possible values are:
// latest - Returns jobs from the most recent execution of the workflow run
// all - Returns all jobs for a workflow run, including from old executions of the workflow run
//
// Default value is "latest".
Filter string `url:"filter,omitempty"`
ListOptions
}
// ListWorkflowJobs lists all jobs for a workflow run.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-jobs#list-jobs-for-a-workflow-run
func (s *ActionsService) ListWorkflowJobs(ctx context.Context, owner, repo string, runID int64, opts *ListWorkflowJobsOptions) (*Jobs, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/actions/runs/%v/jobs", owner, repo, runID)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
jobs := new(Jobs)
resp, err := s.client.Do(ctx, req, &jobs)
if err != nil {
return nil, resp, err
}
return jobs, resp, nil
}
// GetWorkflowJobByID gets a specific job in a workflow run by ID.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-jobs#get-a-job-for-a-workflow-run
func (s *ActionsService) GetWorkflowJobByID(ctx context.Context, owner, repo string, jobID int64) (*WorkflowJob, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/jobs/%v", owner, repo, jobID)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
job := new(WorkflowJob)
resp, err := s.client.Do(ctx, req, job)
if err != nil {
return nil, resp, err
}
return job, resp, nil
}
// GetWorkflowJobLogs gets a redirect URL to download a plain text file of logs for a workflow job.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-jobs#download-job-logs-for-a-workflow-run
func (s *ActionsService) GetWorkflowJobLogs(ctx context.Context, owner, repo string, jobID int64, followRedirects bool) (*url.URL, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/jobs/%v/logs", owner, repo, jobID)
resp, err := s.client.roundTripWithOptionalFollowRedirect(ctx, u, followRedirects)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusFound {
return nil, newResponse(resp), fmt.Errorf("unexpected status code: %s", resp.Status)
}
parsedURL, err := url.Parse(resp.Header.Get("Location"))
return parsedURL, newResponse(resp), err
}

View File

@@ -0,0 +1,351 @@
// Copyright 2020 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
"net/http"
"net/url"
)
// WorkflowRun represents a repository action workflow run.
type WorkflowRun struct {
ID *int64 `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
NodeID *string `json:"node_id,omitempty"`
HeadBranch *string `json:"head_branch,omitempty"`
HeadSHA *string `json:"head_sha,omitempty"`
RunNumber *int `json:"run_number,omitempty"`
RunAttempt *int `json:"run_attempt,omitempty"`
Event *string `json:"event,omitempty"`
Status *string `json:"status,omitempty"`
Conclusion *string `json:"conclusion,omitempty"`
WorkflowID *int64 `json:"workflow_id,omitempty"`
CheckSuiteID *int64 `json:"check_suite_id,omitempty"`
CheckSuiteNodeID *string `json:"check_suite_node_id,omitempty"`
URL *string `json:"url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
PullRequests []*PullRequest `json:"pull_requests,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
RunStartedAt *Timestamp `json:"run_started_at,omitempty"`
JobsURL *string `json:"jobs_url,omitempty"`
LogsURL *string `json:"logs_url,omitempty"`
CheckSuiteURL *string `json:"check_suite_url,omitempty"`
ArtifactsURL *string `json:"artifacts_url,omitempty"`
CancelURL *string `json:"cancel_url,omitempty"`
RerunURL *string `json:"rerun_url,omitempty"`
PreviousAttemptURL *string `json:"previous_attempt_url,omitempty"`
HeadCommit *HeadCommit `json:"head_commit,omitempty"`
WorkflowURL *string `json:"workflow_url,omitempty"`
Repository *Repository `json:"repository,omitempty"`
HeadRepository *Repository `json:"head_repository,omitempty"`
Actor *User `json:"actor,omitempty"`
}
// WorkflowRuns represents a slice of repository action workflow run.
type WorkflowRuns struct {
TotalCount *int `json:"total_count,omitempty"`
WorkflowRuns []*WorkflowRun `json:"workflow_runs,omitempty"`
}
// ListWorkflowRunsOptions specifies optional parameters to ListWorkflowRuns.
type ListWorkflowRunsOptions struct {
Actor string `url:"actor,omitempty"`
Branch string `url:"branch,omitempty"`
Event string `url:"event,omitempty"`
Status string `url:"status,omitempty"`
Created string `url:"created,omitempty"`
ListOptions
}
// WorkflowRunUsage represents a usage of a specific workflow run.
type WorkflowRunUsage struct {
Billable *WorkflowRunEnvironment `json:"billable,omitempty"`
RunDurationMS *int64 `json:"run_duration_ms,omitempty"`
}
// WorkflowRunEnvironment represents different runner environments available for a workflow run.
type WorkflowRunEnvironment struct {
Ubuntu *WorkflowRunBill `json:"UBUNTU,omitempty"`
MacOS *WorkflowRunBill `json:"MACOS,omitempty"`
Windows *WorkflowRunBill `json:"WINDOWS,omitempty"`
}
// WorkflowRunBill specifies billable time for a specific environment in a workflow run.
type WorkflowRunBill struct {
TotalMS *int64 `json:"total_ms,omitempty"`
Jobs *int `json:"jobs,omitempty"`
JobRuns []*WorkflowRunJobRun `json:"job_runs,omitempty"`
}
// WorkflowRunJobRun represents a usage of individual jobs of a specific workflow run.
type WorkflowRunJobRun struct {
JobID *int `json:"job_id,omitempty"`
DurationMS *int64 `json:"duration_ms,omitempty"`
}
// WorkflowRunAttemptOptions specifies optional parameters to GetWorkflowRunAttempt.
type WorkflowRunAttemptOptions struct {
ExcludePullRequests *bool `url:"exclude_pull_requests,omitempty"`
}
// PendingDeploymentsRequest specifies body parameters to PendingDeployments.
type PendingDeploymentsRequest struct {
EnvironmentIDs []int64 `json:"environment_ids"`
// State can be one of: "approved", "rejected".
State string `json:"state"`
Comment string `json:"comment"`
}
func (s *ActionsService) listWorkflowRuns(ctx context.Context, endpoint string, opts *ListWorkflowRunsOptions) (*WorkflowRuns, *Response, error) {
u, err := addOptions(endpoint, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
runs := new(WorkflowRuns)
resp, err := s.client.Do(ctx, req, &runs)
if err != nil {
return nil, resp, err
}
return runs, resp, nil
}
// ListWorkflowRunsByID lists all workflow runs by workflow ID.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#list-workflow-runs
func (s *ActionsService) ListWorkflowRunsByID(ctx context.Context, owner, repo string, workflowID int64, opts *ListWorkflowRunsOptions) (*WorkflowRuns, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/actions/workflows/%v/runs", owner, repo, workflowID)
return s.listWorkflowRuns(ctx, u, opts)
}
// ListWorkflowRunsByFileName lists all workflow runs by workflow file name.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#list-workflow-runs
func (s *ActionsService) ListWorkflowRunsByFileName(ctx context.Context, owner, repo, workflowFileName string, opts *ListWorkflowRunsOptions) (*WorkflowRuns, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/actions/workflows/%v/runs", owner, repo, workflowFileName)
return s.listWorkflowRuns(ctx, u, opts)
}
// ListRepositoryWorkflowRuns lists all workflow runs for a repository.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#list-workflow-runs-for-a-repository
func (s *ActionsService) ListRepositoryWorkflowRuns(ctx context.Context, owner, repo string, opts *ListWorkflowRunsOptions) (*WorkflowRuns, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/actions/runs", owner, repo)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
runs := new(WorkflowRuns)
resp, err := s.client.Do(ctx, req, &runs)
if err != nil {
return nil, resp, err
}
return runs, resp, nil
}
// GetWorkflowRunByID gets a specific workflow run by ID.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#get-a-workflow-run
func (s *ActionsService) GetWorkflowRunByID(ctx context.Context, owner, repo string, runID int64) (*WorkflowRun, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v", owner, repo, runID)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
run := new(WorkflowRun)
resp, err := s.client.Do(ctx, req, run)
if err != nil {
return nil, resp, err
}
return run, resp, nil
}
// GetWorkflowRunAttempt gets a specific workflow run attempt.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#get-a-workflow-run-attempt
func (s *ActionsService) GetWorkflowRunAttempt(ctx context.Context, owner, repo string, runID int64, attemptNumber int, opts *WorkflowRunAttemptOptions) (*WorkflowRun, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/attempts/%v", owner, repo, runID, attemptNumber)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
run := new(WorkflowRun)
resp, err := s.client.Do(ctx, req, run)
if err != nil {
return nil, resp, err
}
return run, resp, nil
}
// RerunWorkflowByID re-runs a workflow by ID.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#re-run-a-workflow
func (s *ActionsService) RerunWorkflowByID(ctx context.Context, owner, repo string, runID int64) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/rerun", owner, repo, runID)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// RerunFailedJobsByID re-runs all of the failed jobs and their dependent jobs in a workflow run by ID.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#re-run-failed-jobs-from-a-workflow-run
func (s *ActionsService) RerunFailedJobsByID(ctx context.Context, owner, repo string, runID int64) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/rerun-failed-jobs", owner, repo, runID)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// RerunJobByID re-runs a job and its dependent jobs in a workflow run by ID.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#re-run-a-job-from-a-workflow-run
func (s *ActionsService) RerunJobByID(ctx context.Context, owner, repo string, jobID int64) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/jobs/%v/rerun", owner, repo, jobID)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// CancelWorkflowRunByID cancels a workflow run by ID.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#cancel-a-workflow-run
func (s *ActionsService) CancelWorkflowRunByID(ctx context.Context, owner, repo string, runID int64) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/cancel", owner, repo, runID)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// GetWorkflowRunLogs gets a redirect URL to download a plain text file of logs for a workflow run.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#download-workflow-run-logs
func (s *ActionsService) GetWorkflowRunLogs(ctx context.Context, owner, repo string, runID int64, followRedirects bool) (*url.URL, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/logs", owner, repo, runID)
resp, err := s.client.roundTripWithOptionalFollowRedirect(ctx, u, followRedirects)
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusFound {
return nil, newResponse(resp), fmt.Errorf("unexpected status code: %s", resp.Status)
}
parsedURL, err := url.Parse(resp.Header.Get("Location"))
return parsedURL, newResponse(resp), err
}
// DeleteWorkflowRun deletes a workflow run by ID.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#delete-a-workflow-run
func (s *ActionsService) DeleteWorkflowRun(ctx context.Context, owner, repo string, runID int64) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v", owner, repo, runID)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// DeleteWorkflowRunLogs deletes all logs for a workflow run.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#delete-workflow-run-logs
func (s *ActionsService) DeleteWorkflowRunLogs(ctx context.Context, owner, repo string, runID int64) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/logs", owner, repo, runID)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// GetWorkflowRunUsageByID gets a specific workflow usage run by run ID in the unit of billable milliseconds.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#get-workflow-run-usage
func (s *ActionsService) GetWorkflowRunUsageByID(ctx context.Context, owner, repo string, runID int64) (*WorkflowRunUsage, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/timing", owner, repo, runID)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
workflowRunUsage := new(WorkflowRunUsage)
resp, err := s.client.Do(ctx, req, workflowRunUsage)
if err != nil {
return nil, resp, err
}
return workflowRunUsage, resp, nil
}
// PendingDeployments approve or reject pending deployments that are waiting on approval by a required reviewer.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflow-runs#review-pending-deployments-for-a-workflow-run
func (s *ActionsService) PendingDeployments(ctx context.Context, owner, repo string, runID int64, request *PendingDeploymentsRequest) ([]*Deployment, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/runs/%v/pending_deployments", owner, repo, runID)
req, err := s.client.NewRequest("POST", u, request)
if err != nil {
return nil, nil, err
}
var deployments []*Deployment
resp, err := s.client.Do(ctx, req, &deployments)
if err != nil {
return nil, resp, err
}
return deployments, resp, nil
}

View File

@@ -0,0 +1,218 @@
// Copyright 2020 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// Workflow represents a repository action workflow.
type Workflow struct {
ID *int64 `json:"id,omitempty"`
NodeID *string `json:"node_id,omitempty"`
Name *string `json:"name,omitempty"`
Path *string `json:"path,omitempty"`
State *string `json:"state,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
URL *string `json:"url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
BadgeURL *string `json:"badge_url,omitempty"`
}
// Workflows represents a slice of repository action workflows.
type Workflows struct {
TotalCount *int `json:"total_count,omitempty"`
Workflows []*Workflow `json:"workflows,omitempty"`
}
// WorkflowUsage represents a usage of a specific workflow.
type WorkflowUsage struct {
Billable *WorkflowEnvironment `json:"billable,omitempty"`
}
// WorkflowEnvironment represents different runner environments available for a workflow.
type WorkflowEnvironment struct {
Ubuntu *WorkflowBill `json:"UBUNTU,omitempty"`
MacOS *WorkflowBill `json:"MACOS,omitempty"`
Windows *WorkflowBill `json:"WINDOWS,omitempty"`
}
// WorkflowBill specifies billable time for a specific environment in a workflow.
type WorkflowBill struct {
TotalMS *int64 `json:"total_ms,omitempty"`
}
// CreateWorkflowDispatchEventRequest represents a request to create a workflow dispatch event.
type CreateWorkflowDispatchEventRequest struct {
// Ref represents the reference of the workflow run.
// The reference can be a branch or a tag.
// Ref is required when creating a workflow dispatch event.
Ref string `json:"ref"`
// Inputs represents input keys and values configured in the workflow file.
// The maximum number of properties is 10.
// Default: Any default properties configured in the workflow file will be used when `inputs` are omitted.
Inputs map[string]interface{} `json:"inputs,omitempty"`
}
// ListWorkflows lists all workflows in a repository.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflows#list-repository-workflows
func (s *ActionsService) ListWorkflows(ctx context.Context, owner, repo string, opts *ListOptions) (*Workflows, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/actions/workflows", owner, repo)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
workflows := new(Workflows)
resp, err := s.client.Do(ctx, req, &workflows)
if err != nil {
return nil, resp, err
}
return workflows, resp, nil
}
// GetWorkflowByID gets a specific workflow by ID.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflows#get-a-workflow
func (s *ActionsService) GetWorkflowByID(ctx context.Context, owner, repo string, workflowID int64) (*Workflow, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/workflows/%v", owner, repo, workflowID)
return s.getWorkflow(ctx, u)
}
// GetWorkflowByFileName gets a specific workflow by file name.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflows#get-a-workflow
func (s *ActionsService) GetWorkflowByFileName(ctx context.Context, owner, repo, workflowFileName string) (*Workflow, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/workflows/%v", owner, repo, workflowFileName)
return s.getWorkflow(ctx, u)
}
func (s *ActionsService) getWorkflow(ctx context.Context, url string) (*Workflow, *Response, error) {
req, err := s.client.NewRequest("GET", url, nil)
if err != nil {
return nil, nil, err
}
workflow := new(Workflow)
resp, err := s.client.Do(ctx, req, workflow)
if err != nil {
return nil, resp, err
}
return workflow, resp, nil
}
// GetWorkflowUsageByID gets a specific workflow usage by ID in the unit of billable milliseconds.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflows#get-workflow-usage
func (s *ActionsService) GetWorkflowUsageByID(ctx context.Context, owner, repo string, workflowID int64) (*WorkflowUsage, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/workflows/%v/timing", owner, repo, workflowID)
return s.getWorkflowUsage(ctx, u)
}
// GetWorkflowUsageByFileName gets a specific workflow usage by file name in the unit of billable milliseconds.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflows#get-workflow-usage
func (s *ActionsService) GetWorkflowUsageByFileName(ctx context.Context, owner, repo, workflowFileName string) (*WorkflowUsage, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/workflows/%v/timing", owner, repo, workflowFileName)
return s.getWorkflowUsage(ctx, u)
}
func (s *ActionsService) getWorkflowUsage(ctx context.Context, url string) (*WorkflowUsage, *Response, error) {
req, err := s.client.NewRequest("GET", url, nil)
if err != nil {
return nil, nil, err
}
workflowUsage := new(WorkflowUsage)
resp, err := s.client.Do(ctx, req, workflowUsage)
if err != nil {
return nil, resp, err
}
return workflowUsage, resp, nil
}
// CreateWorkflowDispatchEventByID manually triggers a GitHub Actions workflow run.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflows#create-a-workflow-dispatch-event
func (s *ActionsService) CreateWorkflowDispatchEventByID(ctx context.Context, owner, repo string, workflowID int64, event CreateWorkflowDispatchEventRequest) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/workflows/%v/dispatches", owner, repo, workflowID)
return s.createWorkflowDispatchEvent(ctx, u, &event)
}
// CreateWorkflowDispatchEventByFileName manually triggers a GitHub Actions workflow run.
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflows#create-a-workflow-dispatch-event
func (s *ActionsService) CreateWorkflowDispatchEventByFileName(ctx context.Context, owner, repo, workflowFileName string, event CreateWorkflowDispatchEventRequest) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/workflows/%v/dispatches", owner, repo, workflowFileName)
return s.createWorkflowDispatchEvent(ctx, u, &event)
}
func (s *ActionsService) createWorkflowDispatchEvent(ctx context.Context, url string, event *CreateWorkflowDispatchEventRequest) (*Response, error) {
req, err := s.client.NewRequest("POST", url, event)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// EnableWorkflowByID enables a workflow and sets the state of the workflow to "active".
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflows#enable-a-workflow
func (s *ActionsService) EnableWorkflowByID(ctx context.Context, owner, repo string, workflowID int64) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/workflows/%v/enable", owner, repo, workflowID)
return s.doNewPutRequest(ctx, u)
}
// EnableWorkflowByFileName enables a workflow and sets the state of the workflow to "active".
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflows#enable-a-workflow
func (s *ActionsService) EnableWorkflowByFileName(ctx context.Context, owner, repo, workflowFileName string) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/workflows/%v/enable", owner, repo, workflowFileName)
return s.doNewPutRequest(ctx, u)
}
// DisableWorkflowByID disables a workflow and sets the state of the workflow to "disabled_manually".
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflows#disable-a-workflow
func (s *ActionsService) DisableWorkflowByID(ctx context.Context, owner, repo string, workflowID int64) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/workflows/%v/disable", owner, repo, workflowID)
return s.doNewPutRequest(ctx, u)
}
// DisableWorkflowByFileName disables a workflow and sets the state of the workflow to "disabled_manually".
//
// GitHub API docs: https://docs.github.com/en/rest/actions/workflows#disable-a-workflow
func (s *ActionsService) DisableWorkflowByFileName(ctx context.Context, owner, repo, workflowFileName string) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/actions/workflows/%v/disable", owner, repo, workflowFileName)
return s.doNewPutRequest(ctx, u)
}
func (s *ActionsService) doNewPutRequest(ctx context.Context, url string) (*Response, error) {
req, err := s.client.NewRequest("PUT", url, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}

View File

@@ -0,0 +1,73 @@
// Copyright 2013 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import "context"
// ActivityService handles communication with the activity related
// methods of the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/
type ActivityService service
// FeedLink represents a link to a related resource.
type FeedLink struct {
HRef *string `json:"href,omitempty"`
Type *string `json:"type,omitempty"`
}
// Feeds represents timeline resources in Atom format.
type Feeds struct {
TimelineURL *string `json:"timeline_url,omitempty"`
UserURL *string `json:"user_url,omitempty"`
CurrentUserPublicURL *string `json:"current_user_public_url,omitempty"`
CurrentUserURL *string `json:"current_user_url,omitempty"`
CurrentUserActorURL *string `json:"current_user_actor_url,omitempty"`
CurrentUserOrganizationURL *string `json:"current_user_organization_url,omitempty"`
CurrentUserOrganizationURLs []string `json:"current_user_organization_urls,omitempty"`
Links *FeedLinks `json:"_links,omitempty"`
}
// FeedLinks represents the links in a Feed.
type FeedLinks struct {
Timeline *FeedLink `json:"timeline,omitempty"`
User *FeedLink `json:"user,omitempty"`
CurrentUserPublic *FeedLink `json:"current_user_public,omitempty"`
CurrentUser *FeedLink `json:"current_user,omitempty"`
CurrentUserActor *FeedLink `json:"current_user_actor,omitempty"`
CurrentUserOrganization *FeedLink `json:"current_user_organization,omitempty"`
CurrentUserOrganizations []*FeedLink `json:"current_user_organizations,omitempty"`
}
// ListFeeds lists all the feeds available to the authenticated user.
//
// GitHub provides several timeline resources in Atom format:
//
// Timeline: The GitHub global public timeline
// User: The public timeline for any user, using URI template
// Current user public: The public timeline for the authenticated user
// Current user: The private timeline for the authenticated user
// Current user actor: The private timeline for activity created by the
// authenticated user
// Current user organizations: The private timeline for the organizations
// the authenticated user is a member of.
//
// Note: Private feeds are only returned when authenticating via Basic Auth
// since current feed URIs use the older, non revocable auth tokens.
func (s *ActivityService) ListFeeds(ctx context.Context) (*Feeds, *Response, error) {
req, err := s.client.NewRequest("GET", "feeds", nil)
if err != nil {
return nil, nil, err
}
f := &Feeds{}
resp, err := s.client.Do(ctx, req, f)
if err != nil {
return nil, resp, err
}
return f, resp, nil
}

View File

@@ -0,0 +1,217 @@
// Copyright 2013 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// ListEvents drinks from the firehose of all public events across GitHub.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/events#list-public-events
func (s *ActivityService) ListEvents(ctx context.Context, opts *ListOptions) ([]*Event, *Response, error) {
u, err := addOptions("events", opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var events []*Event
resp, err := s.client.Do(ctx, req, &events)
if err != nil {
return nil, resp, err
}
return events, resp, nil
}
// ListRepositoryEvents lists events for a repository.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/events#list-repository-events
func (s *ActivityService) ListRepositoryEvents(ctx context.Context, owner, repo string, opts *ListOptions) ([]*Event, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/events", owner, repo)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var events []*Event
resp, err := s.client.Do(ctx, req, &events)
if err != nil {
return nil, resp, err
}
return events, resp, nil
}
// ListIssueEventsForRepository lists issue events for a repository.
//
// GitHub API docs: https://docs.github.com/en/rest/issues/events#list-issue-events-for-a-repository
func (s *ActivityService) ListIssueEventsForRepository(ctx context.Context, owner, repo string, opts *ListOptions) ([]*IssueEvent, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/issues/events", owner, repo)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var events []*IssueEvent
resp, err := s.client.Do(ctx, req, &events)
if err != nil {
return nil, resp, err
}
return events, resp, nil
}
// ListEventsForRepoNetwork lists public events for a network of repositories.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/events#list-public-events-for-a-network-of-repositories
func (s *ActivityService) ListEventsForRepoNetwork(ctx context.Context, owner, repo string, opts *ListOptions) ([]*Event, *Response, error) {
u := fmt.Sprintf("networks/%v/%v/events", owner, repo)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var events []*Event
resp, err := s.client.Do(ctx, req, &events)
if err != nil {
return nil, resp, err
}
return events, resp, nil
}
// ListEventsForOrganization lists public events for an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/events#list-public-organization-events
func (s *ActivityService) ListEventsForOrganization(ctx context.Context, org string, opts *ListOptions) ([]*Event, *Response, error) {
u := fmt.Sprintf("orgs/%v/events", org)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var events []*Event
resp, err := s.client.Do(ctx, req, &events)
if err != nil {
return nil, resp, err
}
return events, resp, nil
}
// ListEventsPerformedByUser lists the events performed by a user. If publicOnly is
// true, only public events will be returned.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/events#list-events-for-the-authenticated-user
// GitHub API docs: https://docs.github.com/en/rest/activity/events#list-public-events-for-a-user
func (s *ActivityService) ListEventsPerformedByUser(ctx context.Context, user string, publicOnly bool, opts *ListOptions) ([]*Event, *Response, error) {
var u string
if publicOnly {
u = fmt.Sprintf("users/%v/events/public", user)
} else {
u = fmt.Sprintf("users/%v/events", user)
}
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var events []*Event
resp, err := s.client.Do(ctx, req, &events)
if err != nil {
return nil, resp, err
}
return events, resp, nil
}
// ListEventsReceivedByUser lists the events received by a user. If publicOnly is
// true, only public events will be returned.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/events#list-events-received-by-the-authenticated-user
// GitHub API docs: https://docs.github.com/en/rest/activity/events#list-public-events-received-by-a-user
func (s *ActivityService) ListEventsReceivedByUser(ctx context.Context, user string, publicOnly bool, opts *ListOptions) ([]*Event, *Response, error) {
var u string
if publicOnly {
u = fmt.Sprintf("users/%v/received_events/public", user)
} else {
u = fmt.Sprintf("users/%v/received_events", user)
}
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var events []*Event
resp, err := s.client.Do(ctx, req, &events)
if err != nil {
return nil, resp, err
}
return events, resp, nil
}
// ListUserEventsForOrganization provides the users organization dashboard. You
// must be authenticated as the user to view this.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/events#list-organization-events-for-the-authenticated-user
func (s *ActivityService) ListUserEventsForOrganization(ctx context.Context, org, user string, opts *ListOptions) ([]*Event, *Response, error) {
u := fmt.Sprintf("users/%v/events/orgs/%v", user, org)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var events []*Event
resp, err := s.client.Do(ctx, req, &events)
if err != nil {
return nil, resp, err
}
return events, resp, nil
}

View File

@@ -0,0 +1,223 @@
// Copyright 2014 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
"time"
)
// Notification identifies a GitHub notification for a user.
type Notification struct {
ID *string `json:"id,omitempty"`
Repository *Repository `json:"repository,omitempty"`
Subject *NotificationSubject `json:"subject,omitempty"`
// Reason identifies the event that triggered the notification.
//
// GitHub API docs: https://docs.github.com/en/rest/activity#notification-reasons
Reason *string `json:"reason,omitempty"`
Unread *bool `json:"unread,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
LastReadAt *time.Time `json:"last_read_at,omitempty"`
URL *string `json:"url,omitempty"`
}
// NotificationSubject identifies the subject of a notification.
type NotificationSubject struct {
Title *string `json:"title,omitempty"`
URL *string `json:"url,omitempty"`
LatestCommentURL *string `json:"latest_comment_url,omitempty"`
Type *string `json:"type,omitempty"`
}
// NotificationListOptions specifies the optional parameters to the
// ActivityService.ListNotifications method.
type NotificationListOptions struct {
All bool `url:"all,omitempty"`
Participating bool `url:"participating,omitempty"`
Since time.Time `url:"since,omitempty"`
Before time.Time `url:"before,omitempty"`
ListOptions
}
// ListNotifications lists all notifications for the authenticated user.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/notifications#list-notifications-for-the-authenticated-user
func (s *ActivityService) ListNotifications(ctx context.Context, opts *NotificationListOptions) ([]*Notification, *Response, error) {
u := "notifications"
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var notifications []*Notification
resp, err := s.client.Do(ctx, req, &notifications)
if err != nil {
return nil, resp, err
}
return notifications, resp, nil
}
// ListRepositoryNotifications lists all notifications in a given repository
// for the authenticated user.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/notifications#list-repository-notifications-for-the-authenticated-user
func (s *ActivityService) ListRepositoryNotifications(ctx context.Context, owner, repo string, opts *NotificationListOptions) ([]*Notification, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/notifications", owner, repo)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var notifications []*Notification
resp, err := s.client.Do(ctx, req, &notifications)
if err != nil {
return nil, resp, err
}
return notifications, resp, nil
}
type markReadOptions struct {
LastReadAt time.Time `json:"last_read_at,omitempty"`
}
// MarkNotificationsRead marks all notifications up to lastRead as read.
//
// GitHub API docs: https://docs.github.com/en/rest/activity#mark-as-read
func (s *ActivityService) MarkNotificationsRead(ctx context.Context, lastRead time.Time) (*Response, error) {
opts := &markReadOptions{
LastReadAt: lastRead,
}
req, err := s.client.NewRequest("PUT", "notifications", opts)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// MarkRepositoryNotificationsRead marks all notifications up to lastRead in
// the specified repository as read.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/notifications#mark-repository-notifications-as-read
func (s *ActivityService) MarkRepositoryNotificationsRead(ctx context.Context, owner, repo string, lastRead time.Time) (*Response, error) {
opts := &markReadOptions{
LastReadAt: lastRead,
}
u := fmt.Sprintf("repos/%v/%v/notifications", owner, repo)
req, err := s.client.NewRequest("PUT", u, opts)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// GetThread gets the specified notification thread.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/notifications#get-a-thread
func (s *ActivityService) GetThread(ctx context.Context, id string) (*Notification, *Response, error) {
u := fmt.Sprintf("notifications/threads/%v", id)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
notification := new(Notification)
resp, err := s.client.Do(ctx, req, notification)
if err != nil {
return nil, resp, err
}
return notification, resp, nil
}
// MarkThreadRead marks the specified thread as read.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/notifications#mark-a-thread-as-read
func (s *ActivityService) MarkThreadRead(ctx context.Context, id string) (*Response, error) {
u := fmt.Sprintf("notifications/threads/%v", id)
req, err := s.client.NewRequest("PATCH", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// GetThreadSubscription checks to see if the authenticated user is subscribed
// to a thread.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/notifications#get-a-thread-subscription-for-the-authenticated-user
func (s *ActivityService) GetThreadSubscription(ctx context.Context, id string) (*Subscription, *Response, error) {
u := fmt.Sprintf("notifications/threads/%v/subscription", id)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
sub := new(Subscription)
resp, err := s.client.Do(ctx, req, sub)
if err != nil {
return nil, resp, err
}
return sub, resp, nil
}
// SetThreadSubscription sets the subscription for the specified thread for the
// authenticated user.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/notifications#set-a-thread-subscription
func (s *ActivityService) SetThreadSubscription(ctx context.Context, id string, subscription *Subscription) (*Subscription, *Response, error) {
u := fmt.Sprintf("notifications/threads/%v/subscription", id)
req, err := s.client.NewRequest("PUT", u, subscription)
if err != nil {
return nil, nil, err
}
sub := new(Subscription)
resp, err := s.client.Do(ctx, req, sub)
if err != nil {
return nil, resp, err
}
return sub, resp, nil
}
// DeleteThreadSubscription deletes the subscription for the specified thread
// for the authenticated user.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/notifications#delete-a-thread-subscription
func (s *ActivityService) DeleteThreadSubscription(ctx context.Context, id string) (*Response, error) {
u := fmt.Sprintf("notifications/threads/%v/subscription", id)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}

View File

@@ -0,0 +1,141 @@
// Copyright 2013 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
"strings"
)
// StarredRepository is returned by ListStarred.
type StarredRepository struct {
StarredAt *Timestamp `json:"starred_at,omitempty"`
Repository *Repository `json:"repo,omitempty"`
}
// Stargazer represents a user that has starred a repository.
type Stargazer struct {
StarredAt *Timestamp `json:"starred_at,omitempty"`
User *User `json:"user,omitempty"`
}
// ListStargazers lists people who have starred the specified repo.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/starring#list-stargazers
func (s *ActivityService) ListStargazers(ctx context.Context, owner, repo string, opts *ListOptions) ([]*Stargazer, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/stargazers", owner, repo)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeStarringPreview)
var stargazers []*Stargazer
resp, err := s.client.Do(ctx, req, &stargazers)
if err != nil {
return nil, resp, err
}
return stargazers, resp, nil
}
// ActivityListStarredOptions specifies the optional parameters to the
// ActivityService.ListStarred method.
type ActivityListStarredOptions struct {
// How to sort the repository list. Possible values are: created, updated,
// pushed, full_name. Default is "full_name".
Sort string `url:"sort,omitempty"`
// Direction in which to sort repositories. Possible values are: asc, desc.
// Default is "asc" when sort is "full_name", otherwise default is "desc".
Direction string `url:"direction,omitempty"`
ListOptions
}
// ListStarred lists all the repos starred by a user. Passing the empty string
// will list the starred repositories for the authenticated user.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/starring#list-repositories-starred-by-the-authenticated-user
// GitHub API docs: https://docs.github.com/en/rest/activity/starring#list-repositories-starred-by-a-user
func (s *ActivityService) ListStarred(ctx context.Context, user string, opts *ActivityListStarredOptions) ([]*StarredRepository, *Response, error) {
var u string
if user != "" {
u = fmt.Sprintf("users/%v/starred", user)
} else {
u = "user/starred"
}
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when APIs fully launch
acceptHeaders := []string{mediaTypeStarringPreview, mediaTypeTopicsPreview}
req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
var repos []*StarredRepository
resp, err := s.client.Do(ctx, req, &repos)
if err != nil {
return nil, resp, err
}
return repos, resp, nil
}
// IsStarred checks if a repository is starred by authenticated user.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/starring#check-if-a-repository-is-starred-by-the-authenticated-user
func (s *ActivityService) IsStarred(ctx context.Context, owner, repo string) (bool, *Response, error) {
u := fmt.Sprintf("user/starred/%v/%v", owner, repo)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return false, nil, err
}
resp, err := s.client.Do(ctx, req, nil)
starred, err := parseBoolResponse(err)
return starred, resp, err
}
// Star a repository as the authenticated user.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/starring#star-a-repository-for-the-authenticated-user
func (s *ActivityService) Star(ctx context.Context, owner, repo string) (*Response, error) {
u := fmt.Sprintf("user/starred/%v/%v", owner, repo)
req, err := s.client.NewRequest("PUT", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// Unstar a repository as the authenticated user.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/starring#unstar-a-repository-for-the-authenticated-user
func (s *ActivityService) Unstar(ctx context.Context, owner, repo string) (*Response, error) {
u := fmt.Sprintf("user/starred/%v/%v", owner, repo)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}

View File

@@ -0,0 +1,147 @@
// Copyright 2014 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// Subscription identifies a repository or thread subscription.
type Subscription struct {
Subscribed *bool `json:"subscribed,omitempty"`
Ignored *bool `json:"ignored,omitempty"`
Reason *string `json:"reason,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
URL *string `json:"url,omitempty"`
// only populated for repository subscriptions
RepositoryURL *string `json:"repository_url,omitempty"`
// only populated for thread subscriptions
ThreadURL *string `json:"thread_url,omitempty"`
}
// ListWatchers lists watchers of a particular repo.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/watching#list-watchers
func (s *ActivityService) ListWatchers(ctx context.Context, owner, repo string, opts *ListOptions) ([]*User, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/subscribers", owner, repo)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var watchers []*User
resp, err := s.client.Do(ctx, req, &watchers)
if err != nil {
return nil, resp, err
}
return watchers, resp, nil
}
// ListWatched lists the repositories the specified user is watching. Passing
// the empty string will fetch watched repos for the authenticated user.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/watching#list-repositories-watched-by-the-authenticated-user
// GitHub API docs: https://docs.github.com/en/rest/activity/watching#list-repositories-watched-by-a-user
func (s *ActivityService) ListWatched(ctx context.Context, user string, opts *ListOptions) ([]*Repository, *Response, error) {
var u string
if user != "" {
u = fmt.Sprintf("users/%v/subscriptions", user)
} else {
u = "user/subscriptions"
}
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var watched []*Repository
resp, err := s.client.Do(ctx, req, &watched)
if err != nil {
return nil, resp, err
}
return watched, resp, nil
}
// GetRepositorySubscription returns the subscription for the specified
// repository for the authenticated user. If the authenticated user is not
// watching the repository, a nil Subscription is returned.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/watching#get-a-repository-subscription
func (s *ActivityService) GetRepositorySubscription(ctx context.Context, owner, repo string) (*Subscription, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/subscription", owner, repo)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
sub := new(Subscription)
resp, err := s.client.Do(ctx, req, sub)
if err != nil {
// if it's just a 404, don't return that as an error
_, err = parseBoolResponse(err)
return nil, resp, err
}
return sub, resp, nil
}
// SetRepositorySubscription sets the subscription for the specified repository
// for the authenticated user.
//
// To watch a repository, set subscription.Subscribed to true.
// To ignore notifications made within a repository, set subscription.Ignored to true.
// To stop watching a repository, use DeleteRepositorySubscription.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/watching#set-a-repository-subscription
func (s *ActivityService) SetRepositorySubscription(ctx context.Context, owner, repo string, subscription *Subscription) (*Subscription, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/subscription", owner, repo)
req, err := s.client.NewRequest("PUT", u, subscription)
if err != nil {
return nil, nil, err
}
sub := new(Subscription)
resp, err := s.client.Do(ctx, req, sub)
if err != nil {
return nil, resp, err
}
return sub, resp, nil
}
// DeleteRepositorySubscription deletes the subscription for the specified
// repository for the authenticated user.
//
// This is used to stop watching a repository. To control whether or not to
// receive notifications from a repository, use SetRepositorySubscription.
//
// GitHub API docs: https://docs.github.com/en/rest/activity/watching#delete-a-repository-subscription
func (s *ActivityService) DeleteRepositorySubscription(ctx context.Context, owner, repo string) (*Response, error) {
u := fmt.Sprintf("repos/%s/%s/subscription", owner, repo)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}

119
vendor/github.com/google/go-github/v48/github/admin.go generated vendored Normal file
View File

@@ -0,0 +1,119 @@
// Copyright 2016 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// AdminService handles communication with the admin related methods of the
// GitHub API. These API routes are normally only accessible for GitHub
// Enterprise installations.
//
// GitHub API docs: https://docs.github.com/en/rest/enterprise-admin
type AdminService service
// TeamLDAPMapping represents the mapping between a GitHub team and an LDAP group.
type TeamLDAPMapping struct {
ID *int64 `json:"id,omitempty"`
LDAPDN *string `json:"ldap_dn,omitempty"`
URL *string `json:"url,omitempty"`
Name *string `json:"name,omitempty"`
Slug *string `json:"slug,omitempty"`
Description *string `json:"description,omitempty"`
Privacy *string `json:"privacy,omitempty"`
Permission *string `json:"permission,omitempty"`
MembersURL *string `json:"members_url,omitempty"`
RepositoriesURL *string `json:"repositories_url,omitempty"`
}
func (m TeamLDAPMapping) String() string {
return Stringify(m)
}
// UserLDAPMapping represents the mapping between a GitHub user and an LDAP user.
type UserLDAPMapping struct {
ID *int64 `json:"id,omitempty"`
LDAPDN *string `json:"ldap_dn,omitempty"`
Login *string `json:"login,omitempty"`
AvatarURL *string `json:"avatar_url,omitempty"`
GravatarID *string `json:"gravatar_id,omitempty"`
Type *string `json:"type,omitempty"`
SiteAdmin *bool `json:"site_admin,omitempty"`
URL *string `json:"url,omitempty"`
EventsURL *string `json:"events_url,omitempty"`
FollowingURL *string `json:"following_url,omitempty"`
FollowersURL *string `json:"followers_url,omitempty"`
GistsURL *string `json:"gists_url,omitempty"`
OrganizationsURL *string `json:"organizations_url,omitempty"`
ReceivedEventsURL *string `json:"received_events_url,omitempty"`
ReposURL *string `json:"repos_url,omitempty"`
StarredURL *string `json:"starred_url,omitempty"`
SubscriptionsURL *string `json:"subscriptions_url,omitempty"`
}
func (m UserLDAPMapping) String() string {
return Stringify(m)
}
// Enterprise represents the GitHub enterprise profile.
type Enterprise struct {
ID *int `json:"id,omitempty"`
Slug *string `json:"slug,omitempty"`
Name *string `json:"name,omitempty"`
NodeID *string `json:"node_id,omitempty"`
AvatarURL *string `json:"avatar_url,omitempty"`
Description *string `json:"description,omitempty"`
WebsiteURL *string `json:"website_url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
}
func (m Enterprise) String() string {
return Stringify(m)
}
// UpdateUserLDAPMapping updates the mapping between a GitHub user and an LDAP user.
//
// GitHub API docs: https://docs.github.com/en/enterprise-server/rest/enterprise-admin/ldap#update-ldap-mapping-for-a-user
func (s *AdminService) UpdateUserLDAPMapping(ctx context.Context, user string, mapping *UserLDAPMapping) (*UserLDAPMapping, *Response, error) {
u := fmt.Sprintf("admin/ldap/users/%v/mapping", user)
req, err := s.client.NewRequest("PATCH", u, mapping)
if err != nil {
return nil, nil, err
}
m := new(UserLDAPMapping)
resp, err := s.client.Do(ctx, req, m)
if err != nil {
return nil, resp, err
}
return m, resp, nil
}
// UpdateTeamLDAPMapping updates the mapping between a GitHub team and an LDAP group.
//
// GitHub API docs: https://docs.github.com/en/rest/enterprise/ldap/#update-ldap-mapping-for-a-team
func (s *AdminService) UpdateTeamLDAPMapping(ctx context.Context, team int64, mapping *TeamLDAPMapping) (*TeamLDAPMapping, *Response, error) {
u := fmt.Sprintf("admin/ldap/teams/%v/mapping", team)
req, err := s.client.NewRequest("PATCH", u, mapping)
if err != nil {
return nil, nil, err
}
m := new(TeamLDAPMapping)
resp, err := s.client.Do(ctx, req, m)
if err != nil {
return nil, resp, err
}
return m, resp, nil
}

View File

@@ -0,0 +1,89 @@
// Copyright 2019 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// createOrgRequest is a subset of Organization and is used internally
// by CreateOrg to pass only the known fields for the endpoint.
type createOrgRequest struct {
Login *string `json:"login,omitempty"`
Admin *string `json:"admin,omitempty"`
}
// CreateOrg creates a new organization in GitHub Enterprise.
//
// Note that only a subset of the org fields are used and org must
// not be nil.
//
// GitHub Enterprise API docs: https://developer.github.com/enterprise/v3/enterprise-admin/orgs/#create-an-organization
func (s *AdminService) CreateOrg(ctx context.Context, org *Organization, admin string) (*Organization, *Response, error) {
u := "admin/organizations"
orgReq := &createOrgRequest{
Login: org.Login,
Admin: &admin,
}
req, err := s.client.NewRequest("POST", u, orgReq)
if err != nil {
return nil, nil, err
}
o := new(Organization)
resp, err := s.client.Do(ctx, req, o)
if err != nil {
return nil, resp, err
}
return o, resp, nil
}
// renameOrgRequest is a subset of Organization and is used internally
// by RenameOrg and RenameOrgByName to pass only the known fields for the endpoint.
type renameOrgRequest struct {
Login *string `json:"login,omitempty"`
}
// RenameOrgResponse is the response given when renaming an Organization.
type RenameOrgResponse struct {
Message *string `json:"message,omitempty"`
URL *string `json:"url,omitempty"`
}
// RenameOrg renames an organization in GitHub Enterprise.
//
// GitHub Enterprise API docs: https://developer.github.com/enterprise/v3/enterprise-admin/orgs/#rename-an-organization
func (s *AdminService) RenameOrg(ctx context.Context, org *Organization, newName string) (*RenameOrgResponse, *Response, error) {
return s.RenameOrgByName(ctx, *org.Login, newName)
}
// RenameOrgByName renames an organization in GitHub Enterprise using its current name.
//
// GitHub Enterprise API docs: https://developer.github.com/enterprise/v3/enterprise-admin/orgs/#rename-an-organization
func (s *AdminService) RenameOrgByName(ctx context.Context, org, newName string) (*RenameOrgResponse, *Response, error) {
u := fmt.Sprintf("admin/organizations/%v", org)
orgReq := &renameOrgRequest{
Login: &newName,
}
req, err := s.client.NewRequest("PATCH", u, orgReq)
if err != nil {
return nil, nil, err
}
o := new(RenameOrgResponse)
resp, err := s.client.Do(ctx, req, o)
if err != nil {
return nil, resp, err
}
return o, resp, nil
}

View File

@@ -0,0 +1,171 @@
// Copyright 2017 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// AdminStats represents a variety of stats of a GitHub Enterprise
// installation.
type AdminStats struct {
Issues *IssueStats `json:"issues,omitempty"`
Hooks *HookStats `json:"hooks,omitempty"`
Milestones *MilestoneStats `json:"milestones,omitempty"`
Orgs *OrgStats `json:"orgs,omitempty"`
Comments *CommentStats `json:"comments,omitempty"`
Pages *PageStats `json:"pages,omitempty"`
Users *UserStats `json:"users,omitempty"`
Gists *GistStats `json:"gists,omitempty"`
Pulls *PullStats `json:"pulls,omitempty"`
Repos *RepoStats `json:"repos,omitempty"`
}
func (s AdminStats) String() string {
return Stringify(s)
}
// IssueStats represents the number of total, open and closed issues.
type IssueStats struct {
TotalIssues *int `json:"total_issues,omitempty"`
OpenIssues *int `json:"open_issues,omitempty"`
ClosedIssues *int `json:"closed_issues,omitempty"`
}
func (s IssueStats) String() string {
return Stringify(s)
}
// HookStats represents the number of total, active and inactive hooks.
type HookStats struct {
TotalHooks *int `json:"total_hooks,omitempty"`
ActiveHooks *int `json:"active_hooks,omitempty"`
InactiveHooks *int `json:"inactive_hooks,omitempty"`
}
func (s HookStats) String() string {
return Stringify(s)
}
// MilestoneStats represents the number of total, open and close milestones.
type MilestoneStats struct {
TotalMilestones *int `json:"total_milestones,omitempty"`
OpenMilestones *int `json:"open_milestones,omitempty"`
ClosedMilestones *int `json:"closed_milestones,omitempty"`
}
func (s MilestoneStats) String() string {
return Stringify(s)
}
// OrgStats represents the number of total, disabled organizations and the team
// and team member count.
type OrgStats struct {
TotalOrgs *int `json:"total_orgs,omitempty"`
DisabledOrgs *int `json:"disabled_orgs,omitempty"`
TotalTeams *int `json:"total_teams,omitempty"`
TotalTeamMembers *int `json:"total_team_members,omitempty"`
}
func (s OrgStats) String() string {
return Stringify(s)
}
// CommentStats represents the number of total comments on commits, gists, issues
// and pull requests.
type CommentStats struct {
TotalCommitComments *int `json:"total_commit_comments,omitempty"`
TotalGistComments *int `json:"total_gist_comments,omitempty"`
TotalIssueComments *int `json:"total_issue_comments,omitempty"`
TotalPullRequestComments *int `json:"total_pull_request_comments,omitempty"`
}
func (s CommentStats) String() string {
return Stringify(s)
}
// PageStats represents the total number of github pages.
type PageStats struct {
TotalPages *int `json:"total_pages,omitempty"`
}
func (s PageStats) String() string {
return Stringify(s)
}
// UserStats represents the number of total, admin and suspended users.
type UserStats struct {
TotalUsers *int `json:"total_users,omitempty"`
AdminUsers *int `json:"admin_users,omitempty"`
SuspendedUsers *int `json:"suspended_users,omitempty"`
}
func (s UserStats) String() string {
return Stringify(s)
}
// GistStats represents the number of total, private and public gists.
type GistStats struct {
TotalGists *int `json:"total_gists,omitempty"`
PrivateGists *int `json:"private_gists,omitempty"`
PublicGists *int `json:"public_gists,omitempty"`
}
func (s GistStats) String() string {
return Stringify(s)
}
// PullStats represents the number of total, merged, mergable and unmergeable
// pull-requests.
type PullStats struct {
TotalPulls *int `json:"total_pulls,omitempty"`
MergedPulls *int `json:"merged_pulls,omitempty"`
MergablePulls *int `json:"mergeable_pulls,omitempty"`
UnmergablePulls *int `json:"unmergeable_pulls,omitempty"`
}
func (s PullStats) String() string {
return Stringify(s)
}
// RepoStats represents the number of total, root, fork, organization repositories
// together with the total number of pushes and wikis.
type RepoStats struct {
TotalRepos *int `json:"total_repos,omitempty"`
RootRepos *int `json:"root_repos,omitempty"`
ForkRepos *int `json:"fork_repos,omitempty"`
OrgRepos *int `json:"org_repos,omitempty"`
TotalPushes *int `json:"total_pushes,omitempty"`
TotalWikis *int `json:"total_wikis,omitempty"`
}
func (s RepoStats) String() string {
return Stringify(s)
}
// GetAdminStats returns a variety of metrics about a GitHub Enterprise
// installation.
//
// Please note that this is only available to site administrators,
// otherwise it will error with a 404 not found (instead of 401 or 403).
//
// GitHub API docs: https://docs.github.com/en/rest/enterprise-admin/admin_stats/
func (s *AdminService) GetAdminStats(ctx context.Context) (*AdminStats, *Response, error) {
u := fmt.Sprintf("enterprise/stats/all")
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
m := new(AdminStats)
resp, err := s.client.Do(ctx, req, m)
if err != nil {
return nil, resp, err
}
return m, resp, nil
}

View File

@@ -0,0 +1,133 @@
// Copyright 2019 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// createUserRequest is a subset of User and is used internally
// by CreateUser to pass only the known fields for the endpoint.
type createUserRequest struct {
Login *string `json:"login,omitempty"`
Email *string `json:"email,omitempty"`
}
// CreateUser creates a new user in GitHub Enterprise.
//
// GitHub Enterprise API docs: https://developer.github.com/enterprise/v3/enterprise-admin/users/#create-a-new-user
func (s *AdminService) CreateUser(ctx context.Context, login, email string) (*User, *Response, error) {
u := "admin/users"
userReq := &createUserRequest{
Login: &login,
Email: &email,
}
req, err := s.client.NewRequest("POST", u, userReq)
if err != nil {
return nil, nil, err
}
var user User
resp, err := s.client.Do(ctx, req, &user)
if err != nil {
return nil, resp, err
}
return &user, resp, nil
}
// DeleteUser deletes a user in GitHub Enterprise.
//
// GitHub Enterprise API docs: https://developer.github.com/enterprise/v3/enterprise-admin/users/#delete-a-user
func (s *AdminService) DeleteUser(ctx context.Context, username string) (*Response, error) {
u := "admin/users/" + username
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(ctx, req, nil)
if err != nil {
return resp, err
}
return resp, nil
}
// ImpersonateUserOptions represents the scoping for the OAuth token.
type ImpersonateUserOptions struct {
Scopes []string `json:"scopes,omitempty"`
}
// OAuthAPP represents the GitHub Site Administrator OAuth app.
type OAuthAPP struct {
URL *string `json:"url,omitempty"`
Name *string `json:"name,omitempty"`
ClientID *string `json:"client_id,omitempty"`
}
func (s OAuthAPP) String() string {
return Stringify(s)
}
// UserAuthorization represents the impersonation response.
type UserAuthorization struct {
ID *int64 `json:"id,omitempty"`
URL *string `json:"url,omitempty"`
Scopes []string `json:"scopes,omitempty"`
Token *string `json:"token,omitempty"`
TokenLastEight *string `json:"token_last_eight,omitempty"`
HashedToken *string `json:"hashed_token,omitempty"`
App *OAuthAPP `json:"app,omitempty"`
Note *string `json:"note,omitempty"`
NoteURL *string `json:"note_url,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
Fingerprint *string `json:"fingerprint,omitempty"`
}
// CreateUserImpersonation creates an impersonation OAuth token.
//
// GitHub Enterprise API docs: https://developer.github.com/enterprise/v3/enterprise-admin/users/#create-an-impersonation-oauth-token
func (s *AdminService) CreateUserImpersonation(ctx context.Context, username string, opts *ImpersonateUserOptions) (*UserAuthorization, *Response, error) {
u := fmt.Sprintf("admin/users/%s/authorizations", username)
req, err := s.client.NewRequest("POST", u, opts)
if err != nil {
return nil, nil, err
}
a := new(UserAuthorization)
resp, err := s.client.Do(ctx, req, a)
if err != nil {
return nil, resp, err
}
return a, resp, nil
}
// DeleteUserImpersonation deletes an impersonation OAuth token.
//
// GitHub Enterprise API docs: https://developer.github.com/enterprise/v3/enterprise-admin/users/#delete-an-impersonation-oauth-token
func (s *AdminService) DeleteUserImpersonation(ctx context.Context, username string) (*Response, error) {
u := fmt.Sprintf("admin/users/%s/authorizations", username)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(ctx, req, nil)
if err != nil {
return resp, err
}
return resp, nil
}

359
vendor/github.com/google/go-github/v48/github/apps.go generated vendored Normal file
View File

@@ -0,0 +1,359 @@
// Copyright 2016 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
"time"
)
// AppsService provides access to the installation related functions
// in the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/
type AppsService service
// App represents a GitHub App.
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 *Timestamp `json:"created_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
Permissions *InstallationPermissions `json:"permissions,omitempty"`
Events []string `json:"events,omitempty"`
}
// InstallationToken represents an installation token.
type InstallationToken struct {
Token *string `json:"token,omitempty"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
Permissions *InstallationPermissions `json:"permissions,omitempty"`
Repositories []*Repository `json:"repositories,omitempty"`
}
// InstallationTokenOptions allow restricting a token's access to specific repositories.
type InstallationTokenOptions struct {
// The IDs of the repositories that the installation token can access.
// Providing repository IDs restricts the access of an installation token to specific repositories.
RepositoryIDs []int64 `json:"repository_ids,omitempty"`
// The names of the repositories that the installation token can access.
// Providing repository names restricts the access of an installation token to specific repositories.
Repositories []string `json:"repositories,omitempty"`
// The permissions granted to the access token.
// The permissions object includes the permission names and their access type.
Permissions *InstallationPermissions `json:"permissions,omitempty"`
}
// InstallationPermissions lists the repository and organization permissions for an installation.
//
// Permission names taken from:
//
// https://docs.github.com/en/enterprise-server@3.0/rest/apps#create-an-installation-access-token-for-an-app
// https://docs.github.com/en/rest/apps#create-an-installation-access-token-for-an-app
type InstallationPermissions struct {
Actions *string `json:"actions,omitempty"`
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"`
Environments *string `json:"environments,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"`
OrganizationCustomRoles *string `json:"organization_custom_roles,omitempty"`
OrganizationHooks *string `json:"organization_hooks,omitempty"`
OrganizationPackages *string `json:"organization_packages,omitempty"`
OrganizationPlan *string `json:"organization_plan,omitempty"`
OrganizationPreReceiveHooks *string `json:"organization_pre_receive_hooks,omitempty"`
OrganizationProjects *string `json:"organization_projects,omitempty"`
OrganizationSecrets *string `json:"organization_secrets,omitempty"`
OrganizationSelfHostedRunners *string `json:"organization_self_hosted_runners,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"`
Secrets *string `json:"secrets,omitempty"`
SecretScanningAlerts *string `json:"secret_scanning_alerts,omitempty"`
SecurityEvents *string `json:"security_events,omitempty"`
SingleFile *string `json:"single_file,omitempty"`
Statuses *string `json:"statuses,omitempty"`
TeamDiscussions *string `json:"team_discussions,omitempty"`
VulnerabilityAlerts *string `json:"vulnerability_alerts,omitempty"`
Workflows *string `json:"workflows,omitempty"`
}
// Installation represents a GitHub Apps installation.
type Installation struct {
ID *int64 `json:"id,omitempty"`
NodeID *string `json:"node_id,omitempty"`
AppID *int64 `json:"app_id,omitempty"`
AppSlug *string `json:"app_slug,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"`
SingleFilePaths []string `json:"single_file_paths,omitempty"`
Permissions *InstallationPermissions `json:"permissions,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
HasMultipleSingleFiles *bool `json:"has_multiple_single_files,omitempty"`
SuspendedBy *User `json:"suspended_by,omitempty"`
SuspendedAt *Timestamp `json:"suspended_at,omitempty"`
}
// Attachment represents a GitHub Apps attachment.
type Attachment struct {
ID *int64 `json:"id,omitempty"`
Title *string `json:"title,omitempty"`
Body *string `json:"body,omitempty"`
}
// ContentReference represents a reference to a URL in an issue or pull request.
type ContentReference struct {
ID *int64 `json:"id,omitempty"`
NodeID *string `json:"node_id,omitempty"`
Reference *string `json:"reference,omitempty"`
}
func (i Installation) String() string {
return Stringify(i)
}
// Get a single GitHub App. Passing the empty string will get
// the authenticated GitHub App.
//
// Note: appSlug is just the URL-friendly name of your GitHub App.
// You can find this on the settings page for your GitHub App
// (e.g., https://github.com/settings/apps/:app_slug).
//
// GitHub API docs: https://docs.github.com/en/rest/apps/apps#get-the-authenticated-app
// GitHub API docs: https://docs.github.com/en/rest/apps/apps#get-an-app
func (s *AppsService) Get(ctx context.Context, appSlug string) (*App, *Response, error) {
var u string
if appSlug != "" {
u = fmt.Sprintf("apps/%v", appSlug)
} else {
u = "app"
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
app := new(App)
resp, err := s.client.Do(ctx, req, app)
if err != nil {
return nil, resp, err
}
return app, resp, nil
}
// ListInstallations lists the installations that the current GitHub App has.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/apps#list-installations-for-the-authenticated-app
func (s *AppsService) ListInstallations(ctx context.Context, opts *ListOptions) ([]*Installation, *Response, error) {
u, err := addOptions("app/installations", opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var i []*Installation
resp, err := s.client.Do(ctx, req, &i)
if err != nil {
return nil, resp, err
}
return i, resp, nil
}
// GetInstallation returns the specified installation.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/apps#get-an-installation-for-the-authenticated-app
func (s *AppsService) GetInstallation(ctx context.Context, id int64) (*Installation, *Response, error) {
return s.getInstallation(ctx, fmt.Sprintf("app/installations/%v", id))
}
// ListUserInstallations lists installations that are accessible to the authenticated user.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/installations#list-app-installations-accessible-to-the-user-access-token
func (s *AppsService) ListUserInstallations(ctx context.Context, opts *ListOptions) ([]*Installation, *Response, error) {
u, err := addOptions("user/installations", opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var i struct {
Installations []*Installation `json:"installations"`
}
resp, err := s.client.Do(ctx, req, &i)
if err != nil {
return nil, resp, err
}
return i.Installations, resp, nil
}
// SuspendInstallation suspends the specified installation.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/apps#suspend-an-app-installation
func (s *AppsService) SuspendInstallation(ctx context.Context, id int64) (*Response, error) {
u := fmt.Sprintf("app/installations/%v/suspended", id)
req, err := s.client.NewRequest("PUT", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// UnsuspendInstallation unsuspends the specified installation.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/apps#unsuspend-an-app-installation
func (s *AppsService) UnsuspendInstallation(ctx context.Context, id int64) (*Response, error) {
u := fmt.Sprintf("app/installations/%v/suspended", id)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// DeleteInstallation deletes the specified installation.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/apps#delete-an-installation-for-the-authenticated-app
func (s *AppsService) DeleteInstallation(ctx context.Context, id int64) (*Response, error) {
u := fmt.Sprintf("app/installations/%v", id)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// CreateInstallationToken creates a new installation token.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/apps#create-an-installation-access-token-for-an-app
func (s *AppsService) CreateInstallationToken(ctx context.Context, id int64, opts *InstallationTokenOptions) (*InstallationToken, *Response, error) {
u := fmt.Sprintf("app/installations/%v/access_tokens", id)
req, err := s.client.NewRequest("POST", u, opts)
if err != nil {
return nil, nil, err
}
t := new(InstallationToken)
resp, err := s.client.Do(ctx, req, t)
if err != nil {
return nil, resp, err
}
return t, resp, nil
}
// CreateAttachment creates a new attachment on user comment containing a url.
//
// TODO: Find GitHub API docs.
func (s *AppsService) CreateAttachment(ctx context.Context, contentReferenceID int64, title, body string) (*Attachment, *Response, error) {
u := fmt.Sprintf("content_references/%v/attachments", contentReferenceID)
payload := &Attachment{Title: String(title), Body: String(body)}
req, err := s.client.NewRequest("POST", u, payload)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept headers when APIs fully launch.
req.Header.Set("Accept", mediaTypeContentAttachmentsPreview)
m := &Attachment{}
resp, err := s.client.Do(ctx, req, m)
if err != nil {
return nil, resp, err
}
return m, resp, nil
}
// FindOrganizationInstallation finds the organization's installation information.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/apps#get-an-organization-installation-for-the-authenticated-app
func (s *AppsService) FindOrganizationInstallation(ctx context.Context, org string) (*Installation, *Response, error) {
return s.getInstallation(ctx, fmt.Sprintf("orgs/%v/installation", org))
}
// FindRepositoryInstallation finds the repository's installation information.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/apps#get-a-repository-installation-for-the-authenticated-app
func (s *AppsService) FindRepositoryInstallation(ctx context.Context, owner, repo string) (*Installation, *Response, error) {
return s.getInstallation(ctx, fmt.Sprintf("repos/%v/%v/installation", owner, repo))
}
// FindRepositoryInstallationByID finds the repository's installation information.
//
// Note: FindRepositoryInstallationByID uses the undocumented GitHub API endpoint /repositories/:id/installation.
func (s *AppsService) FindRepositoryInstallationByID(ctx context.Context, id int64) (*Installation, *Response, error) {
return s.getInstallation(ctx, fmt.Sprintf("repositories/%d/installation", id))
}
// FindUserInstallation finds the user's installation information.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/apps#get-a-user-installation-for-the-authenticated-app
func (s *AppsService) FindUserInstallation(ctx context.Context, user string) (*Installation, *Response, error) {
return s.getInstallation(ctx, fmt.Sprintf("users/%v/installation", user))
}
func (s *AppsService) getInstallation(ctx context.Context, url string) (*Installation, *Response, error) {
req, err := s.client.NewRequest("GET", url, nil)
if err != nil {
return nil, nil, err
}
i := new(Installation)
resp, err := s.client.Do(ctx, req, i)
if err != nil {
return nil, resp, err
}
return i, resp, nil
}

View File

@@ -0,0 +1,48 @@
// Copyright 2021 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
)
// GetHookConfig returns the webhook configuration for a GitHub App.
// The underlying transport must be authenticated as an app.
//
// GitHub API docs: https://docs.github.com/en/rest/apps#get-a-webhook-configuration-for-an-app
func (s *AppsService) GetHookConfig(ctx context.Context) (*HookConfig, *Response, error) {
req, err := s.client.NewRequest("GET", "app/hook/config", nil)
if err != nil {
return nil, nil, err
}
config := new(HookConfig)
resp, err := s.client.Do(ctx, req, &config)
if err != nil {
return nil, resp, err
}
return config, resp, nil
}
// UpdateHookConfig updates the webhook configuration for a GitHub App.
// The underlying transport must be authenticated as an app.
//
// GitHub API docs: https://docs.github.com/en/rest/apps#update-a-webhook-configuration-for-an-app
func (s *AppsService) UpdateHookConfig(ctx context.Context, config *HookConfig) (*HookConfig, *Response, error) {
req, err := s.client.NewRequest("PATCH", "app/hook/config", config)
if err != nil {
return nil, nil, err
}
c := new(HookConfig)
resp, err := s.client.Do(ctx, req, c)
if err != nil {
return nil, resp, err
}
return c, resp, nil
}

View File

@@ -0,0 +1,72 @@
// Copyright 2021 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// ListHookDeliveries lists deliveries of an App webhook.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/webhooks#list-deliveries-for-an-app-webhook
func (s *AppsService) ListHookDeliveries(ctx context.Context, opts *ListCursorOptions) ([]*HookDelivery, *Response, error) {
u, err := addOptions("app/hook/deliveries", opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
deliveries := []*HookDelivery{}
resp, err := s.client.Do(ctx, req, &deliveries)
if err != nil {
return nil, resp, err
}
return deliveries, resp, nil
}
// GetHookDelivery returns the App webhook delivery with the specified ID.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/webhooks#get-a-delivery-for-an-app-webhook
func (s *AppsService) GetHookDelivery(ctx context.Context, deliveryID int64) (*HookDelivery, *Response, error) {
u := fmt.Sprintf("app/hook/deliveries/%v", deliveryID)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
h := new(HookDelivery)
resp, err := s.client.Do(ctx, req, h)
if err != nil {
return nil, resp, err
}
return h, resp, nil
}
// RedeliverHookDelivery redelivers a delivery for an App webhook.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/webhooks#redeliver-a-delivery-for-an-app-webhook
func (s *AppsService) RedeliverHookDelivery(ctx context.Context, deliveryID int64) (*HookDelivery, *Response, error) {
u := fmt.Sprintf("app/hook/deliveries/%v/attempts", deliveryID)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, nil, err
}
h := new(HookDelivery)
resp, err := s.client.Do(ctx, req, h)
if err != nil {
return nil, resp, err
}
return h, resp, nil
}

View File

@@ -0,0 +1,128 @@
// Copyright 2016 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
"strings"
)
// ListRepositories represents the response from the list repos endpoints.
type ListRepositories struct {
TotalCount *int `json:"total_count,omitempty"`
Repositories []*Repository `json:"repositories"`
}
// ListRepos lists the repositories that are accessible to the authenticated installation.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/installations#list-repositories-accessible-to-the-app-installation
func (s *AppsService) ListRepos(ctx context.Context, opts *ListOptions) (*ListRepositories, *Response, error) {
u, err := addOptions("installation/repositories", opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept headers when APIs fully launch.
acceptHeaders := []string{
mediaTypeTopicsPreview,
mediaTypeRepositoryVisibilityPreview,
mediaTypeRepositoryTemplatePreview,
}
req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
var r *ListRepositories
resp, err := s.client.Do(ctx, req, &r)
if err != nil {
return nil, resp, err
}
return r, resp, nil
}
// ListUserRepos lists repositories that are accessible
// to the authenticated user for an installation.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/installations#list-repositories-accessible-to-the-user-access-token
func (s *AppsService) ListUserRepos(ctx context.Context, id int64, opts *ListOptions) (*ListRepositories, *Response, error) {
u := fmt.Sprintf("user/installations/%v/repositories", id)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept headers when APIs fully launch.
acceptHeaders := []string{
mediaTypeTopicsPreview,
mediaTypeRepositoryVisibilityPreview,
mediaTypeRepositoryTemplatePreview,
}
req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
var r *ListRepositories
resp, err := s.client.Do(ctx, req, &r)
if err != nil {
return nil, resp, err
}
return r, resp, nil
}
// AddRepository adds a single repository to an installation.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/installations#add-a-repository-to-an-app-installation
func (s *AppsService) AddRepository(ctx context.Context, instID, repoID int64) (*Repository, *Response, error) {
u := fmt.Sprintf("user/installations/%v/repositories/%v", instID, repoID)
req, err := s.client.NewRequest("PUT", u, nil)
if err != nil {
return nil, nil, err
}
r := new(Repository)
resp, err := s.client.Do(ctx, req, r)
if err != nil {
return nil, resp, err
}
return r, resp, nil
}
// RemoveRepository removes a single repository from an installation.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/installations#remove-a-repository-from-an-app-installation
func (s *AppsService) RemoveRepository(ctx context.Context, instID, repoID int64) (*Response, error) {
u := fmt.Sprintf("user/installations/%v/repositories/%v", instID, repoID)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// RevokeInstallationToken revokes an installation token.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/installations#revoke-an-installation-access-token
func (s *AppsService) RevokeInstallationToken(ctx context.Context) (*Response, error) {
u := "installation/token"
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}

View File

@@ -0,0 +1,49 @@
// Copyright 2019 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// AppConfig describes the configuration of a GitHub App.
type AppConfig 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 *Timestamp `json:"created_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
ClientID *string `json:"client_id,omitempty"`
ClientSecret *string `json:"client_secret,omitempty"`
WebhookSecret *string `json:"webhook_secret,omitempty"`
PEM *string `json:"pem,omitempty"`
}
// CompleteAppManifest completes the App manifest handshake flow for the given
// code.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/apps#create-a-github-app-from-a-manifest
func (s *AppsService) CompleteAppManifest(ctx context.Context, code string) (*AppConfig, *Response, error) {
u := fmt.Sprintf("app-manifests/%s/conversions", code)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, nil, err
}
cfg := new(AppConfig)
resp, err := s.client.Do(ctx, req, cfg)
if err != nil {
return nil, resp, err
}
return cfg, resp, nil
}

View File

@@ -0,0 +1,180 @@
// Copyright 2017 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// MarketplaceService handles communication with the marketplace related
// methods of the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/rest/apps#marketplace
type MarketplaceService struct {
client *Client
// Stubbed controls whether endpoints that return stubbed data are used
// instead of production endpoints. Stubbed data is fake data that's useful
// for testing your GitHub Apps. Stubbed data is hard-coded and will not
// change based on actual subscriptions.
//
// GitHub API docs: https://docs.github.com/en/rest/apps#testing-with-stubbed-endpoints
Stubbed bool
}
// MarketplacePlan represents a GitHub Apps Marketplace Listing Plan.
type MarketplacePlan struct {
URL *string `json:"url,omitempty"`
AccountsURL *string `json:"accounts_url,omitempty"`
ID *int64 `json:"id,omitempty"`
Number *int `json:"number,omitempty"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
MonthlyPriceInCents *int `json:"monthly_price_in_cents,omitempty"`
YearlyPriceInCents *int `json:"yearly_price_in_cents,omitempty"`
// The pricing model for this listing. Can be one of "flat-rate", "per-unit", or "free".
PriceModel *string `json:"price_model,omitempty"`
UnitName *string `json:"unit_name,omitempty"`
Bullets *[]string `json:"bullets,omitempty"`
// State can be one of the values "draft" or "published".
State *string `json:"state,omitempty"`
HasFreeTrial *bool `json:"has_free_trial,omitempty"`
}
// MarketplacePurchase represents a GitHub Apps Marketplace Purchase.
type MarketplacePurchase struct {
// BillingCycle can be one of the values "yearly", "monthly" or nil.
BillingCycle *string `json:"billing_cycle,omitempty"`
NextBillingDate *Timestamp `json:"next_billing_date,omitempty"`
UnitCount *int `json:"unit_count,omitempty"`
Plan *MarketplacePlan `json:"plan,omitempty"`
OnFreeTrial *bool `json:"on_free_trial,omitempty"`
FreeTrialEndsOn *Timestamp `json:"free_trial_ends_on,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
}
// MarketplacePendingChange represents a pending change to a GitHub Apps Marketplace Plan.
type MarketplacePendingChange struct {
EffectiveDate *Timestamp `json:"effective_date,omitempty"`
UnitCount *int `json:"unit_count,omitempty"`
ID *int64 `json:"id,omitempty"`
Plan *MarketplacePlan `json:"plan,omitempty"`
}
// MarketplacePlanAccount represents a GitHub Account (user or organization) on a specific plan.
type MarketplacePlanAccount struct {
URL *string `json:"url,omitempty"`
Type *string `json:"type,omitempty"`
ID *int64 `json:"id,omitempty"`
Login *string `json:"login,omitempty"`
OrganizationBillingEmail *string `json:"organization_billing_email,omitempty"`
MarketplacePurchase *MarketplacePurchase `json:"marketplace_purchase,omitempty"`
MarketplacePendingChange *MarketplacePendingChange `json:"marketplace_pending_change,omitempty"`
}
// ListPlans lists all plans for your Marketplace listing.
//
// GitHub API docs: https://docs.github.com/en/rest/apps#list-plans
func (s *MarketplaceService) ListPlans(ctx context.Context, opts *ListOptions) ([]*MarketplacePlan, *Response, error) {
uri := s.marketplaceURI("plans")
u, err := addOptions(uri, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var plans []*MarketplacePlan
resp, err := s.client.Do(ctx, req, &plans)
if err != nil {
return nil, resp, err
}
return plans, resp, nil
}
// ListPlanAccountsForPlan lists all GitHub accounts (user or organization) on a specific plan.
//
// GitHub API docs: https://docs.github.com/en/rest/apps#list-accounts-for-a-plan
func (s *MarketplaceService) ListPlanAccountsForPlan(ctx context.Context, planID int64, opts *ListOptions) ([]*MarketplacePlanAccount, *Response, error) {
uri := s.marketplaceURI(fmt.Sprintf("plans/%v/accounts", planID))
u, err := addOptions(uri, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var accounts []*MarketplacePlanAccount
resp, err := s.client.Do(ctx, req, &accounts)
if err != nil {
return nil, resp, err
}
return accounts, resp, nil
}
// GetPlanAccountForAccount get GitHub account (user or organization) associated with an account.
//
// GitHub API docs: https://docs.github.com/en/rest/apps#get-a-subscription-plan-for-an-account
func (s *MarketplaceService) GetPlanAccountForAccount(ctx context.Context, accountID int64) (*MarketplacePlanAccount, *Response, error) {
uri := s.marketplaceURI(fmt.Sprintf("accounts/%v", accountID))
req, err := s.client.NewRequest("GET", uri, nil)
if err != nil {
return nil, nil, err
}
var account *MarketplacePlanAccount
resp, err := s.client.Do(ctx, req, &account)
if err != nil {
return nil, resp, err
}
return account, resp, nil
}
// ListMarketplacePurchasesForUser lists all GitHub marketplace purchases made by a user.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/marketplace#list-subscriptions-for-the-authenticated-user-stubbed
// GitHub API docs: https://docs.github.com/en/rest/apps/marketplace#list-subscriptions-for-the-authenticated-user
func (s *MarketplaceService) ListMarketplacePurchasesForUser(ctx context.Context, opts *ListOptions) ([]*MarketplacePurchase, *Response, error) {
uri := "user/marketplace_purchases"
if s.Stubbed {
uri = "user/marketplace_purchases/stubbed"
}
u, err := addOptions(uri, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var purchases []*MarketplacePurchase
resp, err := s.client.Do(ctx, req, &purchases)
if err != nil {
return nil, resp, err
}
return purchases, resp, nil
}
func (s *MarketplaceService) marketplaceURI(endpoint string) string {
url := "marketplace_listing"
if s.Stubbed {
url = "marketplace_listing/stubbed"
}
return url + "/" + endpoint
}

View File

@@ -0,0 +1,281 @@
// Copyright 2015 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// Scope models a GitHub authorization scope.
//
// GitHub API docs: https://docs.github.com/en/rest/oauth/#scopes
type Scope string
// This is the set of scopes for GitHub API V3
const (
ScopeNone Scope = "(no scope)" // REVISIT: is this actually returned, or just a documentation artifact?
ScopeUser Scope = "user"
ScopeUserEmail Scope = "user:email"
ScopeUserFollow Scope = "user:follow"
ScopePublicRepo Scope = "public_repo"
ScopeRepo Scope = "repo"
ScopeRepoDeployment Scope = "repo_deployment"
ScopeRepoStatus Scope = "repo:status"
ScopeDeleteRepo Scope = "delete_repo"
ScopeNotifications Scope = "notifications"
ScopeGist Scope = "gist"
ScopeReadRepoHook Scope = "read:repo_hook"
ScopeWriteRepoHook Scope = "write:repo_hook"
ScopeAdminRepoHook Scope = "admin:repo_hook"
ScopeAdminOrgHook Scope = "admin:org_hook"
ScopeReadOrg Scope = "read:org"
ScopeWriteOrg Scope = "write:org"
ScopeAdminOrg Scope = "admin:org"
ScopeReadPublicKey Scope = "read:public_key"
ScopeWritePublicKey Scope = "write:public_key"
ScopeAdminPublicKey Scope = "admin:public_key"
ScopeReadGPGKey Scope = "read:gpg_key"
ScopeWriteGPGKey Scope = "write:gpg_key"
ScopeAdminGPGKey Scope = "admin:gpg_key"
ScopeSecurityEvents Scope = "security_events"
)
// AuthorizationsService handles communication with the authorization related
// methods of the GitHub API.
//
// This service requires HTTP Basic Authentication; it cannot be accessed using
// an OAuth token.
//
// GitHub API docs: https://docs.github.com/en/rest/oauth-authorizations
type AuthorizationsService service
// Authorization represents an individual GitHub authorization.
type Authorization struct {
ID *int64 `json:"id,omitempty"`
URL *string `json:"url,omitempty"`
Scopes []Scope `json:"scopes,omitempty"`
Token *string `json:"token,omitempty"`
TokenLastEight *string `json:"token_last_eight,omitempty"`
HashedToken *string `json:"hashed_token,omitempty"`
App *AuthorizationApp `json:"app,omitempty"`
Note *string `json:"note,omitempty"`
NoteURL *string `json:"note_url,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
Fingerprint *string `json:"fingerprint,omitempty"`
// User is only populated by the Check and Reset methods.
User *User `json:"user,omitempty"`
}
func (a Authorization) String() string {
return Stringify(a)
}
// AuthorizationApp represents an individual GitHub app (in the context of authorization).
type AuthorizationApp struct {
URL *string `json:"url,omitempty"`
Name *string `json:"name,omitempty"`
ClientID *string `json:"client_id,omitempty"`
}
func (a AuthorizationApp) String() string {
return Stringify(a)
}
// Grant represents an OAuth application that has been granted access to an account.
type Grant struct {
ID *int64 `json:"id,omitempty"`
URL *string `json:"url,omitempty"`
App *AuthorizationApp `json:"app,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
Scopes []string `json:"scopes,omitempty"`
}
func (g Grant) String() string {
return Stringify(g)
}
// AuthorizationRequest represents a request to create an authorization.
type AuthorizationRequest struct {
Scopes []Scope `json:"scopes,omitempty"`
Note *string `json:"note,omitempty"`
NoteURL *string `json:"note_url,omitempty"`
ClientID *string `json:"client_id,omitempty"`
ClientSecret *string `json:"client_secret,omitempty"`
Fingerprint *string `json:"fingerprint,omitempty"`
}
func (a AuthorizationRequest) String() string {
return Stringify(a)
}
// AuthorizationUpdateRequest represents a request to update an authorization.
//
// Note that for any one update, you must only provide one of the "scopes"
// fields. That is, you may provide only one of "Scopes", or "AddScopes", or
// "RemoveScopes".
//
// GitHub API docs: https://docs.github.com/en/rest/oauth-authorizations#update-an-existing-authorization
type AuthorizationUpdateRequest struct {
Scopes []string `json:"scopes,omitempty"`
AddScopes []string `json:"add_scopes,omitempty"`
RemoveScopes []string `json:"remove_scopes,omitempty"`
Note *string `json:"note,omitempty"`
NoteURL *string `json:"note_url,omitempty"`
Fingerprint *string `json:"fingerprint,omitempty"`
}
func (a AuthorizationUpdateRequest) String() string {
return Stringify(a)
}
// Check if an OAuth token is valid for a specific app.
//
// Note that this operation requires the use of BasicAuth, but where the
// username is the OAuth application clientID, and the password is its
// clientSecret. Invalid tokens will return a 404 Not Found.
//
// The returned Authorization.User field will be populated.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/oauth-applications#check-a-token
func (s *AuthorizationsService) Check(ctx context.Context, clientID, accessToken string) (*Authorization, *Response, error) {
u := fmt.Sprintf("applications/%v/token", clientID)
reqBody := &struct {
AccessToken string `json:"access_token"`
}{AccessToken: accessToken}
req, err := s.client.NewRequest("POST", u, reqBody)
if err != nil {
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeOAuthAppPreview)
a := new(Authorization)
resp, err := s.client.Do(ctx, req, a)
if err != nil {
return nil, resp, err
}
return a, resp, nil
}
// Reset is used to reset a valid OAuth token without end user involvement.
// Applications must save the "token" property in the response, because changes
// take effect immediately.
//
// Note that this operation requires the use of BasicAuth, but where the
// username is the OAuth application clientID, and the password is its
// clientSecret. Invalid tokens will return a 404 Not Found.
//
// The returned Authorization.User field will be populated.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/oauth-applications#reset-a-token
func (s *AuthorizationsService) Reset(ctx context.Context, clientID, accessToken string) (*Authorization, *Response, error) {
u := fmt.Sprintf("applications/%v/token", clientID)
reqBody := &struct {
AccessToken string `json:"access_token"`
}{AccessToken: accessToken}
req, err := s.client.NewRequest("PATCH", u, reqBody)
if err != nil {
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeOAuthAppPreview)
a := new(Authorization)
resp, err := s.client.Do(ctx, req, a)
if err != nil {
return nil, resp, err
}
return a, resp, nil
}
// Revoke an authorization for an application.
//
// Note that this operation requires the use of BasicAuth, but where the
// username is the OAuth application clientID, and the password is its
// clientSecret. Invalid tokens will return a 404 Not Found.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-token
func (s *AuthorizationsService) Revoke(ctx context.Context, clientID, accessToken string) (*Response, error) {
u := fmt.Sprintf("applications/%v/token", clientID)
reqBody := &struct {
AccessToken string `json:"access_token"`
}{AccessToken: accessToken}
req, err := s.client.NewRequest("DELETE", u, reqBody)
if err != nil {
return nil, err
}
req.Header.Set("Accept", mediaTypeOAuthAppPreview)
return s.client.Do(ctx, req, nil)
}
// DeleteGrant deletes an OAuth application grant. Deleting an application's
// grant will also delete all OAuth tokens associated with the application for
// the user.
//
// GitHub API docs: https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization
func (s *AuthorizationsService) DeleteGrant(ctx context.Context, clientID, accessToken string) (*Response, error) {
u := fmt.Sprintf("applications/%v/grant", clientID)
reqBody := &struct {
AccessToken string `json:"access_token"`
}{AccessToken: accessToken}
req, err := s.client.NewRequest("DELETE", u, reqBody)
if err != nil {
return nil, err
}
req.Header.Set("Accept", mediaTypeOAuthAppPreview)
return s.client.Do(ctx, req, nil)
}
// CreateImpersonation creates an impersonation OAuth token.
//
// This requires admin permissions. With the returned Authorization.Token
// you can e.g. create or delete a user's public SSH key. NOTE: creating a
// new token automatically revokes an existing one.
//
// GitHub API docs: https://developer.github.com/enterprise/v3/enterprise-admin/users/#create-an-impersonation-oauth-token
func (s *AuthorizationsService) CreateImpersonation(ctx context.Context, username string, authReq *AuthorizationRequest) (*Authorization, *Response, error) {
u := fmt.Sprintf("admin/users/%v/authorizations", username)
req, err := s.client.NewRequest("POST", u, authReq)
if err != nil {
return nil, nil, err
}
a := new(Authorization)
resp, err := s.client.Do(ctx, req, a)
if err != nil {
return nil, resp, err
}
return a, resp, nil
}
// DeleteImpersonation deletes an impersonation OAuth token.
//
// NOTE: there can be only one at a time.
//
// GitHub API docs: https://developer.github.com/enterprise/v3/enterprise-admin/users/#delete-an-impersonation-oauth-token
func (s *AuthorizationsService) DeleteImpersonation(ctx context.Context, username string) (*Response, error) {
u := fmt.Sprintf("admin/users/%v/authorizations", username)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}

View File

@@ -0,0 +1,199 @@
// Copyright 2021 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// BillingService provides access to the billing related functions
// in the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/rest/billing
type BillingService service
// ActionBilling represents a GitHub Action billing.
type ActionBilling struct {
TotalMinutesUsed int `json:"total_minutes_used"`
TotalPaidMinutesUsed float64 `json:"total_paid_minutes_used"`
IncludedMinutes int `json:"included_minutes"`
MinutesUsedBreakdown MinutesUsedBreakdown `json:"minutes_used_breakdown"`
}
type MinutesUsedBreakdown struct {
Ubuntu int `json:"UBUNTU"`
MacOS int `json:"MACOS"`
Windows int `json:"WINDOWS"`
}
// PackageBilling represents a GitHub Package billing.
type PackageBilling struct {
TotalGigabytesBandwidthUsed int `json:"total_gigabytes_bandwidth_used"`
TotalPaidGigabytesBandwidthUsed int `json:"total_paid_gigabytes_bandwidth_used"`
IncludedGigabytesBandwidth int `json:"included_gigabytes_bandwidth"`
}
// StorageBilling represents a GitHub Storage billing.
type StorageBilling struct {
DaysLeftInBillingCycle int `json:"days_left_in_billing_cycle"`
EstimatedPaidStorageForMonth float64 `json:"estimated_paid_storage_for_month"`
EstimatedStorageForMonth int `json:"estimated_storage_for_month"`
}
// ActiveCommitters represents the total active committers across all repositories in an Organization.
type ActiveCommitters struct {
TotalAdvancedSecurityCommitters int `json:"total_advanced_security_committers"`
Repositories []*RepositoryActiveCommitters `json:"repositories,omitempty"`
}
// RepositoryActiveCommitters represents active committers on each repository.
type RepositoryActiveCommitters struct {
Name *string `json:"name,omitempty"`
AdvancedSecurityCommitters *int `json:"advanced_security_committers,omitempty"`
AdvancedSecurityCommittersBreakdown []*AdvancedSecurityCommittersBreakdown `json:"advanced_security_committers_breakdown,omitempty"`
}
// AdvancedSecurityCommittersBreakdown represents the user activity breakdown for ActiveCommitters.
type AdvancedSecurityCommittersBreakdown struct {
UserLogin *string `json:"user_login,omitempty"`
LastPushedDate *string `json:"last_pushed_date,omitempty"`
}
// GetActionsBillingOrg returns the summary of the free and paid GitHub Actions minutes used for an Org.
//
// GitHub API docs: https://docs.github.com/en/rest/billing#get-github-actions-billing-for-an-organization
func (s *BillingService) GetActionsBillingOrg(ctx context.Context, org string) (*ActionBilling, *Response, error) {
u := fmt.Sprintf("orgs/%v/settings/billing/actions", org)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
actionsOrgBilling := new(ActionBilling)
resp, err := s.client.Do(ctx, req, actionsOrgBilling)
if err != nil {
return nil, resp, err
}
return actionsOrgBilling, resp, nil
}
// GetPackagesBillingOrg returns the free and paid storage used for GitHub Packages in gigabytes for an Org.
//
// GitHub API docs: https://docs.github.com/en/rest/billing#get-github-packages-billing-for-an-organization
func (s *BillingService) GetPackagesBillingOrg(ctx context.Context, org string) (*PackageBilling, *Response, error) {
u := fmt.Sprintf("orgs/%v/settings/billing/packages", org)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
packagesOrgBilling := new(PackageBilling)
resp, err := s.client.Do(ctx, req, packagesOrgBilling)
if err != nil {
return nil, resp, err
}
return packagesOrgBilling, resp, nil
}
// GetStorageBillingOrg returns the estimated paid and estimated total storage used for GitHub Actions
// and GitHub Packages in gigabytes for an Org.
//
// GitHub API docs: https://docs.github.com/en/rest/billing#get-shared-storage-billing-for-an-organization
func (s *BillingService) GetStorageBillingOrg(ctx context.Context, org string) (*StorageBilling, *Response, error) {
u := fmt.Sprintf("orgs/%v/settings/billing/shared-storage", org)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
storageOrgBilling := new(StorageBilling)
resp, err := s.client.Do(ctx, req, storageOrgBilling)
if err != nil {
return nil, resp, err
}
return storageOrgBilling, resp, nil
}
// GetAdvancedSecurityActiveCommittersOrg returns the GitHub Advanced Security active committers for an organization per repository.
//
// GitHub API docs: https://docs.github.com/en/rest/billing#get-github-advanced-security-active-committers-for-an-organization
func (s *BillingService) GetAdvancedSecurityActiveCommittersOrg(ctx context.Context, org string) (*ActiveCommitters, *Response, error) {
u := fmt.Sprintf("orgs/%v/settings/billing/advanced-security", org)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
activeOrgCommitters := new(ActiveCommitters)
resp, err := s.client.Do(ctx, req, activeOrgCommitters)
if err != nil {
return nil, resp, err
}
return activeOrgCommitters, resp, nil
}
// GetActionsBillingUser returns the summary of the free and paid GitHub Actions minutes used for a user.
//
// GitHub API docs: https://docs.github.com/en/rest/billing#get-github-actions-billing-for-a-user
func (s *BillingService) GetActionsBillingUser(ctx context.Context, user string) (*ActionBilling, *Response, error) {
u := fmt.Sprintf("users/%v/settings/billing/actions", user)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
actionsUserBilling := new(ActionBilling)
resp, err := s.client.Do(ctx, req, actionsUserBilling)
if err != nil {
return nil, resp, err
}
return actionsUserBilling, resp, nil
}
// GetPackagesBillingUser returns the free and paid storage used for GitHub Packages in gigabytes for a user.
//
// GitHub API docs: https://docs.github.com/en/rest/billing#get-github-packages-billing-for-a-user
func (s *BillingService) GetPackagesBillingUser(ctx context.Context, user string) (*PackageBilling, *Response, error) {
u := fmt.Sprintf("users/%v/settings/billing/packages", user)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
packagesUserBilling := new(PackageBilling)
resp, err := s.client.Do(ctx, req, packagesUserBilling)
if err != nil {
return nil, resp, err
}
return packagesUserBilling, resp, nil
}
// GetStorageBillingUser returns the estimated paid and estimated total storage used for GitHub Actions
// and GitHub Packages in gigabytes for a user.
//
// GitHub API docs: https://docs.github.com/en/rest/billing#get-shared-storage-billing-for-a-user
func (s *BillingService) GetStorageBillingUser(ctx context.Context, user string) (*StorageBilling, *Response, error) {
u := fmt.Sprintf("users/%v/settings/billing/shared-storage", user)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
storageUserBilling := new(StorageBilling)
resp, err := s.client.Do(ctx, req, storageUserBilling)
if err != nil {
return nil, resp, err
}
return storageUserBilling, resp, nil
}

451
vendor/github.com/google/go-github/v48/github/checks.go generated vendored Normal file
View File

@@ -0,0 +1,451 @@
// Copyright 2018 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// ChecksService provides access to the Checks API in the
// GitHub API.
//
// GitHub API docs: https://docs.github.com/en/rest/checks/
type ChecksService service
// CheckRun represents a GitHub check run on a repository associated with a GitHub app.
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 *Timestamp `json:"started_at,omitempty"`
CompletedAt *Timestamp `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"`
}
// CheckRunOutput represents the output of a CheckRun.
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"`
}
// CheckRunAnnotation represents an annotation object for a CheckRun output.
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"`
}
// CheckRunImage represents an image object for a CheckRun output.
type CheckRunImage struct {
Alt *string `json:"alt,omitempty"`
ImageURL *string `json:"image_url,omitempty"`
Caption *string `json:"caption,omitempty"`
}
// CheckSuite represents a suite of check runs.
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"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
App *App `json:"app,omitempty"`
Repository *Repository `json:"repository,omitempty"`
PullRequests []*PullRequest `json:"pull_requests,omitempty"`
// The following fields are only populated by Webhook events.
HeadCommit *Commit `json:"head_commit,omitempty"`
}
func (c CheckRun) String() string {
return Stringify(c)
}
func (c CheckSuite) String() string {
return Stringify(c)
}
// GetCheckRun gets a check-run for a repository.
//
// GitHub API docs: https://docs.github.com/en/rest/checks/runs#get-a-check-run
func (s *ChecksService) GetCheckRun(ctx context.Context, owner, repo string, checkRunID int64) (*CheckRun, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/check-runs/%v", owner, repo, checkRunID)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeCheckRunsPreview)
checkRun := new(CheckRun)
resp, err := s.client.Do(ctx, req, checkRun)
if err != nil {
return nil, resp, err
}
return checkRun, resp, nil
}
// GetCheckSuite gets a single check suite.
//
// GitHub API docs: https://docs.github.com/en/rest/checks/suites#get-a-check-suite
func (s *ChecksService) GetCheckSuite(ctx context.Context, owner, repo string, checkSuiteID int64) (*CheckSuite, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/check-suites/%v", owner, repo, checkSuiteID)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeCheckRunsPreview)
checkSuite := new(CheckSuite)
resp, err := s.client.Do(ctx, req, checkSuite)
if err != nil {
return nil, resp, err
}
return checkSuite, resp, nil
}
// CreateCheckRunOptions sets up parameters needed to create a CheckRun.
type CreateCheckRunOptions struct {
Name string `json:"name"` // The name of the check (e.g., "code-coverage"). (Required.)
HeadSHA string `json:"head_sha"` // The SHA of the commit. (Required.)
DetailsURL *string `json:"details_url,omitempty"` // The URL of the integrator's site that has the full details of the check. (Optional.)
ExternalID *string `json:"external_id,omitempty"` // A reference for the run on the integrator's system. (Optional.)
Status *string `json:"status,omitempty"` // The current status. Can be one of "queued", "in_progress", or "completed". Default: "queued". (Optional.)
Conclusion *string `json:"conclusion,omitempty"` // Can be one of "success", "failure", "neutral", "cancelled", "skipped", "timed_out", or "action_required". (Optional. Required if you provide a status of "completed".)
StartedAt *Timestamp `json:"started_at,omitempty"` // The time that the check run began. (Optional.)
CompletedAt *Timestamp `json:"completed_at,omitempty"` // The time the check completed. (Optional. Required if you provide conclusion.)
Output *CheckRunOutput `json:"output,omitempty"` // Provide descriptive details about the run. (Optional)
Actions []*CheckRunAction `json:"actions,omitempty"` // Possible further actions the integrator can perform, which a user may trigger. (Optional.)
}
// CheckRunAction exposes further actions the integrator can perform, which a user may trigger.
type CheckRunAction struct {
Label string `json:"label"` // The text to be displayed on a button in the web UI. The maximum size is 20 characters. (Required.)
Description string `json:"description"` // A short explanation of what this action would do. The maximum size is 40 characters. (Required.)
Identifier string `json:"identifier"` // A reference for the action on the integrator's system. The maximum size is 20 characters. (Required.)
}
// CreateCheckRun creates a check run for repository.
//
// GitHub API docs: https://docs.github.com/en/rest/checks/runs#create-a-check-run
func (s *ChecksService) CreateCheckRun(ctx context.Context, owner, repo string, opts CreateCheckRunOptions) (*CheckRun, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/check-runs", owner, repo)
req, err := s.client.NewRequest("POST", u, opts)
if err != nil {
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeCheckRunsPreview)
checkRun := new(CheckRun)
resp, err := s.client.Do(ctx, req, checkRun)
if err != nil {
return nil, resp, err
}
return checkRun, resp, nil
}
// UpdateCheckRunOptions sets up parameters needed to update a CheckRun.
type UpdateCheckRunOptions struct {
Name string `json:"name"` // The name of the check (e.g., "code-coverage"). (Required.)
DetailsURL *string `json:"details_url,omitempty"` // The URL of the integrator's site that has the full details of the check. (Optional.)
ExternalID *string `json:"external_id,omitempty"` // A reference for the run on the integrator's system. (Optional.)
Status *string `json:"status,omitempty"` // The current status. Can be one of "queued", "in_progress", or "completed". Default: "queued". (Optional.)
Conclusion *string `json:"conclusion,omitempty"` // Can be one of "success", "failure", "neutral", "cancelled", "skipped", "timed_out", or "action_required". (Optional. Required if you provide a status of "completed".)
CompletedAt *Timestamp `json:"completed_at,omitempty"` // The time the check completed. (Optional. Required if you provide conclusion.)
Output *CheckRunOutput `json:"output,omitempty"` // Provide descriptive details about the run. (Optional)
Actions []*CheckRunAction `json:"actions,omitempty"` // Possible further actions the integrator can perform, which a user may trigger. (Optional.)
}
// UpdateCheckRun updates a check run for a specific commit in a repository.
//
// GitHub API docs: https://docs.github.com/en/rest/checks/runs#update-a-check-run
func (s *ChecksService) UpdateCheckRun(ctx context.Context, owner, repo string, checkRunID int64, opts UpdateCheckRunOptions) (*CheckRun, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/check-runs/%v", owner, repo, checkRunID)
req, err := s.client.NewRequest("PATCH", u, opts)
if err != nil {
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeCheckRunsPreview)
checkRun := new(CheckRun)
resp, err := s.client.Do(ctx, req, checkRun)
if err != nil {
return nil, resp, err
}
return checkRun, resp, nil
}
// ListCheckRunAnnotations lists the annotations for a check run.
//
// GitHub API docs: https://docs.github.com/en/rest/checks/runs#list-check-run-annotations
func (s *ChecksService) ListCheckRunAnnotations(ctx context.Context, owner, repo string, checkRunID int64, opts *ListOptions) ([]*CheckRunAnnotation, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/check-runs/%v/annotations", owner, repo, checkRunID)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeCheckRunsPreview)
var checkRunAnnotations []*CheckRunAnnotation
resp, err := s.client.Do(ctx, req, &checkRunAnnotations)
if err != nil {
return nil, resp, err
}
return checkRunAnnotations, resp, nil
}
// ListCheckRunsOptions represents parameters to list check runs.
type ListCheckRunsOptions struct {
CheckName *string `url:"check_name,omitempty"` // Returns check runs with the specified name.
Status *string `url:"status,omitempty"` // Returns check runs with the specified status. Can be one of "queued", "in_progress", or "completed".
Filter *string `url:"filter,omitempty"` // Filters check runs by their completed_at timestamp. Can be one of "latest" (returning the most recent check runs) or "all". Default: "latest"
AppID *int64 `url:"app_id,omitempty"` // Filters check runs by GitHub App ID.
ListOptions
}
// ListCheckRunsResults represents the result of a check run list.
type ListCheckRunsResults struct {
Total *int `json:"total_count,omitempty"`
CheckRuns []*CheckRun `json:"check_runs,omitempty"`
}
// ListCheckRunsForRef lists check runs for a specific ref.
//
// GitHub API docs: https://docs.github.com/en/rest/checks/runs#list-check-runs-for-a-git-reference
func (s *ChecksService) ListCheckRunsForRef(ctx context.Context, owner, repo, ref string, opts *ListCheckRunsOptions) (*ListCheckRunsResults, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/commits/%v/check-runs", owner, repo, refURLEscape(ref))
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeCheckRunsPreview)
var checkRunResults *ListCheckRunsResults
resp, err := s.client.Do(ctx, req, &checkRunResults)
if err != nil {
return nil, resp, err
}
return checkRunResults, resp, nil
}
// ListCheckRunsCheckSuite lists check runs for a check suite.
//
// GitHub API docs: https://docs.github.com/en/rest/checks/runs#list-check-runs-in-a-check-suite
func (s *ChecksService) ListCheckRunsCheckSuite(ctx context.Context, owner, repo string, checkSuiteID int64, opts *ListCheckRunsOptions) (*ListCheckRunsResults, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/check-suites/%v/check-runs", owner, repo, checkSuiteID)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeCheckRunsPreview)
var checkRunResults *ListCheckRunsResults
resp, err := s.client.Do(ctx, req, &checkRunResults)
if err != nil {
return nil, resp, err
}
return checkRunResults, resp, nil
}
// ReRequestCheckRun triggers GitHub to rerequest an existing check run.
//
// GitHub API docs: https://docs.github.com/en/rest/checks/runs#rerequest-a-check-run
func (s *ChecksService) ReRequestCheckRun(ctx context.Context, owner, repo string, checkRunID int64) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/check-runs/%v/rerequest", owner, repo, checkRunID)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", mediaTypeCheckRunsPreview)
return s.client.Do(ctx, req, nil)
}
// ListCheckSuiteOptions represents parameters to list check suites.
type ListCheckSuiteOptions struct {
CheckName *string `url:"check_name,omitempty"` // Filters checks suites by the name of the check run.
AppID *int `url:"app_id,omitempty"` // Filters check suites by GitHub App id.
ListOptions
}
// ListCheckSuiteResults represents the result of a check run list.
type ListCheckSuiteResults struct {
Total *int `json:"total_count,omitempty"`
CheckSuites []*CheckSuite `json:"check_suites,omitempty"`
}
// ListCheckSuitesForRef lists check suite for a specific ref.
//
// GitHub API docs: https://docs.github.com/en/rest/checks/suites#list-check-suites-for-a-git-reference
func (s *ChecksService) ListCheckSuitesForRef(ctx context.Context, owner, repo, ref string, opts *ListCheckSuiteOptions) (*ListCheckSuiteResults, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/commits/%v/check-suites", owner, repo, refURLEscape(ref))
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeCheckRunsPreview)
var checkSuiteResults *ListCheckSuiteResults
resp, err := s.client.Do(ctx, req, &checkSuiteResults)
if err != nil {
return nil, resp, err
}
return checkSuiteResults, resp, nil
}
// AutoTriggerCheck enables or disables automatic creation of CheckSuite events upon pushes to the repository.
type AutoTriggerCheck struct {
AppID *int64 `json:"app_id,omitempty"` // The id of the GitHub App. (Required.)
Setting *bool `json:"setting,omitempty"` // Set to "true" to enable automatic creation of CheckSuite events upon pushes to the repository, or "false" to disable them. Default: "true" (Required.)
}
// CheckSuitePreferenceOptions set options for check suite preferences for a repository.
type CheckSuitePreferenceOptions struct {
AutoTriggerChecks []*AutoTriggerCheck `json:"auto_trigger_checks,omitempty"` // A slice of auto trigger checks that can be set for a check suite in a repository.
}
// CheckSuitePreferenceResults represents the results of the preference set operation.
type CheckSuitePreferenceResults struct {
Preferences *PreferenceList `json:"preferences,omitempty"`
Repository *Repository `json:"repository,omitempty"`
}
// PreferenceList represents a list of auto trigger checks for repository
type PreferenceList struct {
AutoTriggerChecks []*AutoTriggerCheck `json:"auto_trigger_checks,omitempty"` // A slice of auto trigger checks that can be set for a check suite in a repository.
}
// SetCheckSuitePreferences changes the default automatic flow when creating check suites.
//
// GitHub API docs: https://docs.github.com/en/rest/checks/suites#update-repository-preferences-for-check-suites
func (s *ChecksService) SetCheckSuitePreferences(ctx context.Context, owner, repo string, opts CheckSuitePreferenceOptions) (*CheckSuitePreferenceResults, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/check-suites/preferences", owner, repo)
req, err := s.client.NewRequest("PATCH", u, opts)
if err != nil {
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeCheckRunsPreview)
var checkSuitePrefResults *CheckSuitePreferenceResults
resp, err := s.client.Do(ctx, req, &checkSuitePrefResults)
if err != nil {
return nil, resp, err
}
return checkSuitePrefResults, resp, nil
}
// CreateCheckSuiteOptions sets up parameters to manually create a check suites
type CreateCheckSuiteOptions struct {
HeadSHA string `json:"head_sha"` // The sha of the head commit. (Required.)
HeadBranch *string `json:"head_branch,omitempty"` // The name of the head branch where the code changes are implemented.
}
// CreateCheckSuite manually creates a check suite for a repository.
//
// GitHub API docs: https://docs.github.com/en/rest/checks/suites#create-a-check-suite
func (s *ChecksService) CreateCheckSuite(ctx context.Context, owner, repo string, opts CreateCheckSuiteOptions) (*CheckSuite, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/check-suites", owner, repo)
req, err := s.client.NewRequest("POST", u, opts)
if err != nil {
return nil, nil, err
}
req.Header.Set("Accept", mediaTypeCheckRunsPreview)
checkSuite := new(CheckSuite)
resp, err := s.client.Do(ctx, req, checkSuite)
if err != nil {
return nil, resp, err
}
return checkSuite, resp, nil
}
// ReRequestCheckSuite triggers GitHub to rerequest an existing check suite, without pushing new code to a repository.
//
// GitHub API docs: https://docs.github.com/en/rest/checks/suites#rerequest-a-check-suite
func (s *ChecksService) ReRequestCheckSuite(ctx context.Context, owner, repo string, checkSuiteID int64) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/check-suites/%v/rerequest", owner, repo, checkSuiteID)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", mediaTypeCheckRunsPreview)
resp, err := s.client.Do(ctx, req, nil)
return resp, err
}

View File

@@ -0,0 +1,339 @@
// Copyright 2020 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
"strconv"
"strings"
)
// CodeScanningService handles communication with the code scanning related
// methods of the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/rest/code-scanning
type CodeScanningService service
// Rule represents the complete details of GitHub Code Scanning alert type.
type Rule struct {
ID *string `json:"id,omitempty"`
Severity *string `json:"severity,omitempty"`
Description *string `json:"description,omitempty"`
Name *string `json:"name,omitempty"`
SecuritySeverityLevel *string `json:"security_severity_level,omitempty"`
FullDescription *string `json:"full_description,omitempty"`
Tags []string `json:"tags,omitempty"`
Help *string `json:"help,omitempty"`
}
// Location represents the exact location of the GitHub Code Scanning Alert in the scanned project.
type Location 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"`
}
// Message is a part of MostRecentInstance struct which provides the appropriate message when any action is performed on the analysis object.
type Message struct {
Text *string `json:"text,omitempty"`
}
// MostRecentInstance provides details of the most recent instance of this alert for the default branch or for the specified Git reference.
type MostRecentInstance struct {
Ref *string `json:"ref,omitempty"`
AnalysisKey *string `json:"analysis_key,omitempty"`
Environment *string `json:"environment,omitempty"`
State *string `json:"state,omitempty"`
CommitSHA *string `json:"commit_sha,omitempty"`
Message *Message `json:"message,omitempty"`
Location *Location `json:"location,omitempty"`
Classifications []string `json:"classifications,omitempty"`
}
// Tool represents the tool used to generate a GitHub Code Scanning Alert.
type Tool struct {
Name *string `json:"name,omitempty"`
GUID *string `json:"guid,omitempty"`
Version *string `json:"version,omitempty"`
}
// Alert represents an individual GitHub Code Scanning Alert on a single repository.
//
// GitHub API docs: https://docs.github.com/en/rest/code-scanning
type Alert struct {
Number *int `json:"number,omitempty"`
Repository *Repository `json:"repository,omitempty"`
RuleID *string `json:"rule_id,omitempty"`
RuleSeverity *string `json:"rule_severity,omitempty"`
RuleDescription *string `json:"rule_description,omitempty"`
Rule *Rule `json:"rule,omitempty"`
Tool *Tool `json:"tool,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
FixedAt *Timestamp `json:"fixed_at,omitempty"`
State *string `json:"state,omitempty"`
ClosedBy *User `json:"closed_by,omitempty"`
ClosedAt *Timestamp `json:"closed_at,omitempty"`
URL *string `json:"url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
MostRecentInstance *MostRecentInstance `json:"most_recent_instance,omitempty"`
Instances []*MostRecentInstance `json:"instances,omitempty"`
DismissedBy *User `json:"dismissed_by,omitempty"`
DismissedAt *Timestamp `json:"dismissed_at,omitempty"`
DismissedReason *string `json:"dismissed_reason,omitempty"`
DismissedComment *string `json:"dismissed_comment,omitempty"`
InstancesURL *string `json:"instances_url,omitempty"`
}
// ID returns the ID associated with an alert. It is the number at the end of the security alert's URL.
func (a *Alert) ID() int64 {
if a == nil {
return 0
}
s := a.GetHTMLURL()
// Check for an ID to parse at the end of the url
if i := strings.LastIndex(s, "/"); i >= 0 {
s = s[i+1:]
}
// Return the alert ID as a 64-bit integer. Unable to convert or out of range returns 0.
id, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return 0
}
return id
}
// AlertListOptions specifies optional parameters to the CodeScanningService.ListAlerts
// method.
type AlertListOptions struct {
// State of the code scanning alerts to list. Set to closed to list only closed code scanning alerts. Default: open
State string `url:"state,omitempty"`
// Return code scanning alerts for a specific branch reference. The ref must be formatted as heads/<branch name>.
Ref string `url:"ref,omitempty"`
ListCursorOptions
// Add ListOptions so offset pagination with integer type "page" query parameter is accepted
// since ListCursorOptions accepts "page" as string only.
ListOptions
}
// AnalysesListOptions specifies optional parameters to the CodeScanningService.ListAnalysesForRepo method.
type AnalysesListOptions struct {
// Return code scanning analyses belonging to the same SARIF upload.
SarifID *string `url:"sarif_id,omitempty"`
// Return code scanning analyses for a specific branch reference. The ref can be formatted as refs/heads/<branch name> or simply <branch name>.
Ref *string `url:"ref,omitempty"`
ListOptions
}
// ScanningAnalysis represents an individual GitHub Code Scanning ScanningAnalysis on a single repository.
//
// GitHub API docs: https://docs.github.com/en/rest/code-scanning
type ScanningAnalysis struct {
ID *int64 `json:"id,omitempty"`
Ref *string `json:"ref,omitempty"`
CommitSHA *string `json:"commit_sha,omitempty"`
AnalysisKey *string `json:"analysis_key,omitempty"`
Environment *string `json:"environment,omitempty"`
Error *string `json:"error,omitempty"`
Category *string `json:"category,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
ResultsCount *int `json:"results_count,omitempty"`
RulesCount *int `json:"rules_count,omitempty"`
URL *string `json:"url,omitempty"`
SarifID *string `json:"sarif_id,omitempty"`
Tool *Tool `json:"tool,omitempty"`
Deletable *bool `json:"deletable,omitempty"`
Warning *string `json:"warning,omitempty"`
}
// SarifAnalysis specifies the results of a code scanning job.
//
// GitHub API docs: https://docs.github.com/en/rest/code-scanning
type SarifAnalysis struct {
CommitSHA *string `json:"commit_sha,omitempty"`
Ref *string `json:"ref,omitempty"`
Sarif *string `json:"sarif,omitempty"`
CheckoutURI *string `json:"checkout_uri,omitempty"`
StartedAt *Timestamp `json:"started_at,omitempty"`
ToolName *string `json:"tool_name,omitempty"`
}
// SarifID identifies a sarif analysis upload.
//
// GitHub API docs: https://docs.github.com/en/rest/code-scanning
type SarifID struct {
ID *string `json:"id,omitempty"`
URL *string `json:"url,omitempty"`
}
// ListAlertsForOrg lists code scanning alerts for an org.
//
// You must use an access token with the security_events scope to use this endpoint. GitHub Apps must have the security_events
// read permission to use this endpoint.
//
// GitHub API docs: https://docs.github.com/en/rest/code-scanning#list-code-scanning-alerts-for-an-organization
func (s *CodeScanningService) ListAlertsForOrg(ctx context.Context, org string, opts *AlertListOptions) ([]*Alert, *Response, error) {
u := fmt.Sprintf("orgs/%v/code-scanning/alerts", org)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var alerts []*Alert
resp, err := s.client.Do(ctx, req, &alerts)
if err != nil {
return nil, resp, err
}
return alerts, resp, nil
}
// ListAlertsForRepo lists code scanning alerts for a repository.
//
// Lists all open code scanning alerts for the default branch (usually master) and protected branches in a repository.
// You must use an access token with the security_events scope to use this endpoint. GitHub Apps must have the security_events
// read permission to use this endpoint.
//
// GitHub API docs: https://docs.github.com/en/rest/code-scanning#list-code-scanning-alerts-for-a-repository
func (s *CodeScanningService) ListAlertsForRepo(ctx context.Context, owner, repo string, opts *AlertListOptions) ([]*Alert, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/code-scanning/alerts", owner, repo)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var alerts []*Alert
resp, err := s.client.Do(ctx, req, &alerts)
if err != nil {
return nil, resp, err
}
return alerts, resp, nil
}
// GetAlert gets a single code scanning alert for a repository.
//
// You must use an access token with the security_events scope to use this endpoint.
// GitHub Apps must have the security_events read permission to use this endpoint.
//
// The security alert_id is the number at the end of the security alert's URL.
//
// GitHub API docs: https://docs.github.com/en/rest/code-scanning#get-a-code-scanning-alert
func (s *CodeScanningService) GetAlert(ctx context.Context, owner, repo string, id int64) (*Alert, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/code-scanning/alerts/%v", owner, repo, id)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
a := new(Alert)
resp, err := s.client.Do(ctx, req, a)
if err != nil {
return nil, resp, err
}
return a, resp, nil
}
// UploadSarif uploads the result of code scanning job to GitHub.
//
// For the parameter sarif, you must first compress your SARIF file using gzip and then translate the contents of the file into a Base64 encoding string.
// You must use an access token with the security_events scope to use this endpoint. GitHub Apps must have the security_events
// write permission to use this endpoint.
//
// GitHub API docs: https://docs.github.com/en/rest/code-scanning#upload-an-analysis-as-sarif-data
func (s *CodeScanningService) UploadSarif(ctx context.Context, owner, repo string, sarif *SarifAnalysis) (*SarifID, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/code-scanning/sarifs", owner, repo)
req, err := s.client.NewRequest("POST", u, sarif)
if err != nil {
return nil, nil, err
}
sarifID := new(SarifID)
resp, err := s.client.Do(ctx, req, sarifID)
if err != nil {
return nil, resp, err
}
return sarifID, resp, nil
}
// ListAnalysesForRepo lists code scanning analyses for a repository.
//
// Lists the details of all code scanning analyses for a repository, starting with the most recent.
// You must use an access token with the security_events scope to use this endpoint.
// GitHub Apps must have the security_events read permission to use this endpoint.
//
// GitHub API docs: https://docs.github.com/en/rest/code-scanning#list-code-scanning-analyses-for-a-repository
func (s *CodeScanningService) ListAnalysesForRepo(ctx context.Context, owner, repo string, opts *AnalysesListOptions) ([]*ScanningAnalysis, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/code-scanning/analyses", owner, repo)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var analyses []*ScanningAnalysis
resp, err := s.client.Do(ctx, req, &analyses)
if err != nil {
return nil, resp, err
}
return analyses, resp, nil
}
// GetAnalysis gets a single code scanning analysis for a repository.
//
// You must use an access token with the security_events scope to use this endpoint.
// GitHub Apps must have the security_events read permission to use this endpoint.
//
// The security analysis_id is the ID of the analysis, as returned from the ListAnalysesForRepo operation.
//
// GitHub API docs: https://docs.github.com/en/rest/code-scanning#get-a-code-scanning-analysis-for-a-repository
func (s *CodeScanningService) GetAnalysis(ctx context.Context, owner, repo string, id int64) (*ScanningAnalysis, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/code-scanning/analyses/%v", owner, repo, id)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
analysis := new(ScanningAnalysis)
resp, err := s.client.Do(ctx, req, analysis)
if err != nil {
return nil, resp, err
}
return analysis, resp, nil
}

View File

@@ -0,0 +1,12 @@
// Copyright 2022 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
// DependabotService handles communication with the Dependabot related
// methods of the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/rest/dependabot/
type DependabotService service

View File

@@ -0,0 +1,134 @@
// Copyright 2022 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"context"
"fmt"
)
// Dependency reprensents the vulnerable dependency.
type Dependency struct {
Package *VulnerabilityPackage `json:"package,omitempty"`
ManifestPath *string `json:"manifest_path,omitempty"`
Scope *string `json:"scope,omitempty"`
}
// AdvisoryCVSs represents the advisory pertaining to the Common Vulnerability Scoring System.
type AdvisoryCVSs struct {
Score *float64 `json:"score,omitempty"`
VectorString *string `json:"vector_string,omitempty"`
}
// AdvisoryCWEs reprensent the advisory pertaining to Common Weakness Enumeration.
type AdvisoryCWEs struct {
CWEID *string `json:"cwe_id,omitempty"`
Name *string `json:"name,omitempty"`
}
// DependabotSecurityAdvisory represents the GitHub Security Advisory.
type DependabotSecurityAdvisory struct {
GHSAID *string `json:"ghsa_id,omitempty"`
CVEID *string `json:"cve_id,omitempty"`
Summary *string `json:"summary,omitempty"`
Description *string `json:"description,omitempty"`
Vulnerabilities []*AdvisoryVulnerability `json:"vulnerabilities,omitempty"`
Severity *string `json:"severity,omitempty"`
CVSs *AdvisoryCVSs `json:"cvss,omitempty"`
CWEs []*AdvisoryCWEs `json:"cwes,omitempty"`
Identifiers []*AdvisoryIdentifier `json:"identifiers,omitempty"`
References []*AdvisoryReference `json:"references,omitempty"`
PublishedAt *Timestamp `json:"published_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
WithdrawnAt *Timestamp `json:"withdrawn_at,omitempty"`
}
// DependabotAlert represents a Dependabot alert.
type DependabotAlert struct {
Number *int `json:"number,omitempty"`
State *string `json:"state,omitempty"`
Dependency *Dependency `json:"dependency,omitempty"`
SecurityAdvisory *DependabotSecurityAdvisory `json:"security_advisory,omitempty"`
SecurityVulnerability *AdvisoryVulnerability `json:"security_vulnerability,omitempty"`
URL *string `json:"url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
DismissedAt *Timestamp `json:"dismissed_at,omitempty"`
DismissedBy *User `json:"dismissed_by,omitempty"`
DismissedReason *string `json:"dismissed_reason,omitempty"`
DismissedComment *string `json:"dismissed_comment,omitempty"`
FixedAt *Timestamp `json:"fixed_at,omitempty"`
}
// ListAlertsOptions specifies the optional parameters to the DependabotService.ListRepoAlerts
// and DependabotService.ListOrgAlerts methods.
type ListAlertsOptions struct {
State *string `url:"state,omitempty"`
Severity *string `url:"severity,omitempty"`
Ecosystem *string `url:"ecosystem,omitempty"`
Package *string `url:"package,omitempty"`
Scope *string `url:"scope,omitempty"`
Sort *string `url:"sort,omitempty"`
Direction *string `url:"direction,omitempty"`
ListCursorOptions
}
func (s *DependabotService) listAlerts(ctx context.Context, url string, opts *ListAlertsOptions) ([]*DependabotAlert, *Response, error) {
u, err := addOptions(url, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
var alerts []*DependabotAlert
resp, err := s.client.Do(ctx, req, &alerts)
if err != nil {
return nil, resp, err
}
return alerts, resp, nil
}
// ListRepoAlerts lists all Dependabot alerts of a repository.
//
// GitHub API docs: https://docs.github.com/en/rest/dependabot/alerts#list-dependabot-alerts-for-a-repository
func (s *DependabotService) ListRepoAlerts(ctx context.Context, owner, repo string, opts *ListAlertsOptions) ([]*DependabotAlert, *Response, error) {
url := fmt.Sprintf("repos/%v/%v/dependabot/alerts", owner, repo)
return s.listAlerts(ctx, url, opts)
}
// ListOrgAlerts lists all Dependabot alerts of an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/dependabot/alerts#list-dependabot-alerts-for-an-organization
func (s *DependabotService) ListOrgAlerts(ctx context.Context, org string, opts *ListAlertsOptions) ([]*DependabotAlert, *Response, error) {
url := fmt.Sprintf("orgs/%v/dependabot/alerts", org)
return s.listAlerts(ctx, url, opts)
}
// GetRepoAlert gets a single repository Dependabot alert.
//
// GitHub API docs: https://docs.github.com/en/rest/dependabot/alerts#get-a-dependabot-alert
func (s *DependabotService) GetRepoAlert(ctx context.Context, owner, repo string, number int) (*DependabotAlert, *Response, error) {
url := fmt.Sprintf("repos/%v/%v/dependabot/alerts/%v", owner, repo, number)
req, err := s.client.NewRequest("GET", url, nil)
if err != nil {
return nil, nil, err
}
alert := new(DependabotAlert)
resp, err := s.client.Do(ctx, req, alert)
if err != nil {
return nil, resp, err
}
return alert, resp, nil
}

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