Merge pull request #37 from gabriel-samfira/add-ghe-server-support

Add GitHub Enterprise support
This commit is contained in:
Gabriel 2022-10-21 17:58:52 +03:00 committed by GitHub
commit eba42b0481
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
196 changed files with 6808 additions and 1818 deletions

View file

@ -0,0 +1,282 @@
// Copyright 2022 Cloudbase Solutions SRL
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package controllers
import (
"encoding/json"
"log"
"net/http"
"garm/apiserver/params"
gErrors "garm/errors"
runnerParams "garm/params"
"github.com/gorilla/mux"
)
func (a *APIController) CreateEnterpriseHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var enterpriseData runnerParams.CreateEnterpriseParams
if err := json.NewDecoder(r.Body).Decode(&enterpriseData); err != nil {
handleError(w, gErrors.ErrBadRequest)
return
}
enterprise, err := a.r.CreateEnterprise(ctx, enterpriseData)
if err != nil {
log.Printf("error creating enterprise: %+v", err)
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(enterprise)
}
func (a *APIController) ListEnterprisesHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
enterprise, err := a.r.ListEnterprises(ctx)
if err != nil {
log.Printf("listing enterprise: %s", err)
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(enterprise)
}
func (a *APIController) GetEnterpriseByIDHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
enterpriseID, ok := vars["enterpriseID"]
if !ok {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(params.APIErrorResponse{
Error: "Bad Request",
Details: "No enterprise ID specified",
})
return
}
enterprise, err := a.r.GetEnterpriseByID(ctx, enterpriseID)
if err != nil {
log.Printf("fetching enterprise: %s", err)
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(enterprise)
}
func (a *APIController) DeleteEnterpriseHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
enterpriseID, ok := vars["enterpriseID"]
if !ok {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(params.APIErrorResponse{
Error: "Bad Request",
Details: "No enterprise ID specified",
})
return
}
if err := a.r.DeleteEnterprise(ctx, enterpriseID); err != nil {
log.Printf("removing enterprise: %+v", err)
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
}
func (a *APIController) UpdateEnterpriseHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
enterpriseID, ok := vars["enterpriseID"]
if !ok {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(params.APIErrorResponse{
Error: "Bad Request",
Details: "No enterprise ID specified",
})
return
}
var updatePayload runnerParams.UpdateRepositoryParams
if err := json.NewDecoder(r.Body).Decode(&updatePayload); err != nil {
handleError(w, gErrors.ErrBadRequest)
return
}
enterprise, err := a.r.UpdateEnterprise(ctx, enterpriseID, updatePayload)
if err != nil {
log.Printf("error updating enterprise: %s", err)
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(enterprise)
}
func (a *APIController) CreateEnterprisePoolHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
enterpriseID, ok := vars["enterpriseID"]
if !ok {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(params.APIErrorResponse{
Error: "Bad Request",
Details: "No enterprise ID specified",
})
return
}
var poolData runnerParams.CreatePoolParams
if err := json.NewDecoder(r.Body).Decode(&poolData); err != nil {
log.Printf("failed to decode: %s", err)
handleError(w, gErrors.ErrBadRequest)
return
}
pool, err := a.r.CreateEnterprisePool(ctx, enterpriseID, poolData)
if err != nil {
log.Printf("error creating enterprise pool: %s", err)
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(pool)
}
func (a *APIController) ListEnterprisePoolsHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
enterpriseID, ok := vars["enterpriseID"]
if !ok {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(params.APIErrorResponse{
Error: "Bad Request",
Details: "No enterprise ID specified",
})
return
}
pools, err := a.r.ListEnterprisePools(ctx, enterpriseID)
if err != nil {
log.Printf("listing pools: %s", err)
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(pools)
}
func (a *APIController) GetEnterprisePoolHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
enterpriseID, enterpriseOk := vars["enterpriseID"]
poolID, poolOk := vars["poolID"]
if !enterpriseOk || !poolOk {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(params.APIErrorResponse{
Error: "Bad Request",
Details: "No enterprise or pool ID specified",
})
return
}
pool, err := a.r.GetEnterprisePoolByID(ctx, enterpriseID, poolID)
if err != nil {
log.Printf("listing pools: %s", err)
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(pool)
}
func (a *APIController) DeleteEnterprisePoolHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
enterpriseID, enterpriseOk := vars["enterpriseID"]
poolID, poolOk := vars["poolID"]
if !enterpriseOk || !poolOk {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(params.APIErrorResponse{
Error: "Bad Request",
Details: "No enterprise or pool ID specified",
})
return
}
if err := a.r.DeleteEnterprisePool(ctx, enterpriseID, poolID); err != nil {
log.Printf("removing pool: %s", err)
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
}
func (a *APIController) UpdateEnterprisePoolHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
enterpriseID, enterpriseOk := vars["enterpriseID"]
poolID, poolOk := vars["poolID"]
if !enterpriseOk || !poolOk {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(params.APIErrorResponse{
Error: "Bad Request",
Details: "No enterprise or pool ID specified",
})
return
}
var poolData runnerParams.UpdatePoolParams
if err := json.NewDecoder(r.Body).Decode(&poolData); err != nil {
log.Printf("failed to decode: %s", err)
handleError(w, gErrors.ErrBadRequest)
return
}
pool, err := a.r.UpdateEnterprisePool(ctx, enterpriseID, poolID, poolData)
if err != nil {
log.Printf("error creating enterprise pool: %s", err)
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(pool)
}

View file

@ -136,7 +136,31 @@ func (a *APIController) ListOrgInstancesHandler(w http.ResponseWriter, r *http.R
instances, err := a.r.ListOrgInstances(ctx, orgID)
if err != nil {
log.Printf("listing pools: %s", err)
log.Printf("listing instances: %s", err)
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(instances)
}
func (a *APIController) ListEnterpriseInstancesHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
enterpriseID, ok := vars["enterpriseID"]
if !ok {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(params.APIErrorResponse{
Error: "Bad Request",
Details: "No enterprise ID specified",
})
return
}
instances, err := a.r.ListEnterpriseInstances(ctx, enterpriseID)
if err != nil {
log.Printf("listing instances: %s", err)
handleError(w, err)
return
}

View file

@ -165,6 +165,45 @@ func NewAPIRouter(han *controllers.APIController, logWriter io.Writer, authMiddl
apiRouter.Handle("/organizations/", log(logWriter, http.HandlerFunc(han.CreateOrgHandler))).Methods("POST", "OPTIONS")
apiRouter.Handle("/organizations", log(logWriter, http.HandlerFunc(han.CreateOrgHandler))).Methods("POST", "OPTIONS")
/////////////////////////////
// Enterprises and pools //
/////////////////////////////
// Get pool
apiRouter.Handle("/enterprises/{enterpriseID}/pools/{poolID}/", log(logWriter, http.HandlerFunc(han.GetEnterprisePoolHandler))).Methods("GET", "OPTIONS")
apiRouter.Handle("/enterprises/{enterpriseID}/pools/{poolID}", log(logWriter, http.HandlerFunc(han.GetEnterprisePoolHandler))).Methods("GET", "OPTIONS")
// Delete pool
apiRouter.Handle("/enterprises/{enterpriseID}/pools/{poolID}/", log(logWriter, http.HandlerFunc(han.DeleteEnterprisePoolHandler))).Methods("DELETE", "OPTIONS")
apiRouter.Handle("/enterprises/{enterpriseID}/pools/{poolID}", log(logWriter, http.HandlerFunc(han.DeleteEnterprisePoolHandler))).Methods("DELETE", "OPTIONS")
// Update pool
apiRouter.Handle("/enterprises/{enterpriseID}/pools/{poolID}/", log(logWriter, http.HandlerFunc(han.UpdateEnterprisePoolHandler))).Methods("PUT", "OPTIONS")
apiRouter.Handle("/enterprises/{enterpriseID}/pools/{poolID}", log(logWriter, http.HandlerFunc(han.UpdateEnterprisePoolHandler))).Methods("PUT", "OPTIONS")
// List pools
apiRouter.Handle("/enterprises/{enterpriseID}/pools/", log(logWriter, http.HandlerFunc(han.ListEnterprisePoolsHandler))).Methods("GET", "OPTIONS")
apiRouter.Handle("/enterprises/{enterpriseID}/pools", log(logWriter, http.HandlerFunc(han.ListEnterprisePoolsHandler))).Methods("GET", "OPTIONS")
// Create pool
apiRouter.Handle("/enterprises/{enterpriseID}/pools/", log(logWriter, http.HandlerFunc(han.CreateEnterprisePoolHandler))).Methods("POST", "OPTIONS")
apiRouter.Handle("/enterprises/{enterpriseID}/pools", log(logWriter, http.HandlerFunc(han.CreateEnterprisePoolHandler))).Methods("POST", "OPTIONS")
// Repo instances list
apiRouter.Handle("/enterprises/{enterpriseID}/instances/", log(logWriter, http.HandlerFunc(han.ListEnterpriseInstancesHandler))).Methods("GET", "OPTIONS")
apiRouter.Handle("/enterprises/{enterpriseID}/instances", log(logWriter, http.HandlerFunc(han.ListEnterpriseInstancesHandler))).Methods("GET", "OPTIONS")
// Get org
apiRouter.Handle("/enterprises/{enterpriseID}/", log(logWriter, http.HandlerFunc(han.GetEnterpriseByIDHandler))).Methods("GET", "OPTIONS")
apiRouter.Handle("/enterprises/{enterpriseID}", log(logWriter, http.HandlerFunc(han.GetEnterpriseByIDHandler))).Methods("GET", "OPTIONS")
// Update org
apiRouter.Handle("/enterprises/{enterpriseID}/", log(logWriter, http.HandlerFunc(han.UpdateEnterpriseHandler))).Methods("PUT", "OPTIONS")
apiRouter.Handle("/enterprises/{enterpriseID}", log(logWriter, http.HandlerFunc(han.UpdateEnterpriseHandler))).Methods("PUT", "OPTIONS")
// Delete org
apiRouter.Handle("/enterprises/{enterpriseID}/", log(logWriter, http.HandlerFunc(han.DeleteEnterpriseHandler))).Methods("DELETE", "OPTIONS")
apiRouter.Handle("/enterprises/{enterpriseID}", log(logWriter, http.HandlerFunc(han.DeleteEnterpriseHandler))).Methods("DELETE", "OPTIONS")
// List orgs
apiRouter.Handle("/enterprises/", log(logWriter, http.HandlerFunc(han.ListEnterprisesHandler))).Methods("GET", "OPTIONS")
apiRouter.Handle("/enterprises", log(logWriter, http.HandlerFunc(han.ListEnterprisesHandler))).Methods("GET", "OPTIONS")
// Create org
apiRouter.Handle("/enterprises/", log(logWriter, http.HandlerFunc(han.CreateEnterpriseHandler))).Methods("POST", "OPTIONS")
apiRouter.Handle("/enterprises", log(logWriter, http.HandlerFunc(han.CreateEnterpriseHandler))).Methods("POST", "OPTIONS")
// Credentials and providers
apiRouter.Handle("/credentials/", log(logWriter, http.HandlerFunc(han.ListCredentials))).Methods("GET", "OPTIONS")
apiRouter.Handle("/credentials", log(logWriter, http.HandlerFunc(han.ListCredentials))).Methods("GET", "OPTIONS")

View file

@ -15,6 +15,7 @@
package cloudconfig
import (
"crypto/x509"
"encoding/base64"
"fmt"
"garm/config"
@ -73,6 +74,29 @@ type CloudInit struct {
SystemInfo *SystemInfo `yaml:"system_info,omitempty"`
RunCmd []string `yaml:"runcmd,omitempty"`
WriteFiles []File `yaml:"write_files,omitempty"`
CACerts CACerts `yaml:"ca-certs,omitempty"`
}
type CACerts struct {
RemoveDefaults bool `yaml:"remove-defaults"`
Trusted []string `yaml:"trusted"`
}
func (c *CloudInit) AddCACert(cert []byte) error {
c.mux.Lock()
defer c.mux.Unlock()
if cert == nil {
return nil
}
roots := x509.NewCertPool()
if ok := roots.AppendCertsFromPEM(cert); !ok {
return fmt.Errorf("failed to parse CA cert bundle")
}
c.CACerts.Trusted = append(c.CACerts.Trusted, string(cert))
return nil
}
func (c *CloudInit) AddSSHKey(keys ...string) {

View file

@ -52,7 +52,16 @@ function fail() {
}
sendStatus "downloading tools from {{ .DownloadURL }}"
curl -L -o "/home/runner/{{ .FileName }}" "{{ .DownloadURL }}" || fail "failed to download tools"
TEMP_TOKEN=""
if [ ! -z "{{ .TempDownloadToken }}" ]; then
TEMP_TOKEN="Authorization: Bearer {{ .TempDownloadToken }}"
fi
curl -L -H "${TEMP_TOKEN}" -o "/home/runner/{{ .FileName }}" "{{ .DownloadURL }}" || fail "failed to download tools"
mkdir -p /home/runner/actions-runner || fail "failed to create actions-runner folder"
@ -84,16 +93,17 @@ success "runner successfully installed" $AGENT_ID
`
type InstallRunnerParams struct {
FileName string
DownloadURL string
RunnerUsername string
RunnerGroup string
RepoURL string
GithubToken string
RunnerName string
RunnerLabels string
CallbackURL string
CallbackToken string
FileName string
DownloadURL string
RunnerUsername string
RunnerGroup string
RepoURL string
GithubToken string
RunnerName string
RunnerLabels string
CallbackURL string
CallbackToken string
TempDownloadToken string
}
func InstallRunnerScript(params InstallRunnerParams) ([]byte, error) {

View file

@ -0,0 +1,203 @@
// Copyright 2022 Cloudbase Solutions SRL
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package client
import (
"encoding/json"
"fmt"
"garm/params"
"github.com/pkg/errors"
)
func (c *Client) ListEnterprises() ([]params.Enterprise, error) {
var enterprises []params.Enterprise
url := fmt.Sprintf("%s/api/v1/enterprises", c.Config.BaseURL)
resp, err := c.client.R().
SetResult(&enterprises).
Get(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return nil, errors.Wrap(decErr, "sending request")
}
return nil, fmt.Errorf("error fetching enterprises: %s", apiErr.Details)
}
return enterprises, nil
}
func (c *Client) CreateEnterprise(param params.CreateEnterpriseParams) (params.Enterprise, error) {
var response params.Enterprise
url := fmt.Sprintf("%s/api/v1/enterprises", c.Config.BaseURL)
body, err := json.Marshal(param)
if err != nil {
return params.Enterprise{}, err
}
resp, err := c.client.R().
SetBody(body).
SetResult(&response).
Post(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return response, errors.Wrap(decErr, "sending request")
}
return response, fmt.Errorf("error creating enterprise: %s", apiErr.Details)
}
return response, nil
}
func (c *Client) GetEnterprise(enterpriseID string) (params.Enterprise, error) {
var response params.Enterprise
url := fmt.Sprintf("%s/api/v1/enterprises/%s", c.Config.BaseURL, enterpriseID)
resp, err := c.client.R().
SetResult(&response).
Get(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return response, errors.Wrap(decErr, "sending request")
}
return response, fmt.Errorf("error fetching enterprise: %s", apiErr.Details)
}
return response, nil
}
func (c *Client) DeleteEnterprise(enterpriseID string) error {
url := fmt.Sprintf("%s/api/v1/enterprises/%s", c.Config.BaseURL, enterpriseID)
resp, err := c.client.R().
Delete(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return errors.Wrap(decErr, "sending request")
}
return fmt.Errorf("error fetching removing enterprise: %s", apiErr.Details)
}
return nil
}
func (c *Client) CreateEnterprisePool(enterpriseID string, param params.CreatePoolParams) (params.Pool, error) {
url := fmt.Sprintf("%s/api/v1/enterprises/%s/pools", c.Config.BaseURL, enterpriseID)
var response params.Pool
body, err := json.Marshal(param)
if err != nil {
return response, err
}
resp, err := c.client.R().
SetBody(body).
SetResult(&response).
Post(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return response, errors.Wrap(decErr, "sending request")
}
return response, fmt.Errorf("error creating enterprise pool: %s", apiErr.Details)
}
return response, nil
}
func (c *Client) ListEnterprisePools(enterpriseID string) ([]params.Pool, error) {
url := fmt.Sprintf("%s/api/v1/enterprises/%s/pools", c.Config.BaseURL, enterpriseID)
var response []params.Pool
resp, err := c.client.R().
SetResult(&response).
Get(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return response, errors.Wrap(decErr, "sending request")
}
return response, fmt.Errorf("error listing enterprise pools: %s", apiErr.Details)
}
return response, nil
}
func (c *Client) GetEnterprisePool(enterpriseID, poolID string) (params.Pool, error) {
url := fmt.Sprintf("%s/api/v1/enterprises/%s/pools/%s", c.Config.BaseURL, enterpriseID, poolID)
var response params.Pool
resp, err := c.client.R().
SetResult(&response).
Get(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return response, errors.Wrap(decErr, "sending request")
}
return response, fmt.Errorf("error fetching enterprise pool: %s", apiErr.Details)
}
return response, nil
}
func (c *Client) DeleteEnterprisePool(enterpriseID, poolID string) error {
url := fmt.Sprintf("%s/api/v1/enterprises/%s/pools/%s", c.Config.BaseURL, enterpriseID, poolID)
resp, err := c.client.R().
Delete(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return errors.Wrap(decErr, "sending request")
}
return fmt.Errorf("error deleting enterprise pool: %s", apiErr.Details)
}
return nil
}
func (c *Client) UpdateEnterprisePool(enterpriseID, poolID string, param params.UpdatePoolParams) (params.Pool, error) {
url := fmt.Sprintf("%s/api/v1/enterprises/%s/pools/%s", c.Config.BaseURL, enterpriseID, poolID)
var response params.Pool
body, err := json.Marshal(param)
if err != nil {
return response, err
}
resp, err := c.client.R().
SetBody(body).
SetResult(&response).
Put(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return response, errors.Wrap(decErr, "sending request")
}
return response, fmt.Errorf("error updating enterprise pool: %s", apiErr.Details)
}
return response, nil
}
func (c *Client) ListEnterpriseInstances(enterpriseID string) ([]params.Instance, error) {
url := fmt.Sprintf("%s/api/v1/enterprises/%s/instances", c.Config.BaseURL, enterpriseID)
var response []params.Instance
resp, err := c.client.R().
SetResult(&response).
Get(url)
if err != nil || resp.IsError() {
apiErr, decErr := c.decodeAPIError(resp.Body())
if decErr != nil {
return response, errors.Wrap(decErr, "sending request")
}
return response, fmt.Errorf("error listing enterprise instances: %s", apiErr.Details)
}
return response, nil
}

View file

@ -56,7 +56,7 @@ func (c *Client) CreateOrganization(param params.CreateOrgParams) (params.Organi
if decErr != nil {
return response, errors.Wrap(decErr, "sending request")
}
return response, fmt.Errorf("error performing login: %s", apiErr.Details)
return response, fmt.Errorf("error creating org: %s", apiErr.Details)
}
return response, nil
}
@ -72,7 +72,7 @@ func (c *Client) GetOrganization(orgID string) (params.Organization, error) {
if decErr != nil {
return response, errors.Wrap(decErr, "sending request")
}
return response, fmt.Errorf("error fetching orgs: %s", apiErr.Details)
return response, fmt.Errorf("error fetching org: %s", apiErr.Details)
}
return response, nil
}
@ -86,7 +86,7 @@ func (c *Client) DeleteOrganization(orgID string) error {
if decErr != nil {
return errors.Wrap(decErr, "sending request")
}
return fmt.Errorf("error fetching orgs: %s", apiErr.Details)
return fmt.Errorf("error removing org: %s", apiErr.Details)
}
return nil
}

View file

@ -46,7 +46,7 @@ func init() {
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
creds, err := cli.ListCredentials()
@ -63,10 +63,10 @@ func init() {
func formatGithubCredentials(creds []params.GithubCredentials) {
t := table.NewWriter()
header := table.Row{"Name", "Description"}
header := table.Row{"Name", "Description", "Base URL", "API URL", "Upload URL"}
t.AppendHeader(header)
for _, val := range creds {
t.AppendRow(table.Row{val.Name, val.Description})
t.AppendRow(table.Row{val.Name, val.Description, val.BaseURL, val.APIBaseURL, val.UploadBaseURL})
t.AppendSeparator()
}
fmt.Println(t.Render())

View file

@ -0,0 +1,190 @@
// Copyright 2022 Cloudbase Solutions SRL
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package cmd
import (
"fmt"
"garm/params"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
)
var (
enterpriseName string
enterpriseWebhookSecret string
enterpriseCreds string
)
// enterpriseCmd represents the enterprise command
var enterpriseCmd = &cobra.Command{
Use: "enterprise",
Aliases: []string{"ent"},
SilenceUsage: true,
Short: "Manage enterprise",
Long: `Add, remove or update enterprise for which we manage
self hosted runners.
This command allows you to define a new enterprise or manage an existing
enterprise for which garm maintains pools of self hosted runners.`,
Run: nil,
}
var enterpriseAddCmd = &cobra.Command{
Use: "add",
Aliases: []string{"create"},
Short: "Add enterprise",
Long: `Add a new enterprise to the manager.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
newEnterpriseReq := params.CreateEnterpriseParams{
Name: enterpriseName,
WebhookSecret: enterpriseWebhookSecret,
CredentialsName: enterpriseCreds,
}
enterprise, err := cli.CreateEnterprise(newEnterpriseReq)
if err != nil {
return err
}
formatOneEnterprise(enterprise)
return nil
},
}
var enterpriseListCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List enterprises",
Long: `List all configured enterprises that are currently managed.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
enterprises, err := cli.ListEnterprises()
if err != nil {
return err
}
formatEnterprises(enterprises)
return nil
},
}
var enterpriseShowCmd = &cobra.Command{
Use: "show",
Short: "Show details for one enterprise",
Long: `Displays detailed information about a single enterprise.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires a enterprise ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
enterprise, err := cli.GetEnterprise(args[0])
if err != nil {
return err
}
formatOneEnterprise(enterprise)
return nil
},
}
var enterpriseDeleteCmd = &cobra.Command{
Use: "delete",
Aliases: []string{"remove", "rm", "del"},
Short: "Removes one enterprise",
Long: `Delete one enterprise from the manager.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires a enterprise ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
if err := cli.DeleteEnterprise(args[0]); err != nil {
return err
}
return nil
},
}
func init() {
enterpriseAddCmd.Flags().StringVar(&enterpriseName, "name", "", "The name of the enterprise")
enterpriseAddCmd.Flags().StringVar(&enterpriseWebhookSecret, "webhook-secret", "", "The webhook secret for this enterprise")
enterpriseAddCmd.Flags().StringVar(&enterpriseCreds, "credentials", "", "Credentials name. See credentials list.")
enterpriseAddCmd.MarkFlagRequired("credentials")
enterpriseAddCmd.MarkFlagRequired("name")
enterpriseCmd.AddCommand(
enterpriseListCmd,
enterpriseAddCmd,
enterpriseShowCmd,
enterpriseDeleteCmd,
)
rootCmd.AddCommand(enterpriseCmd)
}
func formatEnterprises(enterprises []params.Enterprise) {
t := table.NewWriter()
header := table.Row{"ID", "Name", "Credentials name", "Pool mgr running"}
t.AppendHeader(header)
for _, val := range enterprises {
t.AppendRow(table.Row{val.ID, val.Name, val.CredentialsName, val.PoolManagerStatus.IsRunning})
t.AppendSeparator()
}
fmt.Println(t.Render())
}
func formatOneEnterprise(enterprise params.Enterprise) {
t := table.NewWriter()
rowConfigAutoMerge := table.RowConfig{AutoMerge: true}
header := table.Row{"Field", "Value"}
t.AppendHeader(header)
t.AppendRow(table.Row{"ID", enterprise.ID})
t.AppendRow(table.Row{"Name", enterprise.Name})
t.AppendRow(table.Row{"Credentials", enterprise.CredentialsName})
t.AppendRow(table.Row{"Pool manager running", enterprise.PoolManagerStatus.IsRunning})
if !enterprise.PoolManagerStatus.IsRunning {
t.AppendRow(table.Row{"Failure reason", enterprise.PoolManagerStatus.FailureReason})
}
if len(enterprise.Pools) > 0 {
for _, pool := range enterprise.Pools {
t.AppendRow(table.Row{"Pools", pool.ID}, rowConfigAutoMerge)
}
}
t.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: true},
})
fmt.Println(t.Render())
}

View file

@ -37,7 +37,7 @@ var orgRunnerListCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {

View file

@ -48,7 +48,7 @@ var orgPoolAddCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {
@ -91,7 +91,7 @@ var orgPoolListCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {
@ -117,7 +117,7 @@ var orgPoolShowCmd = &cobra.Command{
Long: `Displays detailed information about a single pool.`,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) < 2 || len(args) > 2 {
@ -142,7 +142,7 @@ var orgPoolDeleteCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) < 2 || len(args) > 2 {
return fmt.Errorf("command requires orgID and poolID")
@ -167,7 +167,7 @@ explicitly remove them using the runner delete command.
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) < 2 || len(args) > 2 {

View file

@ -38,7 +38,7 @@ var organizationCmd = &cobra.Command{
self hosted runners.
This command allows you to define a new organization or manage an existing
organization for which the garm maintains pools of self hosted runners.`,
organization for which garm maintains pools of self hosted runners.`,
Run: nil,
}
@ -50,7 +50,7 @@ var orgAddCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
newOrgReq := params.CreateOrgParams{
@ -71,11 +71,11 @@ var orgListCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List organizations",
Long: `List all configured respositories that are currently managed.`,
Long: `List all configured organizations that are currently managed.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
orgs, err := cli.ListOrganizations()
@ -94,7 +94,7 @@ var orgShowCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires a organization ID")
@ -119,30 +119,7 @@ var orgDeleteCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires a organization ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
if err := cli.DeleteOrganization(args[0]); err != nil {
return err
}
return nil
},
}
var orgInstanceListCmd = &cobra.Command{
Use: "delete",
Aliases: []string{"remove", "rm", "del"},
Short: "Removes one organization",
Long: `Delete one organization from the manager.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires a organization ID")
@ -177,10 +154,10 @@ func init() {
func formatOrganizations(orgs []params.Organization) {
t := table.NewWriter()
header := table.Row{"ID", "Name", "Credentials name"}
header := table.Row{"ID", "Name", "Credentials name", "Pool mgr running"}
t.AppendHeader(header)
for _, val := range orgs {
t.AppendRow(table.Row{val.ID, val.Name, val.CredentialsName})
t.AppendRow(table.Row{val.ID, val.Name, val.CredentialsName, val.PoolManagerStatus.IsRunning})
t.AppendSeparator()
}
fmt.Println(t.Render())
@ -194,7 +171,10 @@ func formatOneOrganization(org params.Organization) {
t.AppendRow(table.Row{"ID", org.ID})
t.AppendRow(table.Row{"Name", org.Name})
t.AppendRow(table.Row{"Credentials", org.CredentialsName})
t.AppendRow(table.Row{"Pool manager running", org.PoolManagerStatus.IsRunning})
if !org.PoolManagerStatus.IsRunning {
t.AppendRow(table.Row{"Failure reason", org.PoolManagerStatus.FailureReason})
}
if len(org.Pools) > 0 {
for _, pool := range org.Pools {
t.AppendRow(table.Row{"Pools", pool.ID}, rowConfigAutoMerge)

View file

@ -27,6 +27,7 @@ import (
var (
poolRepository string
poolOrganization string
poolEnterprise string
poolAll bool
)
@ -57,6 +58,9 @@ Example:
List pools from one org:
garm-cli pool list --org=5493e51f-3170-4ce3-9f05-3fe690fc6ec6
List pools from one enterprise:
garm-cli pool list --org=a8ee4c66-e762-4cbe-a35d-175dba2c9e62
List all pools from all repos and orgs:
garm-cli pool list --all
@ -64,7 +68,7 @@ Example:
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
var pools []params.Pool
@ -76,6 +80,8 @@ Example:
pools, err = cli.ListRepoPools(poolRepository)
} else if cmd.Flags().Changed("org") {
pools, err = cli.ListOrgPools(poolOrganization)
} else if cmd.Flags().Changed("enterprise") {
pools, err = cli.ListEnterprisePools(poolEnterprise)
} else if cmd.Flags().Changed("all") {
pools, err = cli.ListAllPools()
} else {
@ -102,7 +108,7 @@ var poolShowCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {
@ -130,7 +136,7 @@ var poolDeleteCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {
@ -156,7 +162,7 @@ var poolAddCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
tags := strings.Split(poolTags, ",")
@ -183,6 +189,8 @@ var poolAddCmd = &cobra.Command{
pool, err = cli.CreateRepoPool(poolRepository, newPoolParams)
} else if cmd.Flags().Changed("org") {
pool, err = cli.CreateOrgPool(poolOrganization, newPoolParams)
} else if cmd.Flags().Changed("enterprise") {
pool, err = cli.CreateEnterprisePool(poolEnterprise, newPoolParams)
} else {
cmd.Help()
os.Exit(0)
@ -208,7 +216,7 @@ explicitly remove them using the runner delete command.
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {
@ -270,8 +278,9 @@ explicitly remove them using the runner delete command.
func init() {
poolListCmd.Flags().StringVarP(&poolRepository, "repo", "r", "", "List all pools within this repository.")
poolListCmd.Flags().StringVarP(&poolOrganization, "org", "o", "", "List all pools withing this organization.")
poolListCmd.Flags().StringVarP(&poolEnterprise, "enterprise", "e", "", "List all pools withing this enterprise.")
poolListCmd.Flags().BoolVarP(&poolAll, "all", "a", false, "List all pools, regardless of org or repo.")
poolListCmd.MarkFlagsMutuallyExclusive("repo", "org", "all")
poolListCmd.MarkFlagsMutuallyExclusive("repo", "org", "all", "enterprise")
poolUpdateCmd.Flags().StringVar(&poolImage, "image", "", "The provider-specific image name to use for runners in this pool.")
poolUpdateCmd.Flags().StringVar(&poolFlavor, "flavor", "", "The flavor to use for this runner.")
@ -300,7 +309,8 @@ func init() {
poolAddCmd.Flags().StringVarP(&poolRepository, "repo", "r", "", "Add the new pool within this repository.")
poolAddCmd.Flags().StringVarP(&poolOrganization, "org", "o", "", "Add the new pool withing this organization.")
poolAddCmd.MarkFlagsMutuallyExclusive("repo", "org")
poolAddCmd.Flags().StringVarP(&poolEnterprise, "enterprise", "e", "", "Add the new pool withing this enterprise.")
poolAddCmd.MarkFlagsMutuallyExclusive("repo", "org", "enterprise")
poolCmd.AddCommand(
poolListCmd,

View file

@ -55,7 +55,7 @@ file of the garm client.
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if cfg == nil {
@ -76,7 +76,7 @@ var profileDeleteCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {
@ -101,7 +101,7 @@ var poolSwitchCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {
@ -177,7 +177,7 @@ installation, by performing a login.
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if cfg == nil {

View file

@ -45,7 +45,7 @@ func init() {
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
providers, err := cli.ListProviders()

View file

@ -37,7 +37,7 @@ var repoRunnerListCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {

View file

@ -62,7 +62,7 @@ var repoPoolAddCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {
@ -97,41 +97,13 @@ var repoPoolAddCmd = &cobra.Command{
},
}
var repoPoolListCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List repository pools",
Long: `List all configured pools for a given repository.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires a repository ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
pools, err := cli.ListRepoPools(args[0])
if err != nil {
return err
}
formatPools(pools)
return nil
},
}
var repoPoolShowCmd = &cobra.Command{
Use: "show",
Short: "Show details for one pool",
Long: `Displays detailed information about a single pool.`,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) < 2 || len(args) > 2 {
@ -156,7 +128,7 @@ var repoPoolDeleteCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) < 2 || len(args) > 2 {
return fmt.Errorf("command requires repoID and poolID")
@ -181,7 +153,7 @@ explicitly remove them using the runner delete command.
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) < 2 || len(args) > 2 {
@ -286,6 +258,9 @@ func formatPools(pools []params.Pool) {
} else if pool.OrgID != "" && pool.OrgName != "" {
belongsTo = pool.OrgName
level = "org"
} else if pool.EnterpriseID != "" && pool.EnterpriseName != "" {
belongsTo = pool.EnterpriseName
level = "enterprise"
}
t.AppendRow(table.Row{pool.ID, pool.Image, pool.Flavor, strings.Join(tags, " "), belongsTo, level, pool.Enabled})
t.AppendSeparator()
@ -313,6 +288,9 @@ func formatOnePool(pool params.Pool) {
} else if pool.OrgID != "" && pool.OrgName != "" {
belongsTo = pool.OrgName
level = "org"
} else if pool.EnterpriseID != "" && pool.EnterpriseName != "" {
belongsTo = pool.EnterpriseName
level = "enterprise"
}
t.AppendHeader(header)

View file

@ -51,7 +51,7 @@ var repoAddCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
newRepoReq := params.CreateRepoParams{
@ -77,7 +77,7 @@ var repoListCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
repos, err := cli.ListRepositories()
@ -96,7 +96,7 @@ var repoShowCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires a repository ID")
@ -121,30 +121,7 @@ var repoDeleteCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires a repository ID")
}
if len(args) > 1 {
return fmt.Errorf("too many arguments")
}
if err := cli.DeleteRepository(args[0]); err != nil {
return err
}
return nil
},
}
var repoInstanceListCmd = &cobra.Command{
Use: "delete",
Aliases: []string{"remove", "rm", "del"},
Short: "Removes one repository",
Long: `Delete one repository from the manager.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {
return fmt.Errorf("requires a repository ID")
@ -181,10 +158,10 @@ func init() {
func formatRepositories(repos []params.Repository) {
t := table.NewWriter()
header := table.Row{"ID", "Owner", "Name", "Credentials name"}
header := table.Row{"ID", "Owner", "Name", "Credentials name", "Pool mgr running"}
t.AppendHeader(header)
for _, val := range repos {
t.AppendRow(table.Row{val.ID, val.Owner, val.Name, val.CredentialsName})
t.AppendRow(table.Row{val.ID, val.Owner, val.Name, val.CredentialsName, val.PoolManagerStatus.IsRunning})
t.AppendSeparator()
}
fmt.Println(t.Render())
@ -199,6 +176,10 @@ func formatOneRepository(repo params.Repository) {
t.AppendRow(table.Row{"Owner", repo.Owner})
t.AppendRow(table.Row{"Name", repo.Name})
t.AppendRow(table.Row{"Credentials", repo.CredentialsName})
t.AppendRow(table.Row{"Pool manager running", repo.PoolManagerStatus.IsRunning})
if !repo.PoolManagerStatus.IsRunning {
t.AppendRow(table.Row{"Failure reason", repo.PoolManagerStatus.FailureReason})
}
if len(repo.Pools) > 0 {
for _, pool := range repo.Pools {

View file

@ -26,13 +26,13 @@ import (
var Version string
var (
cfg *config.Config
mgr config.Manager
cli *client.Client
active string
needsInit bool
debug bool
needsInitError = fmt.Errorf("Please log into a garm installation first")
cfg *config.Config
mgr config.Manager
cli *client.Client
active string
needsInit bool
debug bool
errNeedsInitError = fmt.Errorf("please log into a garm installation first")
)
// rootCmd represents the base command when called without any subcommands

View file

@ -26,6 +26,7 @@ import (
var (
runnerRepository string
runnerOrganization string
runnerEnterprise string
runnerAll bool
forceRemove bool
)
@ -61,6 +62,9 @@ Example:
List runners from one org:
garm-cli runner list --org=5493e51f-3170-4ce3-9f05-3fe690fc6ec6
List runners from one enterprise:
garm-cli runner list --enterprise=a966188b-0e05-4edc-9b82-bc81a1fd38ed
List all runners from all pools belonging to all repos and orgs:
garm-cli runner list --all
@ -68,7 +72,7 @@ Example:
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
var instances []params.Instance
@ -78,9 +82,10 @@ Example:
case 1:
if cmd.Flags().Changed("repo") ||
cmd.Flags().Changed("org") ||
cmd.Flags().Changed("enterprise") ||
cmd.Flags().Changed("all") {
return fmt.Errorf("specifying a pool ID and any of [all org repo] are mutually exclusive")
return fmt.Errorf("specifying a pool ID and any of [all org repo enterprise] are mutually exclusive")
}
instances, err = cli.ListPoolInstances(args[0])
case 0:
@ -88,6 +93,8 @@ Example:
instances, err = cli.ListRepoInstances(runnerRepository)
} else if cmd.Flags().Changed("org") {
instances, err = cli.ListOrgInstances(runnerOrganization)
} else if cmd.Flags().Changed("enterprise") {
instances, err = cli.ListEnterpriseInstances(runnerEnterprise)
} else if cmd.Flags().Changed("all") {
instances, err = cli.ListAllInstances()
} else {
@ -114,7 +121,7 @@ var runnerShowCmd = &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {
@ -151,7 +158,7 @@ to either cancel the workflow or wait for it to finish.
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return needsInitError
return errNeedsInitError
}
if len(args) == 0 {
@ -172,8 +179,9 @@ to either cancel the workflow or wait for it to finish.
func init() {
runnerListCmd.Flags().StringVarP(&runnerRepository, "repo", "r", "", "List all runners from all pools within this repository.")
runnerListCmd.Flags().StringVarP(&runnerOrganization, "org", "o", "", "List all runners from all pools withing this organization.")
runnerListCmd.Flags().StringVarP(&runnerEnterprise, "enterprise", "e", "", "List all runners from all pools withing this enterprise.")
runnerListCmd.Flags().BoolVarP(&runnerAll, "all", "a", false, "List all runners, regardless of org or repo.")
runnerListCmd.MarkFlagsMutuallyExclusive("repo", "org", "all")
runnerListCmd.MarkFlagsMutuallyExclusive("repo", "org", "enterprise", "all")
runnerDeleteCmd.Flags().BoolVarP(&forceRemove, "force-remove-runner", "f", false, "Confirm you want to delete a runner")
runnerDeleteCmd.MarkFlagsMutuallyExclusive("force-remove-runner")

View file

@ -18,7 +18,6 @@ import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"log"
"net"
"os"
@ -66,7 +65,13 @@ const (
// of time and no new updates have been made to it's state, it will be removed.
DefaultRunnerBootstrapTimeout = 20
GithubBaseURL = "https://github.com"
// DefaultGithubURL is the default URL where Github or Github Enterprise can be accessed.
DefaultGithubURL = "https://github.com"
// defaultBaseURL is the default URL for the github API.
defaultBaseURL = "https://api.github.com/"
// uploadBaseURL is the default URL for guthub uploads.
uploadBaseURL = "https://uploads.github.com/"
)
var (
@ -190,15 +195,69 @@ func (d *Default) Validate() error {
// Github hold configuration options specific to interacting with github.
// Currently that is just a OAuth2 personal token.
type Github struct {
Name string `toml:"name" json:"name"`
Description string `toml:"description" json:"description"`
OAuth2Token string `toml:"oauth2_token" json:"oauth2-token"`
Name string `toml:"name" json:"name"`
Description string `toml:"description" json:"description"`
OAuth2Token string `toml:"oauth2_token" json:"oauth2-token"`
APIBaseURL string `toml:"api_base_url" json:"api-base-url"`
UploadBaseURL string `toml:"upload_base_url" json:"upload-base-url"`
BaseURL string `toml:"base_url" json:"base-url"`
// CACertBundlePath is the path on disk to a CA certificate bundle that
// can validate the endpoints defined above. Leave empty if not using a
// self signed certificate.
CACertBundlePath string `toml:"ca_cert_bundle" json:"ca-cert-bundle"`
}
func (g *Github) APIEndpoint() string {
if g.APIBaseURL != "" {
return g.APIBaseURL
}
return defaultBaseURL
}
func (g *Github) CACertBundle() ([]byte, error) {
if g.CACertBundlePath == "" {
// No CA bundle defined.
return nil, nil
}
if _, err := os.Stat(g.CACertBundlePath); err != nil {
return nil, errors.Wrap(err, "accessing CA bundle")
}
contents, err := os.ReadFile(g.CACertBundlePath)
if err != nil {
return nil, errors.Wrap(err, "reading CA bundle")
}
roots := x509.NewCertPool()
if ok := roots.AppendCertsFromPEM(contents); !ok {
return nil, fmt.Errorf("failed to parse CA cert bundle")
}
return contents, nil
}
func (g *Github) UploadEndpoint() string {
if g.UploadBaseURL == "" {
if g.APIBaseURL != "" {
return g.APIBaseURL
}
return uploadBaseURL
}
return g.UploadBaseURL
}
func (g *Github) BaseEndpoint() string {
if g.BaseURL != "" {
return g.BaseURL
}
return DefaultGithubURL
}
func (g *Github) Validate() error {
if g.OAuth2Token == "" {
return fmt.Errorf("missing github oauth2 token")
}
return nil
}
@ -372,7 +431,7 @@ func (t *TLSConfig) TLSConfig() (*tls.Config, error) {
var roots *x509.CertPool
if t.CACert != "" {
caCertPEM, err := ioutil.ReadFile(t.CACert)
caCertPEM, err := os.ReadFile(t.CACert)
if err != nil {
return nil, err
}

View file

@ -19,7 +19,7 @@ import (
"garm/params"
)
type Store interface {
type RepoStore interface {
CreateRepository(ctx context.Context, owner, name, credentialsName, webhookSecret string) (params.Repository, error)
GetRepository(ctx context.Context, owner, name string) (params.Repository, error)
GetRepositoryByID(ctx context.Context, repoID string) (params.Repository, error)
@ -27,6 +27,18 @@ type Store interface {
DeleteRepository(ctx context.Context, repoID string) error
UpdateRepository(ctx context.Context, repoID string, param params.UpdateRepositoryParams) (params.Repository, error)
CreateRepositoryPool(ctx context.Context, repoId string, param params.CreatePoolParams) (params.Pool, error)
GetRepositoryPool(ctx context.Context, repoID, poolID string) (params.Pool, error)
DeleteRepositoryPool(ctx context.Context, repoID, poolID string) error
UpdateRepositoryPool(ctx context.Context, repoID, poolID string, param params.UpdatePoolParams) (params.Pool, error)
FindRepositoryPoolByTags(ctx context.Context, repoID string, tags []string) (params.Pool, error)
ListRepoPools(ctx context.Context, repoID string) ([]params.Pool, error)
ListRepoInstances(ctx context.Context, repoID string) ([]params.Instance, error)
}
type OrgStore interface {
CreateOrganization(ctx context.Context, name, credentialsName, webhookSecret string) (params.Organization, error)
GetOrganization(ctx context.Context, name string) (params.Organization, error)
GetOrganizationByID(ctx context.Context, orgID string) (params.Organization, error)
@ -34,53 +46,77 @@ type Store interface {
DeleteOrganization(ctx context.Context, orgID string) error
UpdateOrganization(ctx context.Context, orgID string, param params.UpdateRepositoryParams) (params.Organization, error)
CreateRepositoryPool(ctx context.Context, repoId string, param params.CreatePoolParams) (params.Pool, error)
CreateOrganizationPool(ctx context.Context, orgId string, param params.CreatePoolParams) (params.Pool, error)
GetRepositoryPool(ctx context.Context, repoID, poolID string) (params.Pool, error)
GetOrganizationPool(ctx context.Context, orgID, poolID string) (params.Pool, error)
DeleteOrganizationPool(ctx context.Context, orgID, poolID string) error
UpdateOrganizationPool(ctx context.Context, orgID, poolID string, param params.UpdatePoolParams) (params.Pool, error)
ListRepoPools(ctx context.Context, repoID string) ([]params.Pool, error)
FindOrganizationPoolByTags(ctx context.Context, orgID string, tags []string) (params.Pool, error)
ListOrgPools(ctx context.Context, orgID string) ([]params.Pool, error)
ListOrgInstances(ctx context.Context, orgID string) ([]params.Instance, error)
}
type EnterpriseStore interface {
CreateEnterprise(ctx context.Context, name, credentialsName, webhookSecret string) (params.Enterprise, error)
GetEnterprise(ctx context.Context, name string) (params.Enterprise, error)
GetEnterpriseByID(ctx context.Context, enterpriseID string) (params.Enterprise, error)
ListEnterprises(ctx context.Context) ([]params.Enterprise, error)
DeleteEnterprise(ctx context.Context, enterpriseID string) error
UpdateEnterprise(ctx context.Context, enterpriseID string, param params.UpdateRepositoryParams) (params.Enterprise, error)
CreateEnterprisePool(ctx context.Context, enterpriseID string, param params.CreatePoolParams) (params.Pool, error)
GetEnterprisePool(ctx context.Context, enterpriseID, poolID string) (params.Pool, error)
DeleteEnterprisePool(ctx context.Context, enterpriseID, poolID string) error
UpdateEnterprisePool(ctx context.Context, enterpriseID, poolID string, param params.UpdatePoolParams) (params.Pool, error)
FindEnterprisePoolByTags(ctx context.Context, enterpriseID string, tags []string) (params.Pool, error)
ListEnterprisePools(ctx context.Context, enterpriseID string) ([]params.Pool, error)
ListEnterpriseInstances(ctx context.Context, enterpriseID string) ([]params.Instance, error)
}
type PoolStore interface {
// Probably a bad idea without some king of filter or at least pagination
// TODO: add filter/pagination
ListAllPools(ctx context.Context) ([]params.Pool, error)
GetPoolByID(ctx context.Context, poolID string) (params.Pool, error)
DeletePoolByID(ctx context.Context, poolID string) error
DeleteRepositoryPool(ctx context.Context, repoID, poolID string) error
DeleteOrganizationPool(ctx context.Context, orgID, poolID string) error
UpdateRepositoryPool(ctx context.Context, repoID, poolID string, param params.UpdatePoolParams) (params.Pool, error)
UpdateOrganizationPool(ctx context.Context, orgID, poolID string, param params.UpdatePoolParams) (params.Pool, error)
FindRepositoryPoolByTags(ctx context.Context, repoID string, tags []string) (params.Pool, error)
FindOrganizationPoolByTags(ctx context.Context, orgID string, tags []string) (params.Pool, error)
CreateInstance(ctx context.Context, poolID string, param params.CreateInstanceParams) (params.Instance, error)
DeleteInstance(ctx context.Context, poolID string, instanceName string) error
UpdateInstance(ctx context.Context, instanceID string, param params.UpdateInstanceParams) (params.Instance, error)
ListPoolInstances(ctx context.Context, poolID string) ([]params.Instance, error)
ListRepoInstances(ctx context.Context, repoID string) ([]params.Instance, error)
ListOrgInstances(ctx context.Context, orgID string) ([]params.Instance, error)
PoolInstanceCount(ctx context.Context, poolID string) (int64, error)
// Probably a bad idea without some king of filter or at least pagination
// TODO: add filter/pagination
ListAllInstances(ctx context.Context) ([]params.Instance, error)
GetPoolInstanceByName(ctx context.Context, poolID string, instanceName string) (params.Instance, error)
GetInstanceByName(ctx context.Context, instanceName string) (params.Instance, error)
AddInstanceStatusMessage(ctx context.Context, instanceID string, statusMessage string) error
}
type UserStore interface {
GetUser(ctx context.Context, user string) (params.User, error)
GetUserByID(ctx context.Context, userID string) (params.User, error)
CreateUser(ctx context.Context, user params.NewUserParams) (params.User, error)
UpdateUser(ctx context.Context, user string, param params.UpdateUserParams) (params.User, error)
HasAdminUser(ctx context.Context) bool
}
type InstanceStore interface {
CreateInstance(ctx context.Context, poolID string, param params.CreateInstanceParams) (params.Instance, error)
DeleteInstance(ctx context.Context, poolID string, instanceName string) error
UpdateInstance(ctx context.Context, instanceID string, param params.UpdateInstanceParams) (params.Instance, error)
// Probably a bad idea without some king of filter or at least pagination
// TODO: add filter/pagination
ListAllInstances(ctx context.Context) ([]params.Instance, error)
GetInstanceByName(ctx context.Context, instanceName string) (params.Instance, error)
AddInstanceStatusMessage(ctx context.Context, instanceID string, statusMessage string) error
}
//go:generate mockery --name=Store
type Store interface {
RepoStore
OrgStore
EnterpriseStore
PoolStore
UserStore
InstanceStore
ControllerInfo() (params.ControllerInfo, error)
InitController() (params.ControllerInfo, error)

View file

@ -49,6 +49,48 @@ func (_m *Store) ControllerInfo() (params.ControllerInfo, error) {
return r0, r1
}
// CreateEnterprise provides a mock function with given fields: ctx, name, credentialsName, webhookSecret
func (_m *Store) CreateEnterprise(ctx context.Context, name string, credentialsName string, webhookSecret string) (params.Enterprise, error) {
ret := _m.Called(ctx, name, credentialsName, webhookSecret)
var r0 params.Enterprise
if rf, ok := ret.Get(0).(func(context.Context, string, string, string) params.Enterprise); ok {
r0 = rf(ctx, name, credentialsName, webhookSecret)
} else {
r0 = ret.Get(0).(params.Enterprise)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok {
r1 = rf(ctx, name, credentialsName, webhookSecret)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateEnterprisePool provides a mock function with given fields: ctx, enterpriseID, param
func (_m *Store) CreateEnterprisePool(ctx context.Context, enterpriseID string, param params.CreatePoolParams) (params.Pool, error) {
ret := _m.Called(ctx, enterpriseID, param)
var r0 params.Pool
if rf, ok := ret.Get(0).(func(context.Context, string, params.CreatePoolParams) params.Pool); ok {
r0 = rf(ctx, enterpriseID, param)
} else {
r0 = ret.Get(0).(params.Pool)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, params.CreatePoolParams) error); ok {
r1 = rf(ctx, enterpriseID, param)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateInstance provides a mock function with given fields: ctx, poolID, param
func (_m *Store) CreateInstance(ctx context.Context, poolID string, param params.CreateInstanceParams) (params.Instance, error) {
ret := _m.Called(ctx, poolID, param)
@ -175,6 +217,34 @@ func (_m *Store) CreateUser(ctx context.Context, user params.NewUserParams) (par
return r0, r1
}
// DeleteEnterprise provides a mock function with given fields: ctx, enterpriseID
func (_m *Store) DeleteEnterprise(ctx context.Context, enterpriseID string) error {
ret := _m.Called(ctx, enterpriseID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, enterpriseID)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteEnterprisePool provides a mock function with given fields: ctx, enterpriseID, poolID
func (_m *Store) DeleteEnterprisePool(ctx context.Context, enterpriseID string, poolID string) error {
ret := _m.Called(ctx, enterpriseID, poolID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
r0 = rf(ctx, enterpriseID, poolID)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteInstance provides a mock function with given fields: ctx, poolID, instanceName
func (_m *Store) DeleteInstance(ctx context.Context, poolID string, instanceName string) error {
ret := _m.Called(ctx, poolID, instanceName)
@ -259,6 +329,27 @@ func (_m *Store) DeleteRepositoryPool(ctx context.Context, repoID string, poolID
return r0
}
// FindEnterprisePoolByTags provides a mock function with given fields: ctx, enterpriseID, tags
func (_m *Store) FindEnterprisePoolByTags(ctx context.Context, enterpriseID string, tags []string) (params.Pool, error) {
ret := _m.Called(ctx, enterpriseID, tags)
var r0 params.Pool
if rf, ok := ret.Get(0).(func(context.Context, string, []string) params.Pool); ok {
r0 = rf(ctx, enterpriseID, tags)
} else {
r0 = ret.Get(0).(params.Pool)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, []string) error); ok {
r1 = rf(ctx, enterpriseID, tags)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FindOrganizationPoolByTags provides a mock function with given fields: ctx, orgID, tags
func (_m *Store) FindOrganizationPoolByTags(ctx context.Context, orgID string, tags []string) (params.Pool, error) {
ret := _m.Called(ctx, orgID, tags)
@ -301,6 +392,69 @@ func (_m *Store) FindRepositoryPoolByTags(ctx context.Context, repoID string, ta
return r0, r1
}
// GetEnterprise provides a mock function with given fields: ctx, name
func (_m *Store) GetEnterprise(ctx context.Context, name string) (params.Enterprise, error) {
ret := _m.Called(ctx, name)
var r0 params.Enterprise
if rf, ok := ret.Get(0).(func(context.Context, string) params.Enterprise); ok {
r0 = rf(ctx, name)
} else {
r0 = ret.Get(0).(params.Enterprise)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetEnterpriseByID provides a mock function with given fields: ctx, enterpriseID
func (_m *Store) GetEnterpriseByID(ctx context.Context, enterpriseID string) (params.Enterprise, error) {
ret := _m.Called(ctx, enterpriseID)
var r0 params.Enterprise
if rf, ok := ret.Get(0).(func(context.Context, string) params.Enterprise); ok {
r0 = rf(ctx, enterpriseID)
} else {
r0 = ret.Get(0).(params.Enterprise)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, enterpriseID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetEnterprisePool provides a mock function with given fields: ctx, enterpriseID, poolID
func (_m *Store) GetEnterprisePool(ctx context.Context, enterpriseID string, poolID string) (params.Pool, error) {
ret := _m.Called(ctx, enterpriseID, poolID)
var r0 params.Pool
if rf, ok := ret.Get(0).(func(context.Context, string, string) params.Pool); ok {
r0 = rf(ctx, enterpriseID, poolID)
} else {
r0 = ret.Get(0).(params.Pool)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = rf(ctx, enterpriseID, poolID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetInstanceByName provides a mock function with given fields: ctx, instanceName
func (_m *Store) GetInstanceByName(ctx context.Context, instanceName string) (params.Instance, error) {
ret := _m.Called(ctx, instanceName)
@ -613,6 +767,75 @@ func (_m *Store) ListAllPools(ctx context.Context) ([]params.Pool, error) {
return r0, r1
}
// ListEnterpriseInstances provides a mock function with given fields: ctx, enterpriseID
func (_m *Store) ListEnterpriseInstances(ctx context.Context, enterpriseID string) ([]params.Instance, error) {
ret := _m.Called(ctx, enterpriseID)
var r0 []params.Instance
if rf, ok := ret.Get(0).(func(context.Context, string) []params.Instance); ok {
r0 = rf(ctx, enterpriseID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]params.Instance)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, enterpriseID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListEnterprisePools provides a mock function with given fields: ctx, enterpriseID
func (_m *Store) ListEnterprisePools(ctx context.Context, enterpriseID string) ([]params.Pool, error) {
ret := _m.Called(ctx, enterpriseID)
var r0 []params.Pool
if rf, ok := ret.Get(0).(func(context.Context, string) []params.Pool); ok {
r0 = rf(ctx, enterpriseID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]params.Pool)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, enterpriseID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListEnterprises provides a mock function with given fields: ctx
func (_m *Store) ListEnterprises(ctx context.Context) ([]params.Enterprise, error) {
ret := _m.Called(ctx)
var r0 []params.Enterprise
if rf, ok := ret.Get(0).(func(context.Context) []params.Enterprise); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]params.Enterprise)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListOrgInstances provides a mock function with given fields: ctx, orgID
func (_m *Store) ListOrgInstances(ctx context.Context, orgID string) ([]params.Instance, error) {
ret := _m.Called(ctx, orgID)
@ -795,6 +1018,48 @@ func (_m *Store) PoolInstanceCount(ctx context.Context, poolID string) (int64, e
return r0, r1
}
// UpdateEnterprise provides a mock function with given fields: ctx, enterpriseID, param
func (_m *Store) UpdateEnterprise(ctx context.Context, enterpriseID string, param params.UpdateRepositoryParams) (params.Enterprise, error) {
ret := _m.Called(ctx, enterpriseID, param)
var r0 params.Enterprise
if rf, ok := ret.Get(0).(func(context.Context, string, params.UpdateRepositoryParams) params.Enterprise); ok {
r0 = rf(ctx, enterpriseID, param)
} else {
r0 = ret.Get(0).(params.Enterprise)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, params.UpdateRepositoryParams) error); ok {
r1 = rf(ctx, enterpriseID, param)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateEnterprisePool provides a mock function with given fields: ctx, enterpriseID, poolID, param
func (_m *Store) UpdateEnterprisePool(ctx context.Context, enterpriseID string, poolID string, param params.UpdatePoolParams) (params.Pool, error) {
ret := _m.Called(ctx, enterpriseID, poolID, param)
var r0 params.Pool
if rf, ok := ret.Get(0).(func(context.Context, string, string, params.UpdatePoolParams) params.Pool); ok {
r0 = rf(ctx, enterpriseID, poolID, param)
} else {
r0 = ret.Get(0).(params.Pool)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, string, params.UpdatePoolParams) error); ok {
r1 = rf(ctx, enterpriseID, poolID, param)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateInstance provides a mock function with given fields: ctx, instanceID, param
func (_m *Store) UpdateInstance(ctx context.Context, instanceID string, param params.UpdateInstanceParams) (params.Instance, error) {
ret := _m.Called(ctx, instanceID, param)

367
database/sql/enterprise.go Normal file
View file

@ -0,0 +1,367 @@
package sql
import (
"context"
"fmt"
runnerErrors "garm/errors"
"garm/params"
"garm/util"
"github.com/pkg/errors"
uuid "github.com/satori/go.uuid"
"gorm.io/gorm"
)
func (s *sqlDatabase) CreateEnterprise(ctx context.Context, name, credentialsName, webhookSecret string) (params.Enterprise, error) {
secret := []byte{}
var err error
if webhookSecret != "" {
secret, err = util.Aes256EncodeString(webhookSecret, s.cfg.Passphrase)
if err != nil {
return params.Enterprise{}, fmt.Errorf("failed to encrypt string")
}
}
newEnterprise := Enterprise{
Name: name,
WebhookSecret: secret,
CredentialsName: credentialsName,
}
q := s.conn.Create(&newEnterprise)
if q.Error != nil {
return params.Enterprise{}, errors.Wrap(q.Error, "creating enterprise")
}
param := s.sqlToCommonEnterprise(newEnterprise)
param.WebhookSecret = webhookSecret
return param, nil
}
func (s *sqlDatabase) GetEnterprise(ctx context.Context, name string) (params.Enterprise, error) {
enterprise, err := s.getEnterprise(ctx, name)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}
param := s.sqlToCommonEnterprise(enterprise)
secret, err := util.Aes256DecodeString(enterprise.WebhookSecret, s.cfg.Passphrase)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "decrypting secret")
}
param.WebhookSecret = secret
return param, nil
}
func (s *sqlDatabase) GetEnterpriseByID(ctx context.Context, enterpriseID string) (params.Enterprise, error) {
enterprise, err := s.getEnterpriseByID(ctx, enterpriseID, "Pools")
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}
param := s.sqlToCommonEnterprise(enterprise)
secret, err := util.Aes256DecodeString(enterprise.WebhookSecret, s.cfg.Passphrase)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "decrypting secret")
}
param.WebhookSecret = secret
return param, nil
}
func (s *sqlDatabase) ListEnterprises(ctx context.Context) ([]params.Enterprise, error) {
var enterprises []Enterprise
q := s.conn.Find(&enterprises)
if q.Error != nil {
return []params.Enterprise{}, errors.Wrap(q.Error, "fetching enterprise from database")
}
ret := make([]params.Enterprise, len(enterprises))
for idx, val := range enterprises {
ret[idx] = s.sqlToCommonEnterprise(val)
}
return ret, nil
}
func (s *sqlDatabase) DeleteEnterprise(ctx context.Context, enterpriseID string) error {
enterprise, err := s.getEnterpriseByID(ctx, enterpriseID)
if err != nil {
return errors.Wrap(err, "fetching enterprise")
}
q := s.conn.Unscoped().Delete(&enterprise)
if q.Error != nil && !errors.Is(q.Error, gorm.ErrRecordNotFound) {
return errors.Wrap(q.Error, "deleting enterprise")
}
return nil
}
func (s *sqlDatabase) UpdateEnterprise(ctx context.Context, enterpriseID string, param params.UpdateRepositoryParams) (params.Enterprise, error) {
enterprise, err := s.getEnterpriseByID(ctx, enterpriseID)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}
if param.CredentialsName != "" {
enterprise.CredentialsName = param.CredentialsName
}
if param.WebhookSecret != "" {
secret, err := util.Aes256EncodeString(param.WebhookSecret, s.cfg.Passphrase)
if err != nil {
return params.Enterprise{}, fmt.Errorf("failed to encrypt string")
}
enterprise.WebhookSecret = secret
}
q := s.conn.Save(&enterprise)
if q.Error != nil {
return params.Enterprise{}, errors.Wrap(q.Error, "saving enterprise")
}
newParams := s.sqlToCommonEnterprise(enterprise)
secret, err := util.Aes256DecodeString(enterprise.WebhookSecret, s.cfg.Passphrase)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "decrypting secret")
}
newParams.WebhookSecret = secret
return newParams, nil
}
func (s *sqlDatabase) CreateEnterprisePool(ctx context.Context, enterpriseID string, param params.CreatePoolParams) (params.Pool, error) {
if len(param.Tags) == 0 {
return params.Pool{}, runnerErrors.NewBadRequestError("no tags specified")
}
enterprise, err := s.getEnterpriseByID(ctx, enterpriseID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching enterprise")
}
newPool := Pool{
ProviderName: param.ProviderName,
MaxRunners: param.MaxRunners,
MinIdleRunners: param.MinIdleRunners,
Image: param.Image,
Flavor: param.Flavor,
OSType: param.OSType,
OSArch: param.OSArch,
EnterpriseID: enterprise.ID,
Enabled: param.Enabled,
RunnerBootstrapTimeout: param.RunnerBootstrapTimeout,
}
_, err = s.getEnterprisePoolByUniqueFields(ctx, enterpriseID, newPool.ProviderName, newPool.Image, newPool.Flavor)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
return params.Pool{}, errors.Wrap(err, "creating pool")
}
} else {
return params.Pool{}, runnerErrors.NewConflictError("pool with the same image and flavor already exists on this provider")
}
tags := []Tag{}
for _, val := range param.Tags {
t, err := s.getOrCreateTag(val)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching tag")
}
tags = append(tags, t)
}
q := s.conn.Create(&newPool)
if q.Error != nil {
return params.Pool{}, errors.Wrap(q.Error, "adding pool")
}
for _, tt := range tags {
if err := s.conn.Model(&newPool).Association("Tags").Append(&tt); err != nil {
return params.Pool{}, errors.Wrap(err, "saving tag")
}
}
pool, err := s.getPoolByID(ctx, newPool.ID.String(), "Tags", "Instances", "Enterprise", "Organization", "Repository")
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}
return s.sqlToCommonPool(pool), nil
}
func (s *sqlDatabase) GetEnterprisePool(ctx context.Context, enterpriseID, poolID string) (params.Pool, error) {
pool, err := s.getEnterprisePool(ctx, enterpriseID, poolID, "Tags", "Instances")
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}
return s.sqlToCommonPool(pool), nil
}
func (s *sqlDatabase) DeleteEnterprisePool(ctx context.Context, enterpriseID, poolID string) error {
pool, err := s.getEnterprisePool(ctx, enterpriseID, poolID)
if err != nil {
return errors.Wrap(err, "looking up enterprise pool")
}
q := s.conn.Unscoped().Delete(&pool)
if q.Error != nil && !errors.Is(q.Error, gorm.ErrRecordNotFound) {
return errors.Wrap(q.Error, "deleting pool")
}
return nil
}
func (s *sqlDatabase) UpdateEnterprisePool(ctx context.Context, enterpriseID, poolID string, param params.UpdatePoolParams) (params.Pool, error) {
pool, err := s.getEnterprisePool(ctx, enterpriseID, poolID, "Tags", "Instances", "Enterprise", "Organization", "Repository")
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}
return s.updatePool(pool, param)
}
func (s *sqlDatabase) FindEnterprisePoolByTags(ctx context.Context, enterpriseID string, tags []string) (params.Pool, error) {
pool, err := s.findPoolByTags(enterpriseID, "enterprise_id", tags)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}
return pool, nil
}
func (s *sqlDatabase) ListEnterprisePools(ctx context.Context, enterpriseID string) ([]params.Pool, error) {
pools, err := s.getEnterprisePools(ctx, enterpriseID, "Tags", "Enterprise")
if err != nil {
return nil, errors.Wrap(err, "fetching pools")
}
ret := make([]params.Pool, len(pools))
for idx, pool := range pools {
ret[idx] = s.sqlToCommonPool(pool)
}
return ret, nil
}
func (s *sqlDatabase) ListEnterpriseInstances(ctx context.Context, enterpriseID string) ([]params.Instance, error) {
pools, err := s.getEnterprisePools(ctx, enterpriseID, "Instances")
if err != nil {
return nil, errors.Wrap(err, "fetching enterprise")
}
ret := []params.Instance{}
for _, pool := range pools {
for _, instance := range pool.Instances {
ret = append(ret, s.sqlToParamsInstance(instance))
}
}
return ret, nil
}
func (s *sqlDatabase) getEnterprise(ctx context.Context, name string) (Enterprise, error) {
var enterprise Enterprise
q := s.conn.Where("name = ? COLLATE NOCASE", name)
q = q.First(&enterprise)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return Enterprise{}, runnerErrors.ErrNotFound
}
return Enterprise{}, errors.Wrap(q.Error, "fetching enterprise from database")
}
return enterprise, nil
}
func (s *sqlDatabase) getEnterpriseByID(ctx context.Context, id string, preload ...string) (Enterprise, error) {
u, err := uuid.FromString(id)
if err != nil {
return Enterprise{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
}
var enterprise Enterprise
q := s.conn
if len(preload) > 0 {
for _, field := range preload {
q = q.Preload(field)
}
}
q = q.Where("id = ?", u).First(&enterprise)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return Enterprise{}, runnerErrors.ErrNotFound
}
return Enterprise{}, errors.Wrap(q.Error, "fetching enterprise from database")
}
return enterprise, nil
}
func (s *sqlDatabase) getEnterprisePoolByUniqueFields(ctx context.Context, enterpriseID string, provider, image, flavor string) (Pool, error) {
enterprise, err := s.getEnterpriseByID(ctx, enterpriseID)
if err != nil {
return Pool{}, errors.Wrap(err, "fetching enterprise")
}
q := s.conn
var pool []Pool
err = q.Model(&enterprise).Association("Pools").Find(&pool, "provider_name = ? and image = ? and flavor = ?", provider, image, flavor)
if err != nil {
return Pool{}, errors.Wrap(err, "fetching pool")
}
if len(pool) == 0 {
return Pool{}, runnerErrors.ErrNotFound
}
return pool[0], nil
}
func (s *sqlDatabase) getEnterprisePool(ctx context.Context, enterpriseID, poolID string, preload ...string) (Pool, error) {
enterprise, err := s.getEnterpriseByID(ctx, enterpriseID)
if err != nil {
return Pool{}, errors.Wrap(err, "fetching enterprise")
}
u, err := uuid.FromString(poolID)
if err != nil {
return Pool{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
}
q := s.conn
if len(preload) > 0 {
for _, item := range preload {
q = q.Preload(item)
}
}
var pool []Pool
err = q.Model(&enterprise).Association("Pools").Find(&pool, "id = ?", u)
if err != nil {
return Pool{}, errors.Wrap(err, "fetching pool")
}
if len(pool) == 0 {
return Pool{}, runnerErrors.ErrNotFound
}
return pool[0], nil
}
func (s *sqlDatabase) getEnterprisePools(ctx context.Context, enterpriseID string, preload ...string) ([]Pool, error) {
enterprise, err := s.getEnterpriseByID(ctx, enterpriseID)
if err != nil {
return nil, errors.Wrap(err, "fetching enterprise")
}
var pools []Pool
q := s.conn.Model(&enterprise)
if len(preload) > 0 {
for _, item := range preload {
q = q.Preload(item)
}
}
err = q.Association("Pools").Find(&pools)
if err != nil {
return nil, errors.Wrap(err, "fetching pool")
}
return pools, nil
}

View file

@ -71,6 +71,9 @@ type Pool struct {
OrgID uuid.UUID `gorm:"index"`
Organization Organization `gorm:"foreignKey:OrgID"`
EnterpriseID uuid.UUID `gorm:"index"`
Enterprise Enterprise `gorm:"foreignKey:EnterpriseID"`
Instances []Instance `gorm:"foreignKey:PoolID"`
}
@ -93,6 +96,15 @@ type Organization struct {
Pools []Pool `gorm:"foreignKey:OrgID"`
}
type Enterprise struct {
Base
CredentialsName string
Name string `gorm:"index:idx_ent_name_nocase,collate:nocase"`
WebhookSecret []byte
Pools []Pool `gorm:"foreignKey:EnterpriseID"`
}
type Address struct {
Base

View file

@ -72,7 +72,7 @@ func (s *sqlDatabase) ListOrganizations(ctx context.Context) ([]params.Organizat
var orgs []Organization
q := s.conn.Find(&orgs)
if q.Error != nil {
return []params.Organization{}, errors.Wrap(q.Error, "fetching user from database")
return []params.Organization{}, errors.Wrap(q.Error, "fetching org from database")
}
ret := make([]params.Organization, len(orgs))
@ -197,7 +197,7 @@ func (s *sqlDatabase) CreateOrganizationPool(ctx context.Context, orgId string,
}
}
pool, err := s.getPoolByID(ctx, newPool.ID.String(), "Tags", "Instances", "Organization", "Repository")
pool, err := s.getPoolByID(ctx, newPool.ID.String(), "Tags", "Instances", "Enterprise", "Organization", "Repository")
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}
@ -262,7 +262,7 @@ func (s *sqlDatabase) ListOrgInstances(ctx context.Context, orgID string) ([]par
}
func (s *sqlDatabase) UpdateOrganizationPool(ctx context.Context, orgID, poolID string, param params.UpdatePoolParams) (params.Pool, error) {
pool, err := s.getOrgPool(ctx, orgID, poolID, "Tags", "Instances", "Organization", "Repository")
pool, err := s.getOrgPool(ctx, orgID, poolID, "Tags", "Instances", "Enterprise", "Organization", "Repository")
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}

View file

@ -288,7 +288,7 @@ func (s *OrgTestSuite) TestListOrganizationsDBFetchErr() {
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("fetching user from database: fetching user from database mock error", err.Error())
s.Require().Equal("fetching org from database: fetching user from database mock error", err.Error())
}
func (s *OrgTestSuite) TestDeleteOrganization() {

View file

@ -29,6 +29,7 @@ func (s *sqlDatabase) ListAllPools(ctx context.Context) ([]params.Pool, error) {
Preload("Tags").
Preload("Organization").
Preload("Repository").
Preload("Enterprise").
Find(&pools)
if q.Error != nil {
return nil, errors.Wrap(q.Error, "fetching all pools")
@ -42,7 +43,7 @@ func (s *sqlDatabase) ListAllPools(ctx context.Context) ([]params.Pool, error) {
}
func (s *sqlDatabase) GetPoolByID(ctx context.Context, poolID string) (params.Pool, error) {
pool, err := s.getPoolByID(ctx, poolID, "Tags", "Instances", "Organization", "Repository")
pool, err := s.getPoolByID(ctx, poolID, "Tags", "Instances", "Enterprise", "Organization", "Repository")
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool by ID")
}

View file

@ -205,7 +205,7 @@ func (s *sqlDatabase) CreateRepositoryPool(ctx context.Context, repoId string, p
}
}
pool, err := s.getPoolByID(ctx, newPool.ID.String(), "Tags", "Instances", "Organization", "Repository")
pool, err := s.getPoolByID(ctx, newPool.ID.String(), "Tags", "Instances", "Enterprise", "Organization", "Repository")
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}
@ -271,7 +271,7 @@ func (s *sqlDatabase) ListRepoInstances(ctx context.Context, repoID string) ([]p
}
func (s *sqlDatabase) UpdateRepositoryPool(ctx context.Context, repoID, poolID string, param params.UpdatePoolParams) (params.Pool, error) {
pool, err := s.getRepoPool(ctx, repoID, poolID, "Tags", "Instances", "Organization", "Repository")
pool, err := s.getRepoPool(ctx, repoID, poolID, "Tags", "Instances", "Enterprise", "Organization", "Repository")
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}

View file

@ -96,13 +96,14 @@ func (s *sqlDatabase) migrateDB() error {
&Pool{},
&Repository{},
&Organization{},
&Enterprise{},
&Address{},
&InstanceStatusUpdate{},
&Instance{},
&ControllerInfo{},
&User{},
); err != nil {
return err
return errors.Wrap(err, "running auto migrate")
}
return nil

View file

@ -85,6 +85,21 @@ func (s *sqlDatabase) sqlToCommonOrganization(org Organization) params.Organizat
return ret
}
func (s *sqlDatabase) sqlToCommonEnterprise(enterprise Enterprise) params.Enterprise {
ret := params.Enterprise{
ID: enterprise.ID.String(),
Name: enterprise.Name,
CredentialsName: enterprise.CredentialsName,
Pools: make([]params.Pool, len(enterprise.Pools)),
}
for idx, pool := range enterprise.Pools {
ret.Pools[idx] = s.sqlToCommonPool(pool)
}
return ret
}
func (s *sqlDatabase) sqlToCommonPool(pool Pool) params.Pool {
ret := params.Pool{
ID: pool.ID.String(),
@ -113,6 +128,11 @@ func (s *sqlDatabase) sqlToCommonPool(pool Pool) params.Pool {
ret.OrgName = pool.Organization.Name
}
if pool.EnterpriseID != uuid.Nil && pool.Enterprise.Name != "" {
ret.EnterpriseID = pool.EnterpriseID.String()
ret.EnterpriseName = pool.Enterprise.Name
}
for idx, val := range pool.Tags {
ret.Tags[idx] = s.sqlToCommonTags(*val)
}

View file

@ -1,12 +1,19 @@
# Configuring github credentials
Garm needs a [Personal Access Token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) to create runner registration tokens, list current self hosted runners and potentially remove them if they become orphaned (the VM was manually removed on the provider).
The ```github``` config section holds credentials and API endpoint information for accessing the GitHub APIs. Credentials are tied to the instance of GitHub you're using. Whether you're using [github.com](https://github.com) or your own deployment of GitHub Enterprise server, this section is how ```garm``` knows where it should create the runners.
Tying the API endpoint info to the credentials allows us to use the same ```garm``` installation with both [github.com](https://github.com) and private deployments. All you have to do is to add the needed endpoint info (see bellow).
Garm uses a [Personal Access Token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) to create runner registration tokens, list current self hosted runners and potentially remove them if they become orphaned (the VM was manually removed on the provider).
From the list of scopes, you will need to select:
* ```public_repo``` - for access to a repository
* ```repo``` - for access to a private repository
* ```admin:org``` - if you plan on using this with an organization to which you have access
* ```manage_runners:enterprise``` - if you plan to use garm at the enterprise level
The resulting token must be configured in the ```[[github]]``` section of the config. Sample as follows:
@ -24,6 +31,21 @@ The resulting token must be configured in the ```[[github]]``` section of the co
# to work with repositories, and the admin:org needs to be set if you plan on
# adding an organization.
oauth2_token = "super secret token"
# base_url (optional) is the URL at which your GitHub Enterprise Server can be accessed.
# If these credentials are for github.com, leave this setting blank
base_url = "https://ghe.example.com"
# api_base_url (optional) is the base URL where the GitHub Enterprise Server API can be accessed.
# Leave this blank if these credentials are for github.com.
api_base_url = "https://ghe.example.com"
# upload_base_url (optional) is the base URL where the GitHub Enterprise Server upload API can be accessed.
# Leave this blank if these credentials are for github.com, or if you don't have a separate URL
# for the upload API.
upload_base_url = "https://api.ghe.example.com"
# ca_cert_bundle (optional) is the CA certificate bundle in PEM format that will be used by the github
# client to talk to the API. This bundle will also be sent to all runners as bootstrap params.
# Use this option if you're using a self signed certificate.
# Leave this blank if you're using github.com or if your certificare is signed by a valid CA.
ca_cert_bundle = "/etc/garm/ghe.crt"
```
The double paranthesis means that this is an array. You can specify the ```[[github]]``` section multiple times, with different tokens from different users, or with different access levels. You will then be able to list the available credentials using the API, and reference these credentials when adding repositories or organizations.

View file

@ -135,7 +135,7 @@ Pools are objects that define one type of worker and rules by which that pool of
Before we can create a pool, we need to list the available providers. Providers are defined in the config (see above), but we need to reference them by name in the pool.
```bash
ubuntu@experiments:~$ garm-cli provider list
ubuntu@experiments:~$ garm-cli provider list
+-----------+------------------------+------+
| NAME | DESCRIPTION | TYPE |
+-----------+------------------------+------+

2
go.mod
View file

@ -6,7 +6,7 @@ require (
github.com/BurntSushi/toml v0.4.1
github.com/go-resty/resty/v2 v2.7.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/go-github/v43 v43.0.0
github.com/google/go-github/v48 v48.0.0
github.com/google/uuid v1.3.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0

7
go.sum
View file

@ -116,9 +116,9 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/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.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-github/v43 v43.0.0 h1:y+GL7LIsAIF2NZlJ46ZoC/D1W1ivZasT0lnWHMYPZ+U=
github.com/google/go-github/v43 v43.0.0/go.mod h1:ZkTvvmCXBvsfPpTHXnH/d2hP9Y0cTbvN9kr5xqyXOIc=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-github/v48 v48.0.0 h1:9H5fWVXFK6ZsRriyPbjtnFAkJnoj0WKFtTYfpCRrTm8=
github.com/google/go-github/v48 v48.0.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/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@ -438,7 +438,6 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=

View file

@ -19,7 +19,7 @@ import (
"garm/runner/providers/common"
"time"
"github.com/google/go-github/v43/github"
"github.com/google/go-github/v48/github"
uuid "github.com/satori/go.uuid"
)
@ -98,6 +98,8 @@ type BootstrapInstance struct {
// provider supports it.
SSHKeys []string `json:"ssh-keys"`
CACertBundle []byte `json:"ca-cert-bundle"`
OSArch config.OSArch `json:"arch"`
Flavor string `json:"flavor"`
Image string `json:"image"`
@ -126,6 +128,8 @@ type Pool struct {
RepoName string `json:"repo_name,omitempty"`
OrgID string `json:"org_id,omitempty"`
OrgName string `json:"org_name,omitempty"`
EnterpriseID string `json:"enterprise_id,omitempty"`
EnterpriseName string `json:"enterprise_name,omitempty"`
RunnerBootstrapTimeout uint `json:"runner_bootstrap_timeout"`
}
@ -141,23 +145,38 @@ type Internal struct {
ControllerID string `json:"controller_id"`
InstanceCallbackURL string `json:"instance_callback_url"`
JWTSecret string `json:"jwt_secret"`
// GithubCredentialsDetails contains all info about the credentials, except the
// token, which is added above.
GithubCredentialsDetails GithubCredentials `json:"gh_creds_details"`
}
type Repository struct {
ID string `json:"id"`
Owner string `json:"owner"`
Name string `json:"name"`
Pools []Pool `json:"pool,omitempty"`
CredentialsName string `json:"credentials_name"`
ID string `json:"id"`
Owner string `json:"owner"`
Name string `json:"name"`
Pools []Pool `json:"pool,omitempty"`
CredentialsName string `json:"credentials_name"`
PoolManagerStatus PoolManagerStatus `json:"pool_manager_status,omitempty"`
// Do not serialize sensitive info.
WebhookSecret string `json:"-"`
}
type Organization struct {
ID string `json:"id"`
Name string `json:"name"`
Pools []Pool `json:"pool,omitempty"`
CredentialsName string `json:"credentials_name"`
ID string `json:"id"`
Name string `json:"name"`
Pools []Pool `json:"pool,omitempty"`
CredentialsName string `json:"credentials_name"`
PoolManagerStatus PoolManagerStatus `json:"pool_manager_status,omitempty"`
// Do not serialize sensitive info.
WebhookSecret string `json:"-"`
}
type Enterprise struct {
ID string `json:"id"`
Name string `json:"name"`
Pools []Pool `json:"pool,omitempty"`
CredentialsName string `json:"credentials_name"`
PoolManagerStatus PoolManagerStatus `json:"pool_manager_status,omitempty"`
// Do not serialize sensitive info.
WebhookSecret string `json:"-"`
}
@ -186,8 +205,12 @@ type ControllerInfo struct {
}
type GithubCredentials struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
BaseURL string `json:"base_url"`
APIBaseURL string `json:"api_base_url"`
UploadBaseURL string `json:"upload_base_url"`
CABundle []byte `json:"ca_bundle,omitempty"`
}
type Provider struct {
@ -199,3 +222,8 @@ type Provider struct {
type UpdatePoolStateParams struct {
WebhookSecret string
}
type PoolManagerStatus struct {
IsRunning bool `json:"running"`
FailureReason string `json:"failure_reason,omitempty"`
}

View file

@ -66,6 +66,23 @@ func (c *CreateOrgParams) Validate() error {
return nil
}
type CreateEnterpriseParams struct {
Name string `json:"name"`
CredentialsName string `json:"credentials_name"`
WebhookSecret string `json:"webhook_secret"`
}
func (c *CreateEnterpriseParams) Validate() error {
if c.Name == "" {
return errors.NewBadRequestError("missing org name")
}
if c.CredentialsName == "" {
return errors.NewBadRequestError("missing credentials name")
}
return nil
}
// NewUserParams holds the needed information to create
// a new user
type NewUserParams struct {

View file

@ -5,7 +5,7 @@ package mocks
import (
context "context"
github "github.com/google/go-github/v43/github"
github "github.com/google/go-github/v48/github"
mock "github.com/stretchr/testify/mock"
)
@ -78,6 +78,38 @@ func (_m *GithubClient) CreateRegistrationToken(ctx context.Context, owner strin
return r0, r1, r2
}
// GetWorkflowJobByID provides a mock function with given fields: ctx, owner, repo, jobID
func (_m *GithubClient) GetWorkflowJobByID(ctx context.Context, owner string, repo string, jobID int64) (*github.WorkflowJob, *github.Response, error) {
ret := _m.Called(ctx, owner, repo, jobID)
var r0 *github.WorkflowJob
if rf, ok := ret.Get(0).(func(context.Context, string, string, int64) *github.WorkflowJob); ok {
r0 = rf(ctx, owner, repo, jobID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.WorkflowJob)
}
}
var r1 *github.Response
if rf, ok := ret.Get(1).(func(context.Context, string, string, int64) *github.Response); ok {
r1 = rf(ctx, owner, repo, jobID)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
var r2 error
if rf, ok := ret.Get(2).(func(context.Context, string, string, int64) error); ok {
r2 = rf(ctx, owner, repo, jobID)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// ListOrganizationRunnerApplicationDownloads provides a mock function with given fields: ctx, owner
func (_m *GithubClient) ListOrganizationRunnerApplicationDownloads(ctx context.Context, owner string) ([]*github.RunnerApplicationDownload, *github.Response, error) {
ret := _m.Called(ctx, owner)

View file

@ -0,0 +1,149 @@
// Code generated by mockery v2.14.0. DO NOT EDIT.
package mocks
import (
context "context"
github "github.com/google/go-github/v48/github"
mock "github.com/stretchr/testify/mock"
)
// GithubEnterpriseClient is an autogenerated mock type for the GithubEnterpriseClient type
type GithubEnterpriseClient struct {
mock.Mock
}
// CreateRegistrationToken provides a mock function with given fields: ctx, enterprise
func (_m *GithubEnterpriseClient) CreateRegistrationToken(ctx context.Context, enterprise string) (*github.RegistrationToken, *github.Response, error) {
ret := _m.Called(ctx, enterprise)
var r0 *github.RegistrationToken
if rf, ok := ret.Get(0).(func(context.Context, string) *github.RegistrationToken); ok {
r0 = rf(ctx, enterprise)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.RegistrationToken)
}
}
var r1 *github.Response
if rf, ok := ret.Get(1).(func(context.Context, string) *github.Response); ok {
r1 = rf(ctx, enterprise)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
var r2 error
if rf, ok := ret.Get(2).(func(context.Context, string) error); ok {
r2 = rf(ctx, enterprise)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// ListRunnerApplicationDownloads provides a mock function with given fields: ctx, enterprise
func (_m *GithubEnterpriseClient) ListRunnerApplicationDownloads(ctx context.Context, enterprise string) ([]*github.RunnerApplicationDownload, *github.Response, error) {
ret := _m.Called(ctx, enterprise)
var r0 []*github.RunnerApplicationDownload
if rf, ok := ret.Get(0).(func(context.Context, string) []*github.RunnerApplicationDownload); ok {
r0 = rf(ctx, enterprise)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*github.RunnerApplicationDownload)
}
}
var r1 *github.Response
if rf, ok := ret.Get(1).(func(context.Context, string) *github.Response); ok {
r1 = rf(ctx, enterprise)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
var r2 error
if rf, ok := ret.Get(2).(func(context.Context, string) error); ok {
r2 = rf(ctx, enterprise)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// ListRunners provides a mock function with given fields: ctx, enterprise, opts
func (_m *GithubEnterpriseClient) ListRunners(ctx context.Context, enterprise string, opts *github.ListOptions) (*github.Runners, *github.Response, error) {
ret := _m.Called(ctx, enterprise, opts)
var r0 *github.Runners
if rf, ok := ret.Get(0).(func(context.Context, string, *github.ListOptions) *github.Runners); ok {
r0 = rf(ctx, enterprise, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Runners)
}
}
var r1 *github.Response
if rf, ok := ret.Get(1).(func(context.Context, string, *github.ListOptions) *github.Response); ok {
r1 = rf(ctx, enterprise, opts)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*github.Response)
}
}
var r2 error
if rf, ok := ret.Get(2).(func(context.Context, string, *github.ListOptions) error); ok {
r2 = rf(ctx, enterprise, opts)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// RemoveRunner provides a mock function with given fields: ctx, enterprise, runnerID
func (_m *GithubEnterpriseClient) RemoveRunner(ctx context.Context, enterprise string, runnerID int64) (*github.Response, error) {
ret := _m.Called(ctx, enterprise, runnerID)
var r0 *github.Response
if rf, ok := ret.Get(0).(func(context.Context, string, int64) *github.Response); ok {
r0 = rf(ctx, enterprise, runnerID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*github.Response)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, int64) error); ok {
r1 = rf(ctx, enterprise, runnerID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
type mockConstructorTestingTNewGithubEnterpriseClient interface {
mock.TestingT
Cleanup(func())
}
// NewGithubEnterpriseClient creates a new instance of GithubEnterpriseClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewGithubEnterpriseClient(t mockConstructorTestingTNewGithubEnterpriseClient) *GithubEnterpriseClient {
mock := &GithubEnterpriseClient{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View file

@ -83,6 +83,20 @@ func (_m *PoolManager) Start() error {
return r0
}
// Status provides a mock function with given fields:
func (_m *PoolManager) Status() params.PoolManagerStatus {
ret := _m.Called()
var r0 params.PoolManagerStatus
if rf, ok := ret.Get(0).(func() params.PoolManagerStatus); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(params.PoolManagerStatus)
}
return r0
}
// Stop provides a mock function with given fields:
func (_m *PoolManager) Stop() error {
ret := _m.Called()

View file

@ -27,9 +27,19 @@ const (
PoolConsilitationInterval = 5 * time.Second
PoolReapTimeoutInterval = 5 * time.Minute
PoolToolUpdateInterval = 3 * time.Hour
// Temporary tools download token is valid for 1 hour by default.
// Set this to 15 minutes. This should allow enough time even on slow
// clouds for the instance to spin up, download the tools and join gh.
PoolToolUpdateInterval = 15 * time.Minute
// UnauthorizedBackoffTimer is the time we wait before making another request
// after getting an unauthorized error from github. It is unlikely that a second
// request will not receive the same error, unless the config is changed with new
// credentials and garm is restarted.
UnauthorizedBackoffTimer = 3 * time.Hour
)
//go:generate mockery --all
type PoolManager interface {
ID() string
WebhookSecret() string
@ -41,5 +51,6 @@ type PoolManager interface {
// PoolManager lifecycle functions. Start/stop pool.
Start() error
Stop() error
Status() params.PoolManagerStatus
Wait() error
}

View file

@ -19,6 +19,7 @@ import (
"garm/params"
)
//go:generate mockery --all
type Provider interface {
// CreateInstance creates a new compute instance in the provider.
CreateInstance(ctx context.Context, bootstrapParams params.BootstrapInstance) (params.Instance, error)

View file

@ -3,11 +3,13 @@ package common
import (
"context"
"github.com/google/go-github/v43/github"
"github.com/google/go-github/v48/github"
)
// GithubClient that describes the minimum list of functions we need to interact with github.
// Allows for easier testing.
//
//go:generate mockery --all
type GithubClient interface {
// GetWorkflowJobByID gets details about a single workflow job.
GetWorkflowJobByID(ctx context.Context, owner, repo string, jobID int64) (*github.WorkflowJob, *github.Response, error)
@ -31,3 +33,15 @@ type GithubClient interface {
// CreateOrganizationRegistrationToken creates a runner registration token for an organization.
CreateOrganizationRegistrationToken(ctx context.Context, owner string) (*github.RegistrationToken, *github.Response, error)
}
type GithubEnterpriseClient interface {
// ListRunners lists all runners within a repository.
ListRunners(ctx context.Context, enterprise string, opts *github.ListOptions) (*github.Runners, *github.Response, error)
// RemoveRunner removes one runner from an enterprise.
RemoveRunner(ctx context.Context, enterprise string, runnerID int64) (*github.Response, error)
// CreateRegistrationToken creates a runner registration token for an enterprise.
CreateRegistrationToken(ctx context.Context, enterprise string) (*github.RegistrationToken, *github.Response, error)
// ListRunnerApplicationDownloads returns a list of github runner application downloads for the
// various supported operating systems and architectures.
ListRunnerApplicationDownloads(ctx context.Context, enterprise string) ([]*github.RunnerApplicationDownload, *github.Response, error)
}

333
runner/enterprises.go Normal file
View file

@ -0,0 +1,333 @@
package runner
import (
"context"
"fmt"
"garm/auth"
"garm/config"
runnerErrors "garm/errors"
"garm/params"
"garm/runner/common"
"log"
"strings"
"github.com/pkg/errors"
)
func (r *Runner) CreateEnterprise(ctx context.Context, param params.CreateEnterpriseParams) (enterprise params.Enterprise, err error) {
if !auth.IsAdmin(ctx) {
return enterprise, runnerErrors.ErrUnauthorized
}
err = param.Validate()
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "validating params")
}
creds, ok := r.credentials[param.CredentialsName]
if !ok {
return params.Enterprise{}, runnerErrors.NewBadRequestError("credentials %s not defined", param.CredentialsName)
}
_, err = r.store.GetEnterprise(ctx, param.Name)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}
} else {
return params.Enterprise{}, runnerErrors.NewConflictError("enterprise %s already exists", param.Name)
}
enterprise, err = r.store.CreateEnterprise(ctx, param.Name, creds.Name, param.WebhookSecret)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "creating enterprise")
}
defer func() {
if err != nil {
r.store.DeleteEnterprise(ctx, enterprise.ID)
}
}()
var poolMgr common.PoolManager
poolMgr, err = r.poolManagerCtrl.CreateEnterprisePoolManager(r.ctx, enterprise, r.providers, r.store)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "creating enterprise pool manager")
}
if err := poolMgr.Start(); err != nil {
if deleteErr := r.poolManagerCtrl.DeleteEnterprisePoolManager(enterprise); deleteErr != nil {
log.Printf("failed to cleanup pool manager for enterprise %s", enterprise.ID)
}
return params.Enterprise{}, errors.Wrap(err, "starting enterprise pool manager")
}
return enterprise, nil
}
func (r *Runner) ListEnterprises(ctx context.Context) ([]params.Enterprise, error) {
if !auth.IsAdmin(ctx) {
return nil, runnerErrors.ErrUnauthorized
}
enterprises, err := r.store.ListEnterprises(ctx)
if err != nil {
return nil, errors.Wrap(err, "listing enterprises")
}
var allEnterprises []params.Enterprise
for _, enterprise := range enterprises {
poolMgr, err := r.poolManagerCtrl.GetEnterprisePoolManager(enterprise)
if err != nil {
enterprise.PoolManagerStatus.IsRunning = false
enterprise.PoolManagerStatus.FailureReason = fmt.Sprintf("failed to get pool manager: %q", err)
} else {
enterprise.PoolManagerStatus = poolMgr.Status()
}
allEnterprises = append(allEnterprises, enterprise)
}
return allEnterprises, nil
}
func (r *Runner) GetEnterpriseByID(ctx context.Context, enterpriseID string) (params.Enterprise, error) {
if !auth.IsAdmin(ctx) {
return params.Enterprise{}, runnerErrors.ErrUnauthorized
}
enterprise, err := r.store.GetEnterpriseByID(ctx, enterpriseID)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}
poolMgr, err := r.poolManagerCtrl.GetEnterprisePoolManager(enterprise)
if err != nil {
enterprise.PoolManagerStatus.IsRunning = false
enterprise.PoolManagerStatus.FailureReason = fmt.Sprintf("failed to get pool manager: %q", err)
}
enterprise.PoolManagerStatus = poolMgr.Status()
return enterprise, nil
}
func (r *Runner) DeleteEnterprise(ctx context.Context, enterpriseID string) error {
if !auth.IsAdmin(ctx) {
return runnerErrors.ErrUnauthorized
}
enterprise, err := r.store.GetEnterpriseByID(ctx, enterpriseID)
if err != nil {
return errors.Wrap(err, "fetching enterprise")
}
pools, err := r.store.ListEnterprisePools(ctx, enterpriseID)
if err != nil {
return errors.Wrap(err, "fetching enterprise pools")
}
if len(pools) > 0 {
poolIds := []string{}
for _, pool := range pools {
poolIds = append(poolIds, pool.ID)
}
return runnerErrors.NewBadRequestError("enterprise has pools defined (%s)", strings.Join(poolIds, ", "))
}
if err := r.poolManagerCtrl.DeleteEnterprisePoolManager(enterprise); err != nil {
return errors.Wrap(err, "deleting enterprise pool manager")
}
if err := r.store.DeleteEnterprise(ctx, enterpriseID); err != nil {
return errors.Wrapf(err, "removing enterprise %s", enterpriseID)
}
return nil
}
func (r *Runner) UpdateEnterprise(ctx context.Context, enterpriseID string, param params.UpdateRepositoryParams) (params.Enterprise, error) {
if !auth.IsAdmin(ctx) {
return params.Enterprise{}, runnerErrors.ErrUnauthorized
}
r.mux.Lock()
defer r.mux.Unlock()
enterprise, err := r.store.GetEnterpriseByID(ctx, enterpriseID)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
}
if param.CredentialsName != "" {
// Check that credentials are set before saving to db
if _, ok := r.credentials[param.CredentialsName]; !ok {
return params.Enterprise{}, runnerErrors.NewBadRequestError("invalid credentials (%s) for enterprise %s", param.CredentialsName, enterprise.Name)
}
}
enterprise, err = r.store.UpdateEnterprise(ctx, enterpriseID, param)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "updating enterprise")
}
poolMgr, err := r.poolManagerCtrl.GetEnterprisePoolManager(enterprise)
if err != nil {
newState := params.UpdatePoolStateParams{
WebhookSecret: enterprise.WebhookSecret,
}
// stop the pool mgr
if err := poolMgr.RefreshState(newState); err != nil {
return params.Enterprise{}, errors.Wrap(err, "updating enterprise pool manager")
}
} else {
if _, err := r.poolManagerCtrl.CreateEnterprisePoolManager(r.ctx, enterprise, r.providers, r.store); err != nil {
return params.Enterprise{}, errors.Wrap(err, "creating enterprise pool manager")
}
}
return enterprise, nil
}
func (r *Runner) CreateEnterprisePool(ctx context.Context, enterpriseID string, param params.CreatePoolParams) (params.Pool, error) {
if !auth.IsAdmin(ctx) {
return params.Pool{}, runnerErrors.ErrUnauthorized
}
r.mux.Lock()
defer r.mux.Unlock()
enterprise, err := r.store.GetEnterpriseByID(ctx, enterpriseID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching enterprise")
}
if _, err := r.poolManagerCtrl.GetEnterprisePoolManager(enterprise); err != nil {
return params.Pool{}, runnerErrors.ErrNotFound
}
createPoolParams, err := r.appendTagsToCreatePoolParams(param)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool params")
}
if param.RunnerBootstrapTimeout == 0 {
param.RunnerBootstrapTimeout = config.DefaultRunnerBootstrapTimeout
}
pool, err := r.store.CreateEnterprisePool(ctx, enterpriseID, createPoolParams)
if err != nil {
return params.Pool{}, errors.Wrap(err, "creating pool")
}
return pool, nil
}
func (r *Runner) GetEnterprisePoolByID(ctx context.Context, enterpriseID, poolID string) (params.Pool, error) {
if !auth.IsAdmin(ctx) {
return params.Pool{}, runnerErrors.ErrUnauthorized
}
pool, err := r.store.GetEnterprisePool(ctx, enterpriseID, poolID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}
return pool, nil
}
func (r *Runner) DeleteEnterprisePool(ctx context.Context, enterpriseID, poolID string) error {
if !auth.IsAdmin(ctx) {
return runnerErrors.ErrUnauthorized
}
// TODO: dedup instance count verification
pool, err := r.store.GetEnterprisePool(ctx, enterpriseID, poolID)
if err != nil {
return errors.Wrap(err, "fetching pool")
}
instances, err := r.store.ListPoolInstances(ctx, pool.ID)
if err != nil {
return errors.Wrap(err, "fetching instances")
}
// TODO: implement a count function
if len(instances) > 0 {
runnerIDs := []string{}
for _, run := range instances {
runnerIDs = append(runnerIDs, run.ID)
}
return runnerErrors.NewBadRequestError("pool has runners: %s", strings.Join(runnerIDs, ", "))
}
if err := r.store.DeleteEnterprisePool(ctx, enterpriseID, poolID); err != nil {
return errors.Wrap(err, "deleting pool")
}
return nil
}
func (r *Runner) ListEnterprisePools(ctx context.Context, enterpriseID string) ([]params.Pool, error) {
if !auth.IsAdmin(ctx) {
return []params.Pool{}, runnerErrors.ErrUnauthorized
}
pools, err := r.store.ListEnterprisePools(ctx, enterpriseID)
if err != nil {
return nil, errors.Wrap(err, "fetching pools")
}
return pools, nil
}
func (r *Runner) UpdateEnterprisePool(ctx context.Context, enterpriseID, poolID string, param params.UpdatePoolParams) (params.Pool, error) {
if !auth.IsAdmin(ctx) {
return params.Pool{}, runnerErrors.ErrUnauthorized
}
pool, err := r.store.GetEnterprisePool(ctx, enterpriseID, poolID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}
maxRunners := pool.MaxRunners
minIdleRunners := pool.MinIdleRunners
if param.MaxRunners != nil {
maxRunners = *param.MaxRunners
}
if param.MinIdleRunners != nil {
minIdleRunners = *param.MinIdleRunners
}
if minIdleRunners > maxRunners {
return params.Pool{}, runnerErrors.NewBadRequestError("min_idle_runners cannot be larger than max_runners")
}
newPool, err := r.store.UpdateEnterprisePool(ctx, enterpriseID, poolID, param)
if err != nil {
return params.Pool{}, errors.Wrap(err, "updating pool")
}
return newPool, nil
}
func (r *Runner) ListEnterpriseInstances(ctx context.Context, enterpriseID string) ([]params.Instance, error) {
if !auth.IsAdmin(ctx) {
return nil, runnerErrors.ErrUnauthorized
}
instances, err := r.store.ListEnterpriseInstances(ctx, enterpriseID)
if err != nil {
return []params.Instance{}, errors.Wrap(err, "fetching instances")
}
return instances, nil
}
func (r *Runner) findEnterprisePoolManager(name string) (common.PoolManager, error) {
r.mux.Lock()
defer r.mux.Unlock()
enterprise, err := r.store.GetEnterprise(r.ctx, name)
if err != nil {
return nil, errors.Wrap(err, "fetching enterprise")
}
poolManager, err := r.poolManagerCtrl.GetEnterprisePoolManager(enterprise)
if err != nil {
return nil, errors.Wrap(err, "fetching pool manager for enterprise")
}
return poolManager, nil
}

View file

@ -21,16 +21,31 @@ import (
"garm/runner/common"
)
//go:generate mockery --name=PoolManagerController
type PoolManagerController interface {
type RepoPoolManager interface {
CreateRepoPoolManager(ctx context.Context, repo params.Repository, providers map[string]common.Provider, store dbCommon.Store) (common.PoolManager, error)
GetRepoPoolManager(repo params.Repository) (common.PoolManager, error)
DeleteRepoPoolManager(repo params.Repository) error
GetRepoPoolManagers() (map[string]common.PoolManager, error)
}
type OrgPoolManager interface {
CreateOrgPoolManager(ctx context.Context, org params.Organization, providers map[string]common.Provider, store dbCommon.Store) (common.PoolManager, error)
GetOrgPoolManager(org params.Organization) (common.PoolManager, error)
DeleteOrgPoolManager(org params.Organization) error
GetOrgPoolManagers() (map[string]common.PoolManager, error)
}
type EnterprisePoolManager interface {
CreateEnterprisePoolManager(ctx context.Context, enterprise params.Enterprise, providers map[string]common.Provider, store dbCommon.Store) (common.PoolManager, error)
GetEnterprisePoolManager(enterprise params.Enterprise) (common.PoolManager, error)
DeleteEnterprisePoolManager(enterprise params.Enterprise) error
GetEnterprisePoolManagers() (map[string]common.PoolManager, error)
}
//go:generate mockery --name=PoolManagerController
type PoolManagerController interface {
RepoPoolManager
OrgPoolManager
EnterprisePoolManager
}

View file

@ -18,6 +18,29 @@ type PoolManagerController struct {
mock.Mock
}
// CreateEnterprisePoolManager provides a mock function with given fields: ctx, enterprise, providers, store
func (_m *PoolManagerController) CreateEnterprisePoolManager(ctx context.Context, enterprise params.Enterprise, providers map[string]common.Provider, store databasecommon.Store) (common.PoolManager, error) {
ret := _m.Called(ctx, enterprise, providers, store)
var r0 common.PoolManager
if rf, ok := ret.Get(0).(func(context.Context, params.Enterprise, map[string]common.Provider, databasecommon.Store) common.PoolManager); ok {
r0 = rf(ctx, enterprise, providers, store)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(common.PoolManager)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, params.Enterprise, map[string]common.Provider, databasecommon.Store) error); ok {
r1 = rf(ctx, enterprise, providers, store)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateOrgPoolManager provides a mock function with given fields: ctx, org, providers, store
func (_m *PoolManagerController) CreateOrgPoolManager(ctx context.Context, org params.Organization, providers map[string]common.Provider, store databasecommon.Store) (common.PoolManager, error) {
ret := _m.Called(ctx, org, providers, store)
@ -64,6 +87,20 @@ func (_m *PoolManagerController) CreateRepoPoolManager(ctx context.Context, repo
return r0, r1
}
// DeleteEnterprisePoolManager provides a mock function with given fields: enterprise
func (_m *PoolManagerController) DeleteEnterprisePoolManager(enterprise params.Enterprise) error {
ret := _m.Called(enterprise)
var r0 error
if rf, ok := ret.Get(0).(func(params.Enterprise) error); ok {
r0 = rf(enterprise)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteOrgPoolManager provides a mock function with given fields: org
func (_m *PoolManagerController) DeleteOrgPoolManager(org params.Organization) error {
ret := _m.Called(org)
@ -92,6 +129,52 @@ func (_m *PoolManagerController) DeleteRepoPoolManager(repo params.Repository) e
return r0
}
// GetEnterprisePoolManager provides a mock function with given fields: enterprise
func (_m *PoolManagerController) GetEnterprisePoolManager(enterprise params.Enterprise) (common.PoolManager, error) {
ret := _m.Called(enterprise)
var r0 common.PoolManager
if rf, ok := ret.Get(0).(func(params.Enterprise) common.PoolManager); ok {
r0 = rf(enterprise)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(common.PoolManager)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(params.Enterprise) error); ok {
r1 = rf(enterprise)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetEnterprisePoolManagers provides a mock function with given fields:
func (_m *PoolManagerController) GetEnterprisePoolManagers() (map[string]common.PoolManager, error) {
ret := _m.Called()
var r0 map[string]common.PoolManager
if rf, ok := ret.Get(0).(func() map[string]common.PoolManager); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string]common.PoolManager)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetOrgPoolManager provides a mock function with given fields: org
func (_m *PoolManagerController) GetOrgPoolManager(org params.Organization) (common.PoolManager, error) {
ret := _m.Called(org)

View file

@ -16,6 +16,7 @@ package runner
import (
"context"
"fmt"
"log"
"strings"
@ -85,7 +86,21 @@ func (r *Runner) ListOrganizations(ctx context.Context) ([]params.Organization,
return nil, errors.Wrap(err, "listing organizations")
}
return orgs, nil
var allOrgs []params.Organization
for _, org := range orgs {
poolMgr, err := r.poolManagerCtrl.GetOrgPoolManager(org)
if err != nil {
org.PoolManagerStatus.IsRunning = false
org.PoolManagerStatus.FailureReason = fmt.Sprintf("failed to get pool manager: %q", err)
} else {
org.PoolManagerStatus = poolMgr.Status()
}
allOrgs = append(allOrgs, org)
}
return allOrgs, nil
}
func (r *Runner) GetOrganizationByID(ctx context.Context, orgID string) (params.Organization, error) {
@ -97,6 +112,13 @@ func (r *Runner) GetOrganizationByID(ctx context.Context, orgID string) (params.
if err != nil {
return params.Organization{}, errors.Wrap(err, "fetching organization")
}
poolMgr, err := r.poolManagerCtrl.GetOrgPoolManager(org)
if err != nil {
org.PoolManagerStatus.IsRunning = false
org.PoolManagerStatus.FailureReason = fmt.Sprintf("failed to get pool manager: %q", err)
}
org.PoolManagerStatus = poolMgr.Status()
return org, nil
}

View file

@ -262,6 +262,8 @@ func (s *OrgTestSuite) TestCreateOrganizationStartPoolMgrFailed() {
}
func (s *OrgTestSuite) TestListOrganizations() {
s.Fixtures.PoolMgrCtrlMock.On("GetOrgPoolManager", mock.AnythingOfType("params.Organization")).Return(s.Fixtures.PoolMgrMock, nil)
s.Fixtures.PoolMgrMock.On("Status").Return(params.PoolManagerStatus{IsRunning: true}, nil)
orgs, err := s.Runner.ListOrganizations(s.Fixtures.AdminContext)
s.Require().Nil(err)
@ -275,6 +277,8 @@ func (s *OrgTestSuite) TestListOrganizationsErrUnauthorized() {
}
func (s *OrgTestSuite) TestGetOrganizationByID() {
s.Fixtures.PoolMgrCtrlMock.On("GetOrgPoolManager", mock.AnythingOfType("params.Organization")).Return(s.Fixtures.PoolMgrMock, nil)
s.Fixtures.PoolMgrMock.On("Status").Return(params.PoolManagerStatus{IsRunning: true}, nil)
org, err := s.Runner.GetOrganizationByID(s.Fixtures.AdminContext, s.Fixtures.StoreOrgs["test-org-1"].ID)
s.Require().Nil(err)

207
runner/pool/enterprise.go Normal file
View file

@ -0,0 +1,207 @@
package pool
import (
"context"
"fmt"
"net/http"
"strings"
"sync"
dbCommon "garm/database/common"
runnerErrors "garm/errors"
"garm/params"
"garm/runner/common"
"garm/util"
"github.com/google/go-github/v48/github"
"github.com/pkg/errors"
)
// test that we implement PoolManager
var _ poolHelper = &organization{}
func NewEnterprisePoolManager(ctx context.Context, cfg params.Enterprise, cfgInternal params.Internal, providers map[string]common.Provider, store dbCommon.Store) (common.PoolManager, error) {
ghc, ghEnterpriseClient, err := util.GithubClient(ctx, cfgInternal.OAuth2Token, cfgInternal.GithubCredentialsDetails)
if err != nil {
return nil, errors.Wrap(err, "getting github client")
}
helper := &enterprise{
cfg: cfg,
cfgInternal: cfgInternal,
ctx: ctx,
ghcli: ghc,
ghcEnterpriseCli: ghEnterpriseClient,
id: cfg.ID,
store: store,
}
repo := &basePoolManager{
ctx: ctx,
store: store,
providers: providers,
controllerID: cfgInternal.ControllerID,
quit: make(chan struct{}),
done: make(chan struct{}),
helper: helper,
credsDetails: cfgInternal.GithubCredentialsDetails,
}
return repo, nil
}
type enterprise struct {
cfg params.Enterprise
cfgInternal params.Internal
ctx context.Context
ghcli common.GithubClient
ghcEnterpriseCli common.GithubEnterpriseClient
id string
store dbCommon.Store
mux sync.Mutex
}
func (r *enterprise) GetRunnerNameFromWorkflow(job params.WorkflowJob) (string, error) {
workflow, ghResp, err := r.ghcli.GetWorkflowJobByID(r.ctx, job.Repository.Owner.Login, job.Repository.Name, job.WorkflowJob.ID)
if err != nil {
if ghResp.StatusCode == http.StatusUnauthorized {
return "", errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runners")
}
return "", errors.Wrap(err, "fetching workflow info")
}
if workflow.RunnerName != nil {
return *workflow.RunnerName, nil
}
return "", fmt.Errorf("failed to find runner name from workflow")
}
func (r *enterprise) UpdateState(param params.UpdatePoolStateParams) error {
r.mux.Lock()
defer r.mux.Unlock()
r.cfg.WebhookSecret = param.WebhookSecret
ghc, ghcEnterprise, err := util.GithubClient(r.ctx, r.GetGithubToken(), r.cfgInternal.GithubCredentialsDetails)
if err != nil {
return errors.Wrap(err, "getting github client")
}
r.ghcli = ghc
r.ghcEnterpriseCli = ghcEnterprise
return nil
}
func (r *enterprise) GetGithubToken() string {
return r.cfgInternal.OAuth2Token
}
func (r *enterprise) GetGithubRunners() ([]*github.Runner, error) {
opts := github.ListOptions{
PerPage: 100,
}
var allRunners []*github.Runner
for {
runners, ghResp, err := r.ghcEnterpriseCli.ListRunners(r.ctx, r.cfg.Name, &opts)
if err != nil {
if ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runners")
}
return nil, errors.Wrap(err, "fetching runners")
}
allRunners = append(allRunners, runners.Runners...)
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return allRunners, nil
}
func (r *enterprise) FetchTools() ([]*github.RunnerApplicationDownload, error) {
r.mux.Lock()
defer r.mux.Unlock()
tools, ghResp, err := r.ghcEnterpriseCli.ListRunnerApplicationDownloads(r.ctx, r.cfg.Name)
if err != nil {
if ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runners")
}
return nil, errors.Wrap(err, "fetching runner tools")
}
return tools, nil
}
func (r *enterprise) FetchDbInstances() ([]params.Instance, error) {
return r.store.ListEnterpriseInstances(r.ctx, r.id)
}
func (r *enterprise) RemoveGithubRunner(runnerID int64) (*github.Response, error) {
return r.ghcEnterpriseCli.RemoveRunner(r.ctx, r.cfg.Name, runnerID)
}
func (r *enterprise) ListPools() ([]params.Pool, error) {
pools, err := r.store.ListEnterprisePools(r.ctx, r.id)
if err != nil {
return nil, errors.Wrap(err, "fetching pools")
}
return pools, nil
}
func (r *enterprise) GithubURL() string {
return fmt.Sprintf("%s/enterprises/%s", r.cfgInternal.GithubCredentialsDetails.BaseURL, r.cfg.Name)
}
func (r *enterprise) JwtToken() string {
return r.cfgInternal.JWTSecret
}
func (r *enterprise) GetGithubRegistrationToken() (string, error) {
tk, ghResp, err := r.ghcEnterpriseCli.CreateRegistrationToken(r.ctx, r.cfg.Name)
if err != nil {
if ghResp.StatusCode == http.StatusUnauthorized {
return "", errors.Wrap(runnerErrors.ErrUnauthorized, "fetching registration token")
}
return "", errors.Wrap(err, "creating runner token")
}
return *tk.Token, nil
}
func (r *enterprise) String() string {
return r.cfg.Name
}
func (r *enterprise) WebhookSecret() string {
return r.cfg.WebhookSecret
}
func (r *enterprise) GetCallbackURL() string {
return r.cfgInternal.InstanceCallbackURL
}
func (r *enterprise) FindPoolByTags(labels []string) (params.Pool, error) {
pool, err := r.store.FindEnterprisePoolByTags(r.ctx, r.id, labels)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching suitable pool")
}
return pool, nil
}
func (r *enterprise) GetPoolByID(poolID string) (params.Pool, error) {
pool, err := r.store.GetEnterprisePool(r.ctx, r.id, poolID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}
return pool, nil
}
func (r *enterprise) ValidateOwner(job params.WorkflowJob) error {
if !strings.EqualFold(job.Enterprise.Slug, r.cfg.Name) {
return runnerErrors.NewBadRequestError("job not meant for this pool manager")
}
return nil
}
func (r *enterprise) ID() string {
return r.id
}

View file

@ -17,19 +17,21 @@ package pool
import (
"garm/params"
"github.com/google/go-github/v43/github"
"github.com/google/go-github/v48/github"
)
type poolHelper interface {
GetGithubToken() string
GetGithubRunners() ([]*github.Runner, error)
FetchTools() ([]*github.RunnerApplicationDownload, error)
FetchDbInstances() ([]params.Instance, error)
GetGithubRegistrationToken() (string, error)
GetRunnerNameFromWorkflow(job params.WorkflowJob) (string, error)
RemoveGithubRunner(runnerID int64) (*github.Response, error)
FetchTools() ([]*github.RunnerApplicationDownload, error)
FetchDbInstances() ([]params.Instance, error)
ListPools() ([]params.Pool, error)
GithubURL() string
JwtToken() string
GetGithubRegistrationToken() (string, error)
String() string
GetCallbackURL() string
FindPoolByTags(labels []string) (params.Pool, error)
@ -37,6 +39,5 @@ type poolHelper interface {
ValidateOwner(job params.WorkflowJob) error
UpdateState(param params.UpdatePoolStateParams) error
WebhookSecret() string
GetRunnerNameFromWorkflow(job params.WorkflowJob) (string, error)
ID() string
}

View file

@ -17,17 +17,17 @@ package pool
import (
"context"
"fmt"
"net/http"
"strings"
"sync"
"garm/config"
dbCommon "garm/database/common"
runnerErrors "garm/errors"
"garm/params"
"garm/runner/common"
"garm/util"
"github.com/google/go-github/v43/github"
"github.com/google/go-github/v48/github"
"github.com/pkg/errors"
)
@ -35,7 +35,7 @@ import (
var _ poolHelper = &organization{}
func NewOrganizationPoolManager(ctx context.Context, cfg params.Organization, cfgInternal params.Internal, providers map[string]common.Provider, store dbCommon.Store) (common.PoolManager, error) {
ghc, err := util.GithubClient(ctx, cfgInternal.OAuth2Token)
ghc, _, err := util.GithubClient(ctx, cfgInternal.OAuth2Token, cfgInternal.GithubCredentialsDetails)
if err != nil {
return nil, errors.Wrap(err, "getting github client")
}
@ -49,7 +49,7 @@ func NewOrganizationPoolManager(ctx context.Context, cfg params.Organization, cf
store: store,
}
repo := &basePool{
repo := &basePoolManager{
ctx: ctx,
store: store,
providers: providers,
@ -57,6 +57,7 @@ func NewOrganizationPoolManager(ctx context.Context, cfg params.Organization, cf
quit: make(chan struct{}),
done: make(chan struct{}),
helper: helper,
credsDetails: cfgInternal.GithubCredentialsDetails,
}
return repo, nil
}
@ -73,8 +74,11 @@ type organization struct {
}
func (r *organization) GetRunnerNameFromWorkflow(job params.WorkflowJob) (string, error) {
workflow, _, err := r.ghcli.GetWorkflowJobByID(r.ctx, job.Organization.Login, job.Repository.Name, job.WorkflowJob.ID)
workflow, ghResp, err := r.ghcli.GetWorkflowJobByID(r.ctx, job.Organization.Login, job.Repository.Name, job.WorkflowJob.ID)
if err != nil {
if ghResp.StatusCode == http.StatusUnauthorized {
return "", errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runner name")
}
return "", errors.Wrap(err, "fetching workflow info")
}
if workflow.RunnerName != nil {
@ -89,7 +93,7 @@ func (r *organization) UpdateState(param params.UpdatePoolStateParams) error {
r.cfg.WebhookSecret = param.WebhookSecret
ghc, err := util.GithubClient(r.ctx, r.GetGithubToken())
ghc, _, err := util.GithubClient(r.ctx, r.GetGithubToken(), r.cfgInternal.GithubCredentialsDetails)
if err != nil {
return errors.Wrap(err, "getting github client")
}
@ -102,19 +106,37 @@ func (r *organization) GetGithubToken() string {
}
func (r *organization) GetGithubRunners() ([]*github.Runner, error) {
runners, _, err := r.ghcli.ListOrganizationRunners(r.ctx, r.cfg.Name, nil)
if err != nil {
return nil, errors.Wrap(err, "fetching runners")
opts := github.ListOptions{
PerPage: 100,
}
return runners.Runners, nil
var allRunners []*github.Runner
for {
runners, ghResp, err := r.ghcli.ListOrganizationRunners(r.ctx, r.cfg.Name, &opts)
if err != nil {
if ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runners")
}
return nil, errors.Wrap(err, "fetching runners")
}
allRunners = append(allRunners, runners.Runners...)
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return allRunners, nil
}
func (r *organization) FetchTools() ([]*github.RunnerApplicationDownload, error) {
r.mux.Lock()
defer r.mux.Unlock()
tools, _, err := r.ghcli.ListOrganizationRunnerApplicationDownloads(r.ctx, r.cfg.Name)
tools, ghResp, err := r.ghcli.ListOrganizationRunnerApplicationDownloads(r.ctx, r.cfg.Name)
if err != nil {
if ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching tools")
}
return nil, errors.Wrap(err, "fetching runner tools")
}
@ -138,7 +160,7 @@ func (r *organization) ListPools() ([]params.Pool, error) {
}
func (r *organization) GithubURL() string {
return fmt.Sprintf("%s/%s", config.GithubBaseURL, r.cfg.Name)
return fmt.Sprintf("%s/%s", r.cfgInternal.GithubCredentialsDetails.BaseURL, r.cfg.Name)
}
func (r *organization) JwtToken() string {
@ -146,9 +168,13 @@ func (r *organization) JwtToken() string {
}
func (r *organization) GetGithubRegistrationToken() (string, error) {
tk, _, err := r.ghcli.CreateOrganizationRegistrationToken(r.ctx, r.cfg.Name)
tk, ghResp, err := r.ghcli.CreateOrganizationRegistrationToken(r.ctx, r.cfg.Name)
if err != nil {
if ghResp.StatusCode == http.StatusUnauthorized {
return "", errors.Wrap(runnerErrors.ErrUnauthorized, "fetching token")
}
return "", errors.Wrap(err, "creating runner token")
}
return *tk.Token, nil

View file

@ -30,7 +30,7 @@ import (
"garm/runner/common"
providerCommon "garm/runner/providers/common"
"github.com/google/go-github/v43/github"
"github.com/google/go-github/v48/github"
"github.com/google/uuid"
"github.com/pkg/errors"
)
@ -47,7 +47,7 @@ const (
maxCreateAttempts = 5
)
type basePool struct {
type basePoolManager struct {
ctx context.Context
controllerID string
@ -58,7 +58,11 @@ type basePool struct {
quit chan struct{}
done chan struct{}
helper poolHelper
helper poolHelper
credsDetails params.GithubCredentials
managerIsRunning bool
managerErrorReason string
mux sync.Mutex
}
@ -80,7 +84,7 @@ func controllerIDFromLabels(labels []*github.RunnerLabels) string {
// happens, github will remove the ephemeral worker and send a webhook our way.
// If we were offline and did not process the webhook, the instance will linger.
// We need to remove it from the provider and database.
func (r *basePool) cleanupOrphanedProviderRunners(runners []*github.Runner) error {
func (r *basePoolManager) cleanupOrphanedProviderRunners(runners []*github.Runner) error {
dbInstances, err := r.helper.FetchDbInstances()
if err != nil {
return errors.Wrap(err, "fetching instances from db")
@ -115,7 +119,7 @@ func (r *basePool) cleanupOrphanedProviderRunners(runners []*github.Runner) erro
// reapTimedOutRunners will mark as pending_delete any runner that has a status
// of "running" in the provider, but that has not registered with Github, and has
// received no new updates in the configured timeout interval.
func (r *basePool) reapTimedOutRunners(runners []*github.Runner) error {
func (r *basePoolManager) reapTimedOutRunners(runners []*github.Runner) error {
dbInstances, err := r.helper.FetchDbInstances()
if err != nil {
return errors.Wrap(err, "fetching instances from db")
@ -151,7 +155,7 @@ func (r *basePool) reapTimedOutRunners(runners []*github.Runner) error {
// as offline and for which we no longer have a local instance.
// This may happen if someone manually deletes the instance in the provider. We need to
// first remove the instance from github, and then from our database.
func (r *basePool) cleanupOrphanedGithubRunners(runners []*github.Runner) error {
func (r *basePoolManager) cleanupOrphanedGithubRunners(runners []*github.Runner) error {
for _, runner := range runners {
runnerControllerID := controllerIDFromLabels(runner.Labels)
if runnerControllerID != r.controllerID {
@ -179,6 +183,13 @@ func (r *basePool) cleanupOrphanedGithubRunners(runners []*github.Runner) error
if resp != nil && resp.StatusCode == http.StatusNotFound {
continue
}
if errors.Is(err, runnerErrors.ErrUnauthorized) {
failureReason := fmt.Sprintf("failed to remove github runner: %q", err)
r.setPoolRunningState(false, failureReason)
log.Print(failureReason)
}
return errors.Wrap(err, "removing runner")
}
continue
@ -216,6 +227,12 @@ func (r *basePool) cleanupOrphanedGithubRunners(runners []*github.Runner) error
if resp != nil && resp.StatusCode == http.StatusNotFound {
log.Printf("runner dissapeared from github")
} else {
if errors.Is(err, runnerErrors.ErrUnauthorized) {
failureReason := fmt.Sprintf("failed to remove github runner: %q", err)
r.setPoolRunningState(false, failureReason)
log.Print(failureReason)
}
return errors.Wrap(err, "removing runner from github")
}
}
@ -242,7 +259,7 @@ func (r *basePool) cleanupOrphanedGithubRunners(runners []*github.Runner) error
return nil
}
func (r *basePool) fetchInstance(runnerName string) (params.Instance, error) {
func (r *basePoolManager) fetchInstance(runnerName string) (params.Instance, error) {
runner, err := r.store.GetInstanceByName(r.ctx, runnerName)
if err != nil {
return params.Instance{}, errors.Wrap(err, "fetching instance")
@ -251,7 +268,7 @@ func (r *basePool) fetchInstance(runnerName string) (params.Instance, error) {
return runner, nil
}
func (r *basePool) setInstanceRunnerStatus(runnerName string, status providerCommon.RunnerStatus) error {
func (r *basePoolManager) setInstanceRunnerStatus(runnerName string, status providerCommon.RunnerStatus) error {
updateParams := params.UpdateInstanceParams{
RunnerStatus: status,
}
@ -262,7 +279,7 @@ func (r *basePool) setInstanceRunnerStatus(runnerName string, status providerCom
return nil
}
func (r *basePool) updateInstance(runnerName string, update params.UpdateInstanceParams) error {
func (r *basePoolManager) updateInstance(runnerName string, update params.UpdateInstanceParams) error {
runner, err := r.fetchInstance(runnerName)
if err != nil {
return errors.Wrap(err, "fetching instance")
@ -274,7 +291,7 @@ func (r *basePool) updateInstance(runnerName string, update params.UpdateInstanc
return nil
}
func (r *basePool) setInstanceStatus(runnerName string, status providerCommon.InstanceStatus, providerFault []byte) error {
func (r *basePoolManager) setInstanceStatus(runnerName string, status providerCommon.InstanceStatus, providerFault []byte) error {
updateParams := params.UpdateInstanceParams{
Status: status,
ProviderFault: providerFault,
@ -286,7 +303,7 @@ func (r *basePool) setInstanceStatus(runnerName string, status providerCommon.In
return nil
}
func (r *basePool) acquireNewInstance(job params.WorkflowJob) error {
func (r *basePoolManager) acquireNewInstance(job params.WorkflowJob) error {
requestedLabels := job.WorkflowJob.Labels
if len(requestedLabels) == 0 {
// no labels were requested.
@ -321,7 +338,7 @@ func (r *basePool) acquireNewInstance(job params.WorkflowJob) error {
return nil
}
func (r *basePool) AddRunner(ctx context.Context, poolID string) error {
func (r *basePoolManager) AddRunner(ctx context.Context, poolID string) error {
pool, err := r.helper.GetPoolByID(poolID)
if err != nil {
return errors.Wrap(err, "fetching pool")
@ -347,19 +364,20 @@ func (r *basePool) AddRunner(ctx context.Context, poolID string) error {
return nil
}
func (r *basePool) loop() {
func (r *basePoolManager) loop() {
consolidateTimer := time.NewTicker(common.PoolConsilitationInterval)
reapTimer := time.NewTicker(common.PoolReapTimeoutInterval)
toolUpdateTimer := time.NewTicker(common.PoolToolUpdateInterval)
defer func() {
log.Printf("repository %s loop exited", r.helper.String())
log.Printf("%s loop exited", r.helper.String())
consolidateTimer.Stop()
reapTimer.Stop()
toolUpdateTimer.Stop()
close(r.done)
}()
log.Printf("starting loop for %s", r.helper.String())
// TODO: Consolidate runners on loop start. Provider runners must match runners
// Consolidate runners on loop start. Provider runners must match runners
// in github and DB. When a Workflow job is received, we will first create/update
// an entity in the database, before sending the request to the provider to create/delete
// an instance. If a "queued" job is received, we create an entity in the db with
@ -369,45 +387,114 @@ func (r *basePool) loop() {
// in the database.
// We also ensure we have runners created based on pool characteristics. This is where
// we spin up "MinWorkers" for each runner type.
for {
select {
case <-reapTimer.C:
runners, err := r.helper.GetGithubRunners()
if err != nil {
log.Printf("error fetching github runners: %s", err)
continue
}
if err := r.reapTimedOutRunners(runners); err != nil {
log.Printf("failed to reap timed out runners: %q", err)
}
switch r.managerIsRunning {
case true:
select {
case <-reapTimer.C:
runners, err := r.helper.GetGithubRunners()
if err != nil {
failureReason := fmt.Sprintf("error fetching github runners for %s: %s", r.helper.String(), err)
r.setPoolRunningState(false, failureReason)
log.Print(failureReason)
if errors.Is(err, runnerErrors.ErrUnauthorized) {
break
}
continue
}
if err := r.reapTimedOutRunners(runners); err != nil {
log.Printf("failed to reap timed out runners: %q", err)
}
if err := r.cleanupOrphanedGithubRunners(runners); err != nil {
log.Printf("failed to clean orphaned github runners: %q", err)
if err := r.cleanupOrphanedGithubRunners(runners); err != nil {
log.Printf("failed to clean orphaned github runners: %q", err)
}
case <-consolidateTimer.C:
// consolidate.
r.consolidate()
case <-toolUpdateTimer.C:
// Update tools cache.
tools, err := r.helper.FetchTools()
if err != nil {
failureReason := fmt.Sprintf("failed to update tools for repo %s: %s", r.helper.String(), err)
r.setPoolRunningState(false, failureReason)
log.Print(failureReason)
if errors.Is(err, runnerErrors.ErrUnauthorized) {
break
}
continue
}
r.mux.Lock()
r.tools = tools
r.mux.Unlock()
case <-r.ctx.Done():
// daemon is shutting down.
return
case <-r.quit:
// this worker was stopped.
return
}
case <-consolidateTimer.C:
// consolidate.
r.consolidate()
case <-toolUpdateTimer.C:
// Update tools cache.
default:
log.Printf("attempting to start pool manager for %s", r.helper.String())
tools, err := r.helper.FetchTools()
var failureReason string
if err != nil {
log.Printf("failed to update tools for repo %s: %s", r.helper.String(), err)
failureReason = fmt.Sprintf("failed to fetch tools from github for %s: %q", r.helper.String(), err)
r.setPoolRunningState(false, failureReason)
log.Print(failureReason)
if errors.Is(err, runnerErrors.ErrUnauthorized) {
r.waitForTimeoutOrCanceled(common.UnauthorizedBackoffTimer)
} else {
r.waitForTimeoutOrCanceled(60 * time.Second)
}
continue
}
r.mux.Lock()
r.tools = tools
r.mux.Unlock()
case <-r.ctx.Done():
// daemon is shutting down.
return
case <-r.quit:
// this worker was stopped.
return
if err := r.runnerCleanup(); err != nil {
failureReason = fmt.Sprintf("failed to clean runners for %s: %q", r.helper.String(), err)
r.setPoolRunningState(false, failureReason)
log.Print(failureReason)
if errors.Is(err, runnerErrors.ErrUnauthorized) {
r.waitForTimeoutOrCanceled(common.UnauthorizedBackoffTimer)
} else {
r.waitForTimeoutOrCanceled(60 * time.Second)
}
continue
}
r.setPoolRunningState(true, "")
}
}
}
func (r *basePool) addInstanceToProvider(instance params.Instance) error {
func (r *basePoolManager) Status() params.PoolManagerStatus {
r.mux.Lock()
defer r.mux.Unlock()
return params.PoolManagerStatus{
IsRunning: r.managerIsRunning,
FailureReason: r.managerErrorReason,
}
}
func (r *basePoolManager) waitForTimeoutOrCanceled(timeout time.Duration) {
log.Printf("sleeping for %.2f minutes", timeout.Minutes())
select {
case <-time.After(timeout):
case <-r.ctx.Done():
case <-r.quit:
}
}
func (r *basePoolManager) setPoolRunningState(isRunning bool, failureReason string) {
r.mux.Lock()
r.managerErrorReason = failureReason
r.managerIsRunning = isRunning
r.mux.Unlock()
}
func (r *basePoolManager) addInstanceToProvider(instance params.Instance) error {
pool, err := r.helper.GetPoolByID(instance.PoolID)
if err != nil {
return errors.Wrap(err, "fetching pool")
@ -415,7 +502,7 @@ func (r *basePool) addInstanceToProvider(instance params.Instance) error {
provider, ok := r.providers[pool.ProviderName]
if !ok {
return runnerErrors.NewNotFoundError("invalid provider ID")
return fmt.Errorf("unknown provider %s for pool %s", pool.ProviderName, pool.ID)
}
labels := []string{}
@ -427,6 +514,11 @@ func (r *basePool) addInstanceToProvider(instance params.Instance) error {
tk, err := r.helper.GetGithubRegistrationToken()
if err != nil {
if errors.Is(err, runnerErrors.ErrUnauthorized) {
failureReason := fmt.Sprintf("failed to fetch registration token: %q", err)
r.setPoolRunningState(false, failureReason)
log.Print(failureReason)
}
return errors.Wrap(err, "fetching registration token")
}
@ -454,6 +546,7 @@ func (r *basePool) addInstanceToProvider(instance params.Instance) error {
Image: pool.Image,
Labels: labels,
PoolID: instance.PoolID,
CACertBundle: r.credsDetails.CABundle,
}
var instanceIDToDelete string
@ -488,7 +581,7 @@ func (r *basePool) addInstanceToProvider(instance params.Instance) error {
return nil
}
func (r *basePool) getRunnerNameFromJob(job params.WorkflowJob) (string, error) {
func (r *basePoolManager) getRunnerNameFromJob(job params.WorkflowJob) (string, error) {
if job.WorkflowJob.RunnerName != "" {
return job.WorkflowJob.RunnerName, nil
}
@ -498,13 +591,18 @@ func (r *basePool) getRunnerNameFromJob(job params.WorkflowJob) (string, error)
log.Printf("runner name not found in workflow job, attempting to fetch from API")
runnerName, err := r.helper.GetRunnerNameFromWorkflow(job)
if err != nil {
if errors.Is(err, runnerErrors.ErrUnauthorized) {
failureReason := fmt.Sprintf("failed to fetch runner name from API: %q", err)
r.setPoolRunningState(false, failureReason)
log.Print(failureReason)
}
return "", errors.Wrap(err, "fetching runner name from API")
}
return runnerName, nil
}
func (r *basePool) HandleWorkflowJob(job params.WorkflowJob) error {
func (r *basePoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
if err := r.helper.ValidateOwner(job); err != nil {
return errors.Wrap(err, "validating owner")
}
@ -557,15 +655,15 @@ func (r *basePool) HandleWorkflowJob(job params.WorkflowJob) error {
return nil
}
func (r *basePool) poolLabel(poolID string) string {
func (r *basePoolManager) poolLabel(poolID string) string {
return fmt.Sprintf("%s%s", poolIDLabelprefix, poolID)
}
func (r *basePool) controllerLabel() string {
func (r *basePoolManager) controllerLabel() string {
return fmt.Sprintf("%s%s", controllerLabelPrefix, r.controllerID)
}
func (r *basePool) updateArgsFromProviderInstance(providerInstance params.Instance) params.UpdateInstanceParams {
func (r *basePoolManager) updateArgsFromProviderInstance(providerInstance params.Instance) params.UpdateInstanceParams {
return params.UpdateInstanceParams{
ProviderID: providerInstance.ProviderID,
OSName: providerInstance.OSName,
@ -577,7 +675,7 @@ func (r *basePool) updateArgsFromProviderInstance(providerInstance params.Instan
}
}
func (r *basePool) ensureIdleRunnersForOnePool(pool params.Pool) {
func (r *basePoolManager) ensureIdleRunnersForOnePool(pool params.Pool) {
if !pool.Enabled {
return
}
@ -620,7 +718,7 @@ func (r *basePool) ensureIdleRunnersForOnePool(pool params.Pool) {
}
}
func (r *basePool) retryFailedInstancesForOnePool(pool params.Pool) {
func (r *basePoolManager) retryFailedInstancesForOnePool(pool params.Pool) {
if !pool.Enabled {
return
}
@ -662,7 +760,7 @@ func (r *basePool) retryFailedInstancesForOnePool(pool params.Pool) {
}
}
func (r *basePool) retryFailedInstances() {
func (r *basePoolManager) retryFailedInstances() {
pools, err := r.helper.ListPools()
if err != nil {
log.Printf("error listing pools: %s", err)
@ -679,7 +777,7 @@ func (r *basePool) retryFailedInstances() {
wg.Wait()
}
func (r *basePool) ensureMinIdleRunners() {
func (r *basePoolManager) ensureMinIdleRunners() {
pools, err := r.helper.ListPools()
if err != nil {
log.Printf("error listing pools: %s", err)
@ -696,7 +794,7 @@ func (r *basePool) ensureMinIdleRunners() {
wg.Wait()
}
func (r *basePool) deleteInstanceFromProvider(instance params.Instance) error {
func (r *basePoolManager) deleteInstanceFromProvider(instance params.Instance) error {
pool, err := r.helper.GetPoolByID(instance.PoolID)
if err != nil {
return errors.Wrap(err, "fetching pool")
@ -704,7 +802,7 @@ func (r *basePool) deleteInstanceFromProvider(instance params.Instance) error {
provider, ok := r.providers[pool.ProviderName]
if !ok {
return runnerErrors.NewNotFoundError("invalid provider ID")
return fmt.Errorf("unknown provider %s for pool %s", pool.ProviderName, pool.ID)
}
identifier := instance.ProviderID
@ -724,7 +822,7 @@ func (r *basePool) deleteInstanceFromProvider(instance params.Instance) error {
return nil
}
func (r *basePool) deletePendingInstances() {
func (r *basePoolManager) deletePendingInstances() {
instances, err := r.helper.FetchDbInstances()
if err != nil {
log.Printf("failed to fetch instances from store: %s", err)
@ -762,7 +860,7 @@ func (r *basePool) deletePendingInstances() {
}
}
func (r *basePool) addPendingInstances() {
func (r *basePoolManager) addPendingInstances() {
// TODO: filter instances by status.
instances, err := r.helper.FetchDbInstances()
if err != nil {
@ -794,7 +892,7 @@ func (r *basePool) addPendingInstances() {
}
}
func (r *basePool) consolidate() {
func (r *basePoolManager) consolidate() {
// TODO(gabriel-samfira): replace this with something more efficient.
r.mux.Lock()
defer r.mux.Unlock()
@ -824,7 +922,7 @@ func (r *basePool) consolidate() {
wg.Wait()
}
func (r *basePool) Wait() error {
func (r *basePoolManager) Wait() error {
select {
case <-r.done:
case <-time.After(20 * time.Second):
@ -833,17 +931,14 @@ func (r *basePool) Wait() error {
return nil
}
func (r *basePool) Start() error {
tools, err := r.helper.FetchTools()
if err != nil {
return errors.Wrap(err, "initializing tools")
}
r.mux.Lock()
r.tools = tools
r.mux.Unlock()
func (r *basePoolManager) runnerCleanup() error {
runners, err := r.helper.GetGithubRunners()
if err != nil {
if errors.Is(err, runnerErrors.ErrUnauthorized) {
failureReason := fmt.Sprintf("failed to fetch runners: %q", err)
r.setPoolRunningState(false, failureReason)
log.Print(failureReason)
}
return errors.Wrap(err, "fetching github runners")
}
if err := r.cleanupOrphanedProviderRunners(runners); err != nil {
@ -853,28 +948,35 @@ func (r *basePool) Start() error {
if err := r.cleanupOrphanedGithubRunners(runners); err != nil {
return errors.Wrap(err, "cleaning orphaned github runners")
}
return nil
}
func (r *basePoolManager) Start() error {
go r.loop()
return nil
}
func (r *basePool) Stop() error {
func (r *basePoolManager) Stop() error {
close(r.quit)
return nil
}
func (r *basePool) RefreshState(param params.UpdatePoolStateParams) error {
func (r *basePoolManager) RefreshState(param params.UpdatePoolStateParams) error {
return r.helper.UpdateState(param)
}
func (r *basePool) WebhookSecret() string {
func (r *basePoolManager) WebhookSecret() string {
return r.helper.WebhookSecret()
}
func (r *basePool) ID() string {
func (r *basePoolManager) ID() string {
return r.helper.ID()
}
func (r *basePool) ForceDeleteRunner(runner params.Instance) error {
func (r *basePoolManager) ForceDeleteRunner(runner params.Instance) error {
if !r.managerIsRunning {
return runnerErrors.NewConflictError("pool manager is not running for %s", r.helper.String())
}
if runner.AgentID != 0 {
resp, err := r.helper.RemoveGithubRunner(runner.AgentID)
if err != nil {
@ -885,6 +987,13 @@ func (r *basePool) ForceDeleteRunner(runner params.Instance) error {
case http.StatusNotFound:
// Runner may have been deleted by a finished job, or manually by the user.
log.Printf("runner with agent id %d was not found in github", runner.AgentID)
case http.StatusUnauthorized:
// Mark the pool as offline from this point forward
failureReason := fmt.Sprintf("failed to remove runner: %q", err)
r.setPoolRunningState(false, failureReason)
log.Print(failureReason)
// evaluate the next switch case.
fallthrough
default:
return errors.Wrap(err, "removing runner")
}
@ -894,6 +1003,7 @@ func (r *basePool) ForceDeleteRunner(runner params.Instance) error {
}
}
}
log.Printf("setting instance status for: %v", runner.Name)
if err := r.setInstanceStatus(runner.Name, providerCommon.InstancePendingDelete, nil); err != nil {
log.Printf("failed to update runner %s status", runner.Name)

View file

@ -17,17 +17,17 @@ package pool
import (
"context"
"fmt"
"net/http"
"strings"
"sync"
"garm/config"
dbCommon "garm/database/common"
runnerErrors "garm/errors"
"garm/params"
"garm/runner/common"
"garm/util"
"github.com/google/go-github/v43/github"
"github.com/google/go-github/v48/github"
"github.com/pkg/errors"
)
@ -35,7 +35,7 @@ import (
var _ poolHelper = &repository{}
func NewRepositoryPoolManager(ctx context.Context, cfg params.Repository, cfgInternal params.Internal, providers map[string]common.Provider, store dbCommon.Store) (common.PoolManager, error) {
ghc, err := util.GithubClient(ctx, cfgInternal.OAuth2Token)
ghc, _, err := util.GithubClient(ctx, cfgInternal.OAuth2Token, cfgInternal.GithubCredentialsDetails)
if err != nil {
return nil, errors.Wrap(err, "getting github client")
}
@ -49,7 +49,7 @@ func NewRepositoryPoolManager(ctx context.Context, cfg params.Repository, cfgInt
store: store,
}
repo := &basePool{
repo := &basePoolManager{
ctx: ctx,
store: store,
providers: providers,
@ -57,6 +57,7 @@ func NewRepositoryPoolManager(ctx context.Context, cfg params.Repository, cfgInt
quit: make(chan struct{}),
done: make(chan struct{}),
helper: helper,
credsDetails: cfgInternal.GithubCredentialsDetails,
}
return repo, nil
}
@ -75,8 +76,11 @@ type repository struct {
}
func (r *repository) GetRunnerNameFromWorkflow(job params.WorkflowJob) (string, error) {
workflow, _, err := r.ghcli.GetWorkflowJobByID(r.ctx, job.Repository.Owner.Login, job.Repository.Name, job.WorkflowJob.ID)
workflow, ghResp, err := r.ghcli.GetWorkflowJobByID(r.ctx, job.Repository.Owner.Login, job.Repository.Name, job.WorkflowJob.ID)
if err != nil {
if ghResp.StatusCode == http.StatusUnauthorized {
return "", errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runner name")
}
return "", errors.Wrap(err, "fetching workflow info")
}
if workflow.RunnerName != nil {
@ -91,7 +95,7 @@ func (r *repository) UpdateState(param params.UpdatePoolStateParams) error {
r.cfg.WebhookSecret = param.WebhookSecret
ghc, err := util.GithubClient(r.ctx, r.GetGithubToken())
ghc, _, err := util.GithubClient(r.ctx, r.GetGithubToken(), r.cfgInternal.GithubCredentialsDetails)
if err != nil {
return errors.Wrap(err, "getting github client")
}
@ -104,19 +108,37 @@ func (r *repository) GetGithubToken() string {
}
func (r *repository) GetGithubRunners() ([]*github.Runner, error) {
runners, _, err := r.ghcli.ListRunners(r.ctx, r.cfg.Owner, r.cfg.Name, nil)
if err != nil {
return nil, errors.Wrap(err, "fetching runners")
opts := github.ListOptions{
PerPage: 100,
}
return runners.Runners, nil
var allRunners []*github.Runner
for {
runners, ghResp, err := r.ghcli.ListRunners(r.ctx, r.cfg.Owner, r.cfg.Name, &opts)
if err != nil {
if ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runners")
}
return nil, errors.Wrap(err, "fetching runners")
}
allRunners = append(allRunners, runners.Runners...)
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return allRunners, nil
}
func (r *repository) FetchTools() ([]*github.RunnerApplicationDownload, error) {
r.mux.Lock()
defer r.mux.Unlock()
tools, _, err := r.ghcli.ListRunnerApplicationDownloads(r.ctx, r.cfg.Owner, r.cfg.Name)
tools, ghResp, err := r.ghcli.ListRunnerApplicationDownloads(r.ctx, r.cfg.Owner, r.cfg.Name)
if err != nil {
if ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching tools")
}
return nil, errors.Wrap(err, "fetching runner tools")
}
@ -140,7 +162,7 @@ func (r *repository) ListPools() ([]params.Pool, error) {
}
func (r *repository) GithubURL() string {
return fmt.Sprintf("%s/%s/%s", config.GithubBaseURL, r.cfg.Owner, r.cfg.Name)
return fmt.Sprintf("%s/%s/%s", r.cfgInternal.GithubCredentialsDetails.BaseURL, r.cfg.Owner, r.cfg.Name)
}
func (r *repository) JwtToken() string {
@ -148,9 +170,12 @@ func (r *repository) JwtToken() string {
}
func (r *repository) GetGithubRegistrationToken() (string, error) {
tk, _, err := r.ghcli.CreateRegistrationToken(r.ctx, r.cfg.Owner, r.cfg.Name)
tk, ghResp, err := r.ghcli.CreateRegistrationToken(r.ctx, r.cfg.Owner, r.cfg.Name)
if err != nil {
if ghResp.StatusCode == http.StatusUnauthorized {
return "", errors.Wrap(runnerErrors.ErrUnauthorized, "fetching token")
}
return "", errors.Wrap(err, "creating runner token")
}
return *tk.Token, nil

View file

@ -16,6 +16,7 @@ package runner
import (
"context"
"fmt"
"garm/auth"
runnerErrors "garm/errors"
@ -112,6 +113,10 @@ func (r *Runner) UpdatePoolByID(ctx context.Context, poolID string, param params
newPool, err = r.store.UpdateRepositoryPool(ctx, pool.RepoID, poolID, param)
} else if pool.OrgID != "" {
newPool, err = r.store.UpdateOrganizationPool(ctx, pool.OrgID, poolID, param)
} else if pool.EnterpriseID != "" {
newPool, err = r.store.UpdateEnterprisePool(ctx, pool.EnterpriseID, poolID, param)
} else {
return params.Pool{}, fmt.Errorf("pool not bound to a repo, org or enterprise")
}
if err != nil {

View file

@ -25,7 +25,7 @@ import (
"garm/runner/common"
"garm/util"
"github.com/google/go-github/v43/github"
"github.com/google/go-github/v48/github"
lxd "github.com/lxc/lxd/client"
"github.com/lxc/lxd/shared/api"
"github.com/pkg/errors"

View file

@ -16,6 +16,7 @@ package runner
import (
"context"
"fmt"
"log"
"strings"
@ -85,7 +86,20 @@ func (r *Runner) ListRepositories(ctx context.Context) ([]params.Repository, err
return nil, errors.Wrap(err, "listing repositories")
}
return repos, nil
var allRepos []params.Repository
for _, repo := range repos {
poolMgr, err := r.poolManagerCtrl.GetRepoPoolManager(repo)
if err != nil {
repo.PoolManagerStatus.IsRunning = false
repo.PoolManagerStatus.FailureReason = fmt.Sprintf("failed to get pool manager: %q", err)
} else {
repo.PoolManagerStatus = poolMgr.Status()
}
allRepos = append(allRepos, repo)
}
return allRepos, nil
}
func (r *Runner) GetRepositoryByID(ctx context.Context, repoID string) (params.Repository, error) {
@ -97,6 +111,13 @@ func (r *Runner) GetRepositoryByID(ctx context.Context, repoID string) (params.R
if err != nil {
return params.Repository{}, errors.Wrap(err, "fetching repository")
}
poolMgr, err := r.poolManagerCtrl.GetRepoPoolManager(repo)
if err != nil {
repo.PoolManagerStatus.IsRunning = false
repo.PoolManagerStatus.FailureReason = fmt.Sprintf("failed to get pool manager: %q", err)
}
repo.PoolManagerStatus = poolMgr.Status()
return repo, nil
}

View file

@ -265,6 +265,8 @@ func (s *RepoTestSuite) TestCreateRepositoryStartPoolMgrFailed() {
}
func (s *RepoTestSuite) TestListRepositories() {
s.Fixtures.PoolMgrCtrlMock.On("GetRepoPoolManager", mock.AnythingOfType("params.Repository")).Return(s.Fixtures.PoolMgrMock, nil)
s.Fixtures.PoolMgrMock.On("Status").Return(params.PoolManagerStatus{IsRunning: true}, nil)
repos, err := s.Runner.ListRepositories(s.Fixtures.AdminContext)
s.Require().Nil(err)
@ -278,6 +280,8 @@ func (s *RepoTestSuite) TestListRepositoriesErrUnauthorized() {
}
func (s *RepoTestSuite) TestGetRepositoryByID() {
s.Fixtures.PoolMgrCtrlMock.On("GetRepoPoolManager", mock.AnythingOfType("params.Repository")).Return(s.Fixtures.PoolMgrMock, nil)
s.Fixtures.PoolMgrMock.On("Status").Return(params.PoolManagerStatus{IsRunning: true}, nil)
repo, err := s.Runner.GetRepositoryByID(s.Fixtures.AdminContext, s.Fixtures.StoreRepos["test-repo-1"].ID)
s.Require().Nil(err)

View file

@ -23,9 +23,7 @@ import (
"encoding/json"
"fmt"
"hash"
"io/ioutil"
"log"
"path/filepath"
"strings"
"sync"
"time"
@ -43,7 +41,6 @@ import (
"garm/util"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh"
)
func NewRunner(ctx context.Context, cfg config.Config) (*Runner, error) {
@ -74,6 +71,7 @@ func NewRunner(ctx context.Context, cfg config.Config) (*Runner, error) {
credentials: creds,
repositories: map[string]common.PoolManager{},
organizations: map[string]common.PoolManager{},
enterprises: map[string]common.PoolManager{},
}
runner := &Runner{
ctx: ctx,
@ -84,7 +82,7 @@ func NewRunner(ctx context.Context, cfg config.Config) (*Runner, error) {
credentials: creds,
}
if err := runner.loadReposAndOrgs(); err != nil {
if err := runner.loadReposOrgsAndEnterprises(); err != nil {
return nil, errors.Wrap(err, "loading pool managers")
}
@ -100,6 +98,7 @@ type poolManagerCtrl struct {
repositories map[string]common.PoolManager
organizations map[string]common.PoolManager
enterprises map[string]common.PoolManager
}
func (p *poolManagerCtrl) CreateRepoPoolManager(ctx context.Context, repo params.Repository, providers map[string]common.Provider, store dbCommon.Store) (common.PoolManager, error) {
@ -184,17 +183,71 @@ func (p *poolManagerCtrl) GetOrgPoolManagers() (map[string]common.PoolManager, e
return p.organizations, nil
}
func (p *poolManagerCtrl) CreateEnterprisePoolManager(ctx context.Context, enterprise params.Enterprise, providers map[string]common.Provider, store dbCommon.Store) (common.PoolManager, error) {
p.mux.Lock()
defer p.mux.Unlock()
cfgInternal, err := p.getInternalConfig(enterprise.CredentialsName)
if err != nil {
return nil, errors.Wrap(err, "fetching internal config")
}
poolManager, err := pool.NewEnterprisePoolManager(ctx, enterprise, cfgInternal, providers, store)
if err != nil {
return nil, errors.Wrap(err, "creating enterprise pool manager")
}
p.enterprises[enterprise.ID] = poolManager
return poolManager, nil
}
func (p *poolManagerCtrl) GetEnterprisePoolManager(enterprise params.Enterprise) (common.PoolManager, error) {
if enterprisePoolMgr, ok := p.enterprises[enterprise.ID]; ok {
return enterprisePoolMgr, nil
}
return nil, errors.Wrapf(runnerErrors.ErrNotFound, "enterprise %s pool manager not loaded", enterprise.Name)
}
func (p *poolManagerCtrl) DeleteEnterprisePoolManager(enterprise params.Enterprise) error {
p.mux.Lock()
defer p.mux.Unlock()
poolMgr, ok := p.enterprises[enterprise.ID]
if ok {
if err := poolMgr.Stop(); err != nil {
return errors.Wrap(err, "stopping enterprise pool manager")
}
delete(p.enterprises, enterprise.ID)
}
return nil
}
func (p *poolManagerCtrl) GetEnterprisePoolManagers() (map[string]common.PoolManager, error) {
return p.enterprises, nil
}
func (p *poolManagerCtrl) getInternalConfig(credsName string) (params.Internal, error) {
creds, ok := p.credentials[credsName]
if !ok {
return params.Internal{}, runnerErrors.NewBadRequestError("invalid credential name (%s)", credsName)
}
caBundle, err := creds.CACertBundle()
if err != nil {
return params.Internal{}, fmt.Errorf("fetching CA bundle for creds: %w", err)
}
return params.Internal{
OAuth2Token: creds.OAuth2Token,
ControllerID: p.controllerID,
InstanceCallbackURL: p.config.Default.CallbackURL,
JWTSecret: p.config.JWTAuth.Secret,
GithubCredentialsDetails: params.GithubCredentials{
Name: creds.Name,
Description: creds.Description,
BaseURL: creds.BaseEndpoint(),
APIBaseURL: creds.APIEndpoint(),
UploadBaseURL: creds.UploadEndpoint(),
CABundle: caBundle,
},
}, nil
}
@ -219,8 +272,11 @@ func (r *Runner) ListCredentials(ctx context.Context) ([]params.GithubCredential
for _, val := range r.config.Github {
ret = append(ret, params.GithubCredentials{
Name: val.Name,
Description: val.Description,
Name: val.Name,
Description: val.Description,
BaseURL: val.BaseEndpoint(),
APIBaseURL: val.APIEndpoint(),
UploadBaseURL: val.UploadEndpoint(),
})
}
return ret, nil
@ -238,7 +294,7 @@ func (r *Runner) ListProviders(ctx context.Context) ([]params.Provider, error) {
return ret, nil
}
func (r *Runner) loadReposAndOrgs() error {
func (r *Runner) loadReposOrgsAndEnterprises() error {
r.mux.Lock()
defer r.mux.Unlock()
@ -252,7 +308,12 @@ func (r *Runner) loadReposAndOrgs() error {
return errors.Wrap(err, "fetching organizations")
}
expectedReplies := len(repos) + len(orgs)
enterprises, err := r.store.ListEnterprises(r.ctx)
if err != nil {
return errors.Wrap(err, "fetching enterprises")
}
expectedReplies := len(repos) + len(orgs) + len(enterprises)
errChan := make(chan error, expectedReplies)
for _, repo := range repos {
@ -271,6 +332,14 @@ func (r *Runner) loadReposAndOrgs() error {
}(org)
}
for _, enterprise := range enterprises {
go func(enterprise params.Enterprise) {
log.Printf("creating pool manager for enterprise %s", enterprise.Name)
_, err := r.poolManagerCtrl.CreateEnterprisePoolManager(r.ctx, enterprise, r.providers, r.store)
errChan <- err
}(enterprise)
}
for i := 0; i < expectedReplies; i++ {
select {
case err := <-errChan:
@ -299,7 +368,12 @@ func (r *Runner) Start() error {
return errors.Wrap(err, "fetch org pool managers")
}
expectedReplies := len(repositories) + len(organizations)
enterprises, err := r.poolManagerCtrl.GetEnterprisePoolManagers()
if err != nil {
return errors.Wrap(err, "fetch enterprise pool managers")
}
expectedReplies := len(repositories) + len(organizations) + len(enterprises)
errChan := make(chan error, expectedReplies)
for _, repo := range repositories {
@ -318,6 +392,14 @@ func (r *Runner) Start() error {
}
for _, enterprise := range enterprises {
go func(org common.PoolManager) {
err := org.Start()
errChan <- err
}(enterprise)
}
for i := 0; i < expectedReplies; i++ {
select {
case err := <-errChan:
@ -339,19 +421,49 @@ func (r *Runner) Stop() error {
if err != nil {
return errors.Wrap(err, "fetch repo pool managers")
}
for _, repo := range repos {
if err := repo.Stop(); err != nil {
return errors.Wrap(err, "stopping repo pool manager")
}
}
orgs, err := r.poolManagerCtrl.GetOrgPoolManagers()
if err != nil {
return errors.Wrap(err, "fetch org pool managers")
}
enterprises, err := r.poolManagerCtrl.GetEnterprisePoolManagers()
if err != nil {
return errors.Wrap(err, "fetch enterprise pool managers")
}
expectedReplies := len(repos) + len(orgs) + len(enterprises)
errChan := make(chan error, expectedReplies)
for _, repo := range repos {
go func(poolMgr common.PoolManager) {
err := poolMgr.Stop()
errChan <- err
}(repo)
}
for _, org := range orgs {
if err := org.Stop(); err != nil {
return errors.Wrap(err, "stopping org pool manager")
go func(poolMgr common.PoolManager) {
err := poolMgr.Stop()
errChan <- err
}(org)
}
for _, enterprise := range enterprises {
go func(poolMgr common.PoolManager) {
err := poolMgr.Stop()
errChan <- err
}(enterprise)
}
for i := 0; i < expectedReplies; i++ {
select {
case err := <-errChan:
if err != nil {
return errors.Wrap(err, "stopping pool manager")
}
case <-time.After(60 * time.Second):
return fmt.Errorf("timed out waiting for pool mamager stop")
}
}
return nil
@ -367,6 +479,17 @@ func (r *Runner) Wait() error {
if err != nil {
return errors.Wrap(err, "fetch repo pool managers")
}
orgs, err := r.poolManagerCtrl.GetOrgPoolManagers()
if err != nil {
return errors.Wrap(err, "fetch org pool managers")
}
enterprises, err := r.poolManagerCtrl.GetEnterprisePoolManagers()
if err != nil {
return errors.Wrap(err, "fetch enterprise pool managers")
}
for poolId, repo := range repos {
wg.Add(1)
go func(id string, poolMgr common.PoolManager) {
@ -377,10 +500,6 @@ func (r *Runner) Wait() error {
}(poolId, repo)
}
orgs, err := r.poolManagerCtrl.GetOrgPoolManagers()
if err != nil {
return errors.Wrap(err, "fetch org pool managers")
}
for poolId, org := range orgs {
wg.Add(1)
go func(id string, poolMgr common.PoolManager) {
@ -390,6 +509,17 @@ func (r *Runner) Wait() error {
}
}(poolId, org)
}
for poolId, enterprise := range enterprises {
wg.Add(1)
go func(id string, poolMgr common.PoolManager) {
defer wg.Done()
if err := poolMgr.Wait(); err != nil {
log.Printf("timed out waiting for pool manager %s to exit", id)
}
}(poolId, enterprise)
}
wg.Wait()
return nil
}
@ -456,6 +586,8 @@ func (r *Runner) DispatchWorkflowJob(hookTargetType, signature string, jobData [
poolManager, err = r.findRepoPoolManager(job.Repository.Owner.Login, job.Repository.Name)
case OrganizationHook:
poolManager, err = r.findOrgPoolManager(job.Organization.Login)
case EnterpriseHook:
poolManager, err = r.findEnterprisePoolManager(job.Enterprise.Slug)
default:
return runnerErrors.NewBadRequestError("cannot handle hook target type %s", hookTargetType)
}
@ -480,45 +612,6 @@ func (r *Runner) DispatchWorkflowJob(hookTargetType, signature string, jobData [
return nil
}
func (r *Runner) sshDir() string {
return filepath.Join(r.config.Default.ConfigDir, "ssh")
}
func (r *Runner) sshKeyPath() string {
keyPath := filepath.Join(r.sshDir(), "runner_rsa_key")
return keyPath
}
func (r *Runner) sshPubKeyPath() string {
keyPath := filepath.Join(r.sshDir(), "runner_rsa_key.pub")
return keyPath
}
func (r *Runner) parseSSHKey() (ssh.Signer, error) {
r.mux.Lock()
defer r.mux.Unlock()
key, err := ioutil.ReadFile(r.sshKeyPath())
if err != nil {
return nil, errors.Wrapf(err, "reading private key %s", r.sshKeyPath())
}
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
return nil, errors.Wrapf(err, "parsing private key %s", r.sshKeyPath())
}
return signer, nil
}
func (r *Runner) sshPubKey() ([]byte, error) {
key, err := ioutil.ReadFile(r.sshPubKeyPath())
if err != nil {
return nil, errors.Wrapf(err, "reading public key %s", r.sshPubKeyPath())
}
return key, nil
}
func (r *Runner) appendTagsToCreatePoolParams(param params.CreatePoolParams) (params.CreatePoolParams, error) {
if err := param.Validate(); err != nil {
return params.CreatePoolParams{}, errors.Wrapf(runnerErrors.ErrBadRequest, "validating params: %s", err)
@ -670,6 +763,15 @@ func (r *Runner) ForceDeleteRunner(ctx context.Context, instanceName string) err
if err != nil {
return errors.Wrapf(err, "fetching pool manager for org %s", pool.OrgName)
}
} else if pool.EnterpriseID != "" {
enterprise, err := r.store.GetEnterpriseByID(ctx, pool.EnterpriseID)
if err != nil {
return errors.Wrap(err, "fetching enterprise")
}
poolMgr, err = r.findEnterprisePoolManager(enterprise.Name)
if err != nil {
return errors.Wrapf(err, "fetching pool manager for enterprise %s", pool.EnterpriseName)
}
}
if err := poolMgr.ForceDeleteRunner(instance); err != nil {

View file

@ -21,6 +21,7 @@ type HookTargetType string
const (
RepoHook HookTargetType = "repository"
OrganizationHook HookTargetType = "organization"
EnterpriseHook HookTargetType = "business"
)
var (

15
testdata/config.toml vendored
View file

@ -192,3 +192,18 @@ provider_type = "external"
# to work with repositories, and the admin:org needs to be set if you plan on
# adding an organization.
oauth2_token = "super secret token"
# base_url (optional) is the URL at which your GitHub Enterprise Server can be accessed.
# If these credentials are for github.com, leave this setting blank
base_url = "https://ghe.example.com"
# api_base_url (optional) is the base URL where the GitHub Enterprise Server API can be accessed.
# Leave this blank if these credentials are for github.com.
api_base_url = "https://ghe.example.com"
# upload_base_url (optional) is the base URL where the GitHub Enterprise Server upload API can be accessed.
# Leave this blank if these credentials are for github.com, or if you don't have a separate URL
# for the upload API.
upload_base_url = "https://api.ghe.example.com"
# ca_cert_bundle (optional) is the CA certificate bundle in PEM format that will be used by the github
# client to talk to the API. This bundle will also be sent to all runners as bootstrap params.
# Use this option if you're using a self signed certificate.
# Leave this blank if you're using github.com or if your certificare is signed by a valid CA.
ca_cert_bundle = "/etc/garm/ghe.crt"

View file

@ -19,10 +19,13 @@ import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"regexp"
@ -35,7 +38,7 @@ import (
"garm/params"
"garm/runner/common"
"github.com/google/go-github/v43/github"
"github.com/google/go-github/v48/github"
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
"golang.org/x/oauth2"
@ -160,32 +163,64 @@ func OSToOSType(os string) (config.OSType, error) {
return osType, nil
}
func GithubClient(ctx context.Context, token string) (common.GithubClient, error) {
func GithubClient(ctx context.Context, token string, credsDetails params.GithubCredentials) (common.GithubClient, common.GithubEnterpriseClient, error) {
var roots *x509.CertPool
if credsDetails.CABundle != nil && len(credsDetails.CABundle) > 0 {
roots = x509.NewCertPool()
ok := roots.AppendCertsFromPEM(credsDetails.CABundle)
if !ok {
return nil, nil, fmt.Errorf("failed to parse CA cert")
}
}
httpTransport := &http.Transport{
TLSClientConfig: &tls.Config{
ClientCAs: roots,
},
}
httpClient := &http.Client{Transport: httpTransport}
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
tc := oauth2.NewClient(ctx, ts)
ghClient := github.NewClient(tc)
ghClient, err := github.NewEnterpriseClient(credsDetails.APIBaseURL, credsDetails.UploadBaseURL, tc)
if err != nil {
return nil, nil, errors.Wrap(err, "fetching github client")
}
return ghClient.Actions, nil
return ghClient.Actions, ghClient.Enterprise, nil
}
func GetCloudConfig(bootstrapParams params.BootstrapInstance, tools github.RunnerApplicationDownload, runnerName string) (string, error) {
cloudCfg := cloudconfig.NewDefaultCloudInitConfig()
if tools.Filename == nil {
return "", fmt.Errorf("missing tools filename")
}
if tools.DownloadURL == nil {
return "", fmt.Errorf("missing tools download URL")
}
var tempToken string
if tools.TempDownloadToken != nil {
tempToken = *tools.TempDownloadToken
}
installRunnerParams := cloudconfig.InstallRunnerParams{
FileName: *tools.Filename,
DownloadURL: *tools.DownloadURL,
GithubToken: bootstrapParams.GithubRunnerAccessToken,
RunnerUsername: config.DefaultUser,
RunnerGroup: config.DefaultUser,
RepoURL: bootstrapParams.RepoURL,
RunnerName: runnerName,
RunnerLabels: strings.Join(bootstrapParams.Labels, ","),
CallbackURL: bootstrapParams.CallbackURL,
CallbackToken: bootstrapParams.InstanceToken,
FileName: *tools.Filename,
DownloadURL: *tools.DownloadURL,
TempDownloadToken: tempToken,
GithubToken: bootstrapParams.GithubRunnerAccessToken,
RunnerUsername: config.DefaultUser,
RunnerGroup: config.DefaultUser,
RepoURL: bootstrapParams.RepoURL,
RunnerName: runnerName,
RunnerLabels: strings.Join(bootstrapParams.Labels, ","),
CallbackURL: bootstrapParams.CallbackURL,
CallbackToken: bootstrapParams.InstanceToken,
}
installScript, err := cloudconfig.InstallRunnerScript(installRunnerParams)
@ -198,6 +233,12 @@ func GetCloudConfig(bootstrapParams params.BootstrapInstance, tools github.Runne
cloudCfg.AddRunCmd("/install_runner.sh")
cloudCfg.AddRunCmd("rm -f /install_runner.sh")
if bootstrapParams.CACertBundle != nil && len(bootstrapParams.CACertBundle) > 0 {
if err := cloudCfg.AddCACert(bootstrapParams.CACertBundle); err != nil {
return "", errors.Wrap(err, "adding CA cert bundle")
}
}
asStr, err := cloudCfg.Serialize()
if err != nil {
return "", errors.Wrap(err, "creating cloud config")

View file

@ -1,38 +0,0 @@
// 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"
)
// RepositoryMergeRequest represents a request to merge a branch in a
// repository.
type RepositoryMergeRequest struct {
Base *string `json:"base,omitempty"`
Head *string `json:"head,omitempty"`
CommitMessage *string `json:"commit_message,omitempty"`
}
// Merge a branch in the specified repository.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#merge-a-branch
func (s *RepositoriesService) Merge(ctx context.Context, owner, repo string, request *RepositoryMergeRequest) (*RepositoryCommit, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/merges", owner, repo)
req, err := s.client.NewRequest("POST", u, request)
if err != nil {
return nil, nil, err
}
commit := new(RepositoryCommit)
resp, err := s.client.Do(ctx, req, commit)
if err != nil {
return nil, resp, err
}
return commit, resp, nil
}

View file

@ -59,6 +59,7 @@ Beyang Liu <beyang.liu@gmail.com>
Billy Keyes <bluekeyes@gmail.com>
Billy Lynch <wlynch92@gmail.com>
Björn Häuser <b.haeuser@rebuy.de>
Bjorn Neergaard <bjorn@neersighted.com>
boljen <bol.christophe@gmail.com>
Brad Harris <bmharris@gmail.com>
Brad Moylan <moylan.brad@gmail.com>

View file

@ -8,5 +8,5 @@ package github
// ActionsService handles communication with the actions related
// methods of the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/
// GitHub API docs: https://docs.github.com/en/rest/actions/
type ActionsService service

View file

@ -16,7 +16,7 @@ import (
// data between jobs in a workflow and provide storage for data
// once a workflow is complete.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#artifacts
// 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"`
@ -30,7 +30,7 @@ type Artifact struct {
// ArtifactList represents a list of GitHub artifacts.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#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"`
@ -38,7 +38,7 @@ type ArtifactList struct {
// ListArtifacts lists all artifacts that belong to a repository.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-artifacts-for-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)
@ -62,7 +62,7 @@ func (s *ActionsService) ListArtifacts(ctx context.Context, owner, repo string,
// ListWorkflowRunArtifacts lists all artifacts that belong to a workflow run.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-workflow-run-artifacts
// 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)
@ -86,7 +86,7 @@ func (s *ActionsService) ListWorkflowRunArtifacts(ctx context.Context, owner, re
// GetArtifact gets a specific artifact for a workflow run.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-an-artifact
// 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)
@ -106,52 +106,31 @@ func (s *ActionsService) GetArtifact(ctx context.Context, owner, repo string, ar
// DownloadArtifact gets a redirect URL to download an archive for a repository.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#download-an-artifact
// 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.getDownloadArtifactFromURL(ctx, u, followRedirects)
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
}
func (s *ActionsService) getDownloadArtifactFromURL(ctx context.Context, u string, followRedirects bool) (*http.Response, error) {
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}
var resp *http.Response
// Use http.DefaultTransport if no custom Transport is configured
req = withContext(ctx, req)
if s.client.client.Transport == nil {
resp, err = http.DefaultTransport.RoundTrip(req)
} else {
resp, err = s.client.client.Transport.RoundTrip(req)
}
if err != nil {
return nil, err
}
resp.Body.Close()
// If redirect response is returned, follow it
if followRedirects && resp.StatusCode == http.StatusMovedPermanently {
u = resp.Header.Get("Location")
resp, err = s.getDownloadArtifactFromURL(ctx, u, false)
}
return resp, err
}
// DeleteArtifact deletes a workflow run artifact.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#delete-an-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)

View file

@ -61,10 +61,18 @@ type SetRunnerGroupRunnersRequest struct {
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/reference/actions#list-self-hosted-runner-groups-for-an-organization
func (s *ActionsService) ListOrganizationRunnerGroups(ctx context.Context, org string, opts *ListOptions) (*RunnerGroups, *Response, error) {
// 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 {
@ -87,7 +95,7 @@ func (s *ActionsService) ListOrganizationRunnerGroups(ctx context.Context, org s
// GetOrganizationRunnerGroup gets a specific self-hosted runner group for an organization using its RunnerGroup ID.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/actions#get-a-self-hosted-runner-group-for-an-organization
// 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)
@ -106,7 +114,7 @@ func (s *ActionsService) GetOrganizationRunnerGroup(ctx context.Context, org str
// DeleteOrganizationRunnerGroup deletes a self-hosted runner group from an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/actions#delete-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)
@ -120,7 +128,7 @@ func (s *ActionsService) DeleteOrganizationRunnerGroup(ctx context.Context, org
// CreateOrganizationRunnerGroup creates a new self-hosted runner group for an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/actions#create-a-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)
@ -139,7 +147,7 @@ func (s *ActionsService) CreateOrganizationRunnerGroup(ctx context.Context, org
// UpdateOrganizationRunnerGroup updates a self-hosted runner group for an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/actions#update-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)
@ -158,7 +166,7 @@ func (s *ActionsService) UpdateOrganizationRunnerGroup(ctx context.Context, org
// 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/reference/actions#list-repository-access-to-a-self-hosted-runner-group-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)
@ -183,7 +191,7 @@ func (s *ActionsService) ListRepositoryAccessRunnerGroup(ctx context.Context, or
// 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/reference/actions#set-repository-access-for-a-self-hosted-runner-group-in-an-organization
// 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)
@ -198,7 +206,7 @@ func (s *ActionsService) SetRepositoryAccessRunnerGroup(ctx context.Context, org
// 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/reference/actions#add-repository-access-to-a-self-hosted-runner-group-in-an-organization
// 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)
@ -213,7 +221,7 @@ func (s *ActionsService) AddRepositoryAccessRunnerGroup(ctx context.Context, org
// 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/reference/actions#remove-repository-access-to-a-self-hosted-runner-group-in-an-organization
// 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)
@ -227,7 +235,7 @@ func (s *ActionsService) RemoveRepositoryAccessRunnerGroup(ctx context.Context,
// ListRunnerGroupRunners lists self-hosted runners that are in a specific organization group.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/actions#list-self-hosted-runners-in-a-group-for-an-organization
// 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)
@ -252,7 +260,7 @@ func (s *ActionsService) ListRunnerGroupRunners(ctx context.Context, org string,
// 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/reference/actions#set-self-hosted-runners-in-a-group-for-an-organization
// 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)
@ -266,7 +274,7 @@ func (s *ActionsService) SetRunnerGroupRunners(ctx context.Context, org string,
// AddRunnerGroupRunners adds a self-hosted runner to a runner group configured in an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/actions#add-a-self-hosted-runner-to-a-group-for-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)
@ -281,7 +289,7 @@ func (s *ActionsService) AddRunnerGroupRunners(ctx context.Context, org string,
// 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/reference/actions#remove-a-self-hosted-runner-from-a-group-for-an-organization
// 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)

View file

@ -28,7 +28,7 @@ type ActionsEnabledOnOrgRepos struct {
// ListRunnerApplicationDownloads lists self-hosted runner application binaries that can be downloaded and run.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-runner-applications-for-a-repository
// 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)
@ -53,7 +53,7 @@ type RegistrationToken struct {
// CreateRegistrationToken creates a token that can be used to add a self-hosted runner.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#create-a-registration-token-for-a-repository
// 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)
@ -96,7 +96,7 @@ type Runners struct {
// ListRunners lists all the self-hosted runners for a repository.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-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)
@ -120,7 +120,7 @@ func (s *ActionsService) ListRunners(ctx context.Context, owner, repo string, op
// GetRunner gets a specific self-hosted runner for a repository using its runner ID.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-a-self-hosted-runner-for-a-repository
// 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)
@ -145,7 +145,7 @@ type RemoveToken struct {
// 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/free-pro-team@latest/rest/reference/actions/#create-a-remove-token-for-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)
@ -165,7 +165,7 @@ func (s *ActionsService) CreateRemoveToken(ctx context.Context, owner, repo stri
// RemoveRunner forces the removal of a self-hosted runner in a repository using the runner id.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#delete-a-self-hosted-runner-from-a-repository
// 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)
@ -179,7 +179,7 @@ func (s *ActionsService) RemoveRunner(ctx context.Context, owner, repo string, r
// ListOrganizationRunnerApplicationDownloads lists self-hosted runner application binaries that can be downloaded and run.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-runner-applications-for-an-organization
// 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)
@ -198,7 +198,7 @@ func (s *ActionsService) ListOrganizationRunnerApplicationDownloads(ctx context.
// 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/free-pro-team@latest/rest/reference/actions/#create-a-registration-token-for-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)
@ -218,7 +218,7 @@ func (s *ActionsService) CreateOrganizationRegistrationToken(ctx context.Context
// ListOrganizationRunners lists all the self-hosted runners for an organization.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-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)
@ -242,7 +242,7 @@ func (s *ActionsService) ListOrganizationRunners(ctx context.Context, owner stri
// ListEnabledReposInOrg lists the selected repositories that are enabled for GitHub Actions in an organization.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-selected-repositories-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)
@ -266,7 +266,7 @@ func (s *ActionsService) ListEnabledReposInOrg(ctx context.Context, owner string
// 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/reference/actions#set-selected-repositories-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)
@ -287,7 +287,7 @@ func (s *ActionsService) SetEnabledReposInOrg(ctx context.Context, owner string,
// 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/reference/actions#enable-a-selected-repository-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)
@ -306,7 +306,7 @@ func (s *ActionsService) AddEnabledReposInOrg(ctx context.Context, owner string,
// 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/reference/actions#disable-a-selected-repository-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)
@ -325,7 +325,7 @@ func (s *ActionsService) RemoveEnabledRepoInOrg(ctx context.Context, owner strin
// GetOrganizationRunner gets a specific self-hosted runner for an organization using its runner ID.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-a-self-hosted-runner-for-an-organization
// 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)
@ -344,7 +344,7 @@ func (s *ActionsService) GetOrganizationRunner(ctx context.Context, owner string
// 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/free-pro-team@latest/rest/reference/actions/#create-a-remove-token-for-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)
@ -364,7 +364,7 @@ func (s *ActionsService) CreateOrganizationRemoveToken(ctx context.Context, owne
// RemoveOrganizationRunner forces the removal of a self-hosted runner from an organization using the runner id.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#delete-a-self-hosted-runner-from-an-organization
// 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)

View file

@ -64,7 +64,7 @@ func (s *ActionsService) getPublicKey(ctx context.Context, url string) (*PublicK
// GetRepoPublicKey gets a public key that should be used for secret encryption.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-a-repository-public-key
// 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)
@ -72,7 +72,7 @@ func (s *ActionsService) GetRepoPublicKey(ctx context.Context, owner, repo strin
// GetOrgPublicKey gets a public key that should be used for secret encryption.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-an-organization-public-key
// 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)
@ -80,7 +80,7 @@ func (s *ActionsService) GetOrgPublicKey(ctx context.Context, org string) (*Publ
// GetEnvPublicKey gets a public key that should be used for secret encryption.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/actions#get-an-environment-public-key
// 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)
@ -124,7 +124,7 @@ func (s *ActionsService) listSecrets(ctx context.Context, url string, opts *List
// ListRepoSecrets lists all secrets available in a repository
// without revealing their encrypted values.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-repository-secrets
// 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)
@ -133,7 +133,7 @@ func (s *ActionsService) ListRepoSecrets(ctx context.Context, owner, repo string
// ListOrgSecrets lists all secrets available in an organization
// without revealing their encrypted values.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-organization-secrets
// 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)
@ -141,7 +141,7 @@ func (s *ActionsService) ListOrgSecrets(ctx context.Context, org string, opts *L
// ListEnvSecrets lists all secrets available in an environment.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/actions#list-environment-secrets
// 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)
@ -164,7 +164,7 @@ func (s *ActionsService) getSecret(ctx context.Context, url string) (*Secret, *R
// GetRepoSecret gets a single repository secret without revealing its encrypted value.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-a-repository-secret
// 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)
@ -172,7 +172,7 @@ func (s *ActionsService) GetRepoSecret(ctx context.Context, owner, repo, name st
// GetOrgSecret gets a single organization secret without revealing its encrypted value.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-an-organization-secret
// 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)
@ -180,13 +180,13 @@ func (s *ActionsService) GetOrgSecret(ctx context.Context, org, name string) (*S
// GetEnvSecret gets a single environment secret without revealing its encrypted value.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/actions#list-environment-secrets
// 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 secret.
// 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.
@ -213,7 +213,7 @@ func (s *ActionsService) putSecret(ctx context.Context, url string, eSecret *Enc
// CreateOrUpdateRepoSecret creates or updates a repository secret with an encrypted value.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#create-or-update-a-repository-secret
// 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)
@ -221,7 +221,7 @@ func (s *ActionsService) CreateOrUpdateRepoSecret(ctx context.Context, owner, re
// CreateOrUpdateOrgSecret creates or updates an organization secret with an encrypted value.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#create-or-update-an-organization-secret
// 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)
@ -229,7 +229,7 @@ func (s *ActionsService) CreateOrUpdateOrgSecret(ctx context.Context, org string
// CreateOrUpdateEnvSecret creates or updates a single environment secret with an encrypted value.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/actions#create-or-update-an-environment-secret
// 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)
@ -246,7 +246,7 @@ func (s *ActionsService) deleteSecret(ctx context.Context, url string) (*Respons
// DeleteRepoSecret deletes a secret in a repository using the secret name.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#delete-a-repository-secret
// 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)
@ -254,7 +254,7 @@ func (s *ActionsService) DeleteRepoSecret(ctx context.Context, owner, repo, name
// DeleteOrgSecret deletes a secret in an organization using the secret name.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#delete-an-organization-secret
// 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)
@ -262,7 +262,7 @@ func (s *ActionsService) DeleteOrgSecret(ctx context.Context, org, name string)
// DeleteEnvSecret deletes a secret in an environment using the secret name.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/actions#delete-an-environment-secret
// 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)
@ -296,7 +296,7 @@ func (s *ActionsService) listSelectedReposForSecret(ctx context.Context, url str
// ListSelectedReposForOrgSecret lists all repositories that have access to a secret.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-selected-repositories-for-an-organization-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)
@ -317,7 +317,7 @@ func (s *ActionsService) setSelectedReposForSecret(ctx context.Context, url stri
// SetSelectedReposForOrgSecret sets the repositories that have access to a secret.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#set-selected-repositories-for-an-organization-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)
@ -334,7 +334,7 @@ func (s *ActionsService) addSelectedRepoToSecret(ctx context.Context, url string
// AddSelectedRepoToOrgSecret adds a repository to an organization secret.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#add-selected-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)
@ -351,7 +351,7 @@ func (s *ActionsService) removeSelectedRepoFromSecret(ctx context.Context, url s
// RemoveSelectedRepoFromOrgSecret removes a repository from an organization secret.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#remove-selected-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

@ -66,7 +66,7 @@ type ListWorkflowJobsOptions struct {
// ListWorkflowJobs lists all jobs for a workflow run.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-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)
@ -90,7 +90,7 @@ func (s *ActionsService) ListWorkflowJobs(ctx context.Context, owner, repo strin
// GetWorkflowJobByID gets a specific job in a workflow run by ID.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-a-job-for-a-workflow-run
// 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)
@ -110,45 +110,20 @@ func (s *ActionsService) GetWorkflowJobByID(ctx context.Context, owner, repo str
// 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/free-pro-team@latest/rest/reference/actions/#download-job-logs-for-a-workflow-run
// 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.getWorkflowLogsFromURL(ctx, u, followRedirects)
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
}
func (s *ActionsService) getWorkflowLogsFromURL(ctx context.Context, u string, followRedirects bool) (*http.Response, error) {
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}
var resp *http.Response
// Use http.DefaultTransport if no custom Transport is configured
req = withContext(ctx, req)
if s.client.client.Transport == nil {
resp, err = http.DefaultTransport.RoundTrip(req)
} else {
resp, err = s.client.client.Transport.RoundTrip(req)
}
if err != nil {
return nil, err
}
resp.Body.Close()
// If redirect response is returned, follow it
if followRedirects && resp.StatusCode == http.StatusMovedPermanently {
u = resp.Header.Get("Location")
resp, err = s.getWorkflowLogsFromURL(ctx, u, false)
}
return resp, err
}

View file

@ -44,6 +44,7 @@ type WorkflowRun struct {
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.
@ -93,6 +94,14 @@ 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 {
@ -115,7 +124,7 @@ func (s *ActionsService) listWorkflowRuns(ctx context.Context, endpoint string,
// ListWorkflowRunsByID lists all workflow runs by workflow ID.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-workflow-runs
// 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)
@ -123,7 +132,7 @@ func (s *ActionsService) ListWorkflowRunsByID(ctx context.Context, owner, repo s
// ListWorkflowRunsByFileName lists all workflow runs by workflow file name.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-workflow-runs
// 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)
@ -131,7 +140,7 @@ func (s *ActionsService) ListWorkflowRunsByFileName(ctx context.Context, owner,
// ListRepositoryWorkflowRuns lists all workflow runs for a repository.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-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)
@ -155,7 +164,7 @@ func (s *ActionsService) ListRepositoryWorkflowRuns(ctx context.Context, owner,
// GetWorkflowRunByID gets a specific workflow run by ID.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-a-workflow-run
// 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)
@ -175,7 +184,7 @@ func (s *ActionsService) GetWorkflowRunByID(ctx context.Context, owner, repo str
// GetWorkflowRunAttempt gets a specific workflow run attempt.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-a-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)
@ -199,7 +208,7 @@ func (s *ActionsService) GetWorkflowRunAttempt(ctx context.Context, owner, repo
// RerunWorkflowByID re-runs a workflow by ID.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#re-run-a-workflow
// 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)
@ -211,9 +220,37 @@ func (s *ActionsService) RerunWorkflowByID(ctx context.Context, owner, repo stri
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/free-pro-team@latest/rest/reference/actions/#cancel-a-workflow-run
// 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)
@ -227,25 +264,27 @@ func (s *ActionsService) CancelWorkflowRunByID(ctx context.Context, owner, repo
// 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/free-pro-team@latest/rest/reference/actions/#download-workflow-run-logs
// 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.getWorkflowLogsFromURL(ctx, u, followRedirects)
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/reference/actions#delete-a-workflow-run
// 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)
@ -259,7 +298,7 @@ func (s *ActionsService) DeleteWorkflowRun(ctx context.Context, owner, repo stri
// DeleteWorkflowRunLogs deletes all logs for a workflow run.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#delete-workflow-run-logs
// 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)
@ -273,7 +312,7 @@ func (s *ActionsService) DeleteWorkflowRunLogs(ctx context.Context, owner, repo
// GetWorkflowRunUsageByID gets a specific workflow usage run by run ID in the unit of billable milliseconds.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-workflow-run-usage
// 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)
@ -290,3 +329,23 @@ func (s *ActionsService) GetWorkflowRunUsageByID(ctx context.Context, owner, rep
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

@ -61,7 +61,7 @@ type CreateWorkflowDispatchEventRequest struct {
// ListWorkflows lists all workflows in a repository.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#list-repository-workflows
// 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)
@ -85,7 +85,7 @@ func (s *ActionsService) ListWorkflows(ctx context.Context, owner, repo string,
// GetWorkflowByID gets a specific workflow by ID.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-a-workflow
// 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)
@ -94,7 +94,7 @@ func (s *ActionsService) GetWorkflowByID(ctx context.Context, owner, repo string
// GetWorkflowByFileName gets a specific workflow by file name.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-a-workflow
// 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)
@ -118,7 +118,7 @@ func (s *ActionsService) getWorkflow(ctx context.Context, url string) (*Workflow
// GetWorkflowUsageByID gets a specific workflow usage by ID in the unit of billable milliseconds.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-workflow-usage
// 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)
@ -127,7 +127,7 @@ func (s *ActionsService) GetWorkflowUsageByID(ctx context.Context, owner, repo s
// GetWorkflowUsageByFileName gets a specific workflow usage by file name in the unit of billable milliseconds.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#get-workflow-usage
// 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)
@ -151,7 +151,7 @@ func (s *ActionsService) getWorkflowUsage(ctx context.Context, url string) (*Wor
// CreateWorkflowDispatchEventByID manually triggers a GitHub Actions workflow run.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#create-a-workflow-dispatch-event
// 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)
@ -160,7 +160,7 @@ func (s *ActionsService) CreateWorkflowDispatchEventByID(ctx context.Context, ow
// CreateWorkflowDispatchEventByFileName manually triggers a GitHub Actions workflow run.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#create-a-workflow-dispatch-event
// 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)
@ -178,7 +178,7 @@ func (s *ActionsService) createWorkflowDispatchEvent(ctx context.Context, url st
// EnableWorkflowByID enables a workflow and sets the state of the workflow to "active".
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#enable-a-workflow
// 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)
@ -186,7 +186,7 @@ func (s *ActionsService) EnableWorkflowByID(ctx context.Context, owner, repo str
// EnableWorkflowByFileName enables a workflow and sets the state of the workflow to "active".
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#enable-a-workflow
// 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)
@ -194,7 +194,7 @@ func (s *ActionsService) EnableWorkflowByFileName(ctx context.Context, owner, re
// DisableWorkflowByID disables a workflow and sets the state of the workflow to "disabled_manually".
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#disable-a-workflow
// 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)
@ -202,7 +202,7 @@ func (s *ActionsService) DisableWorkflowByID(ctx context.Context, owner, repo st
// DisableWorkflowByFileName disables a workflow and sets the state of the workflow to "disabled_manually".
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/actions/#disable-a-workflow
// 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)

View file

@ -10,7 +10,7 @@ import "context"
// ActivityService handles communication with the activity related
// methods of the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/
// GitHub API docs: https://docs.github.com/en/rest/activity/
type ActivityService service
// FeedLink represents a link to a related resource.
@ -45,14 +45,15 @@ type FeedLinks struct {
// 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.
//
// 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.

View file

@ -12,7 +12,7 @@ import (
// ListEvents drinks from the firehose of all public events across GitHub.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#list-public-events
// 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 {
@ -35,7 +35,7 @@ func (s *ActivityService) ListEvents(ctx context.Context, opts *ListOptions) ([]
// ListRepositoryEvents lists events for a repository.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#list-repository-events
// 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)
@ -59,7 +59,7 @@ func (s *ActivityService) ListRepositoryEvents(ctx context.Context, owner, repo
// ListIssueEventsForRepository lists issue events for a repository.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/issues/#list-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)
@ -83,7 +83,7 @@ func (s *ActivityService) ListIssueEventsForRepository(ctx context.Context, owne
// ListEventsForRepoNetwork lists public events for a network of repositories.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#list-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)
@ -107,7 +107,7 @@ func (s *ActivityService) ListEventsForRepoNetwork(ctx context.Context, owner, r
// ListEventsForOrganization lists public events for an organization.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#list-public-organization-events
// 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)
@ -132,8 +132,8 @@ func (s *ActivityService) ListEventsForOrganization(ctx context.Context, org str
// 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/free-pro-team@latest/rest/reference/activity/#list-events-for-the-authenticated-user
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#list-public-events-for-a-user
// 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 {
@ -163,8 +163,8 @@ func (s *ActivityService) ListEventsPerformedByUser(ctx context.Context, user st
// 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/free-pro-team@latest/rest/reference/activity/#list-events-received-by-the-authenticated-user
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#list-public-events-received-by-a-user
// 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 {
@ -194,7 +194,7 @@ func (s *ActivityService) ListEventsReceivedByUser(ctx context.Context, user str
// ListUserEventsForOrganization provides the users organization dashboard. You
// must be authenticated as the user to view this.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#list-organization-events-for-the-authenticated-user
// 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)

View file

@ -19,7 +19,7 @@ type Notification struct {
// Reason identifies the event that triggered the notification.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity#notification-reasons
// GitHub API docs: https://docs.github.com/en/rest/activity#notification-reasons
Reason *string `json:"reason,omitempty"`
Unread *bool `json:"unread,omitempty"`
@ -49,7 +49,7 @@ type NotificationListOptions struct {
// ListNotifications lists all notifications for the authenticated user.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#list-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)
@ -74,7 +74,7 @@ func (s *ActivityService) ListNotifications(ctx context.Context, opts *Notificat
// ListRepositoryNotifications lists all notifications in a given repository
// for the authenticated user.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#list-repository-notifications-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)
@ -102,7 +102,7 @@ type markReadOptions struct {
// MarkNotificationsRead marks all notifications up to lastRead as read.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity#mark-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,
@ -118,7 +118,7 @@ func (s *ActivityService) MarkNotificationsRead(ctx context.Context, lastRead ti
// MarkRepositoryNotificationsRead marks all notifications up to lastRead in
// the specified repository as read.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#mark-repository-notifications-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,
@ -134,7 +134,7 @@ func (s *ActivityService) MarkRepositoryNotificationsRead(ctx context.Context, o
// GetThread gets the specified notification thread.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#get-a-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)
@ -154,7 +154,7 @@ func (s *ActivityService) GetThread(ctx context.Context, id string) (*Notificati
// MarkThreadRead marks the specified thread as read.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#mark-a-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)
@ -169,7 +169,7 @@ func (s *ActivityService) MarkThreadRead(ctx context.Context, id string) (*Respo
// GetThreadSubscription checks to see if the authenticated user is subscribed
// to a thread.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#get-a-thread-subscription-for-the-authenticated-user
// 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)
@ -190,7 +190,7 @@ func (s *ActivityService) GetThreadSubscription(ctx context.Context, id string)
// SetThreadSubscription sets the subscription for the specified thread for the
// authenticated user.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#set-a-thread-subscription
// 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)
@ -211,7 +211,7 @@ func (s *ActivityService) SetThreadSubscription(ctx context.Context, id string,
// DeleteThreadSubscription deletes the subscription for the specified thread
// for the authenticated user.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#delete-a-thread-subscription
// 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)

View file

@ -25,7 +25,7 @@ type Stargazer struct {
// ListStargazers lists people who have starred the specified repo.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#list-stargazers
// 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)
@ -67,8 +67,8 @@ type ActivityListStarredOptions struct {
// 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/free-pro-team@latest/rest/reference/activity/#list-repositories-starred-by-the-authenticated-user
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#list-repositories-starred-by-a-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 != "" {
@ -101,13 +101,14 @@ func (s *ActivityService) ListStarred(ctx context.Context, user string, opts *Ac
// IsStarred checks if a repository is starred by authenticated user.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#check-if-a-repository-is-starred-by-the-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
@ -115,24 +116,26 @@ func (s *ActivityService) IsStarred(ctx context.Context, owner, repo string) (bo
// Star a repository as the authenticated user.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#star-a-repository-for-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/free-pro-team@latest/rest/reference/activity/#unstar-a-repository-for-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

@ -27,7 +27,7 @@ type Subscription struct {
// ListWatchers lists watchers of a particular repo.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#list-watchers
// 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)
@ -52,8 +52,8 @@ func (s *ActivityService) ListWatchers(ctx context.Context, owner, repo string,
// 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/free-pro-team@latest/rest/reference/activity/#list-repositories-watched-by-the-authenticated-user
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/activity/#list-repositories-watched-by-a-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 != "" {
@ -84,7 +84,7 @@ func (s *ActivityService) ListWatched(ctx context.Context, user string, opts *Li
// 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/free-pro-team@latest/rest/reference/activity/#get-a-repository-subscription
// 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)
@ -111,7 +111,7 @@ func (s *ActivityService) GetRepositorySubscription(ctx context.Context, owner,
// 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/free-pro-team@latest/rest/reference/activity/#set-a-repository-subscription
// 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)
@ -135,7 +135,7 @@ func (s *ActivityService) SetRepositorySubscription(ctx context.Context, owner,
// 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/free-pro-team@latest/rest/reference/activity/#delete-a-repository-subscription
// 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)

View file

@ -14,7 +14,7 @@ import (
// GitHub API. These API routes are normally only accessible for GitHub
// Enterprise installations.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/enterprise/
// 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.
@ -82,7 +82,7 @@ func (m Enterprise) String() string {
// UpdateUserLDAPMapping updates the mapping between a GitHub user and an LDAP user.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/enterprise/ldap/#update-ldap-mapping-for-a-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)
@ -101,7 +101,7 @@ func (s *AdminService) UpdateUserLDAPMapping(ctx context.Context, user string, m
// UpdateTeamLDAPMapping updates the mapping between a GitHub team and an LDAP group.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/enterprise/ldap/#update-ldap-mapping-for-a-team
// 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)

View file

@ -153,7 +153,7 @@ func (s RepoStats) String() string {
// 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/free-pro-team@latest/rest/reference/enterprise-admin/admin_stats/
// 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)

View file

@ -14,7 +14,7 @@ import (
// AppsService provides access to the installation related functions
// in the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/
// GitHub API docs: https://docs.github.com/en/rest/apps/
type AppsService service
// App represents a GitHub App.
@ -59,8 +59,9 @@ type InstallationTokenOptions struct {
// InstallationPermissions lists the repository and organization permissions for an installation.
//
// Permission names taken from:
// https://docs.github.com/en/enterprise-server@3.0/rest/reference/apps#create-an-installation-access-token-for-an-app
// https://docs.github.com/en/rest/reference/apps#create-an-installation-access-token-for-an-app
//
// 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"`
@ -76,7 +77,9 @@ type InstallationPermissions struct {
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"`
@ -148,8 +151,8 @@ func (i Installation) String() string {
// 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/free-pro-team@latest/rest/reference/apps/#get-the-authenticated-app
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#get-an-app
// 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 != "" {
@ -174,7 +177,7 @@ func (s *AppsService) Get(ctx context.Context, appSlug string) (*App, *Response,
// ListInstallations lists the installations that the current GitHub App has.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#list-installations-for-the-authenticated-app
// 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 {
@ -197,14 +200,14 @@ func (s *AppsService) ListInstallations(ctx context.Context, opts *ListOptions)
// GetInstallation returns the specified installation.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#get-an-installation-for-the-authenticated-app
// 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/free-pro-team@latest/rest/reference/apps/#list-app-installations-accessible-to-the-user-access-token
// 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 {
@ -229,7 +232,7 @@ func (s *AppsService) ListUserInstallations(ctx context.Context, opts *ListOptio
// SuspendInstallation suspends the specified installation.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#suspend-an-app-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)
@ -243,7 +246,7 @@ func (s *AppsService) SuspendInstallation(ctx context.Context, id int64) (*Respo
// UnsuspendInstallation unsuspends the specified installation.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#unsuspend-an-app-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)
@ -257,7 +260,7 @@ func (s *AppsService) UnsuspendInstallation(ctx context.Context, id int64) (*Res
// DeleteInstallation deletes the specified installation.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#delete-an-installation-for-the-authenticated-app
// 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)
@ -271,7 +274,7 @@ func (s *AppsService) DeleteInstallation(ctx context.Context, id int64) (*Respon
// CreateInstallationToken creates a new installation token.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#create-an-installation-access-token-for-an-app
// 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)
@ -291,7 +294,7 @@ func (s *AppsService) CreateInstallationToken(ctx context.Context, id int64, opt
// CreateAttachment creates a new attachment on user comment containing a url.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#create-a-content-attachment
// 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)}
@ -314,14 +317,14 @@ func (s *AppsService) CreateAttachment(ctx context.Context, contentReferenceID i
// FindOrganizationInstallation finds the organization's installation information.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#get-an-organization-installation-for-the-authenticated-app
// 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/free-pro-team@latest/rest/reference/apps/#get-a-repository-installation-for-the-authenticated-app
// 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))
}
@ -335,7 +338,7 @@ func (s *AppsService) FindRepositoryInstallationByID(ctx context.Context, id int
// FindUserInstallation finds the user's installation information.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#get-a-user-installation-for-the-authenticated-app
// 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))
}

View file

@ -12,7 +12,7 @@ import (
// 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/free-pro-team@latest/rest/reference/apps#get-a-webhook-configuration-for-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 {
@ -31,7 +31,7 @@ func (s *AppsService) GetHookConfig(ctx context.Context) (*HookConfig, *Response
// 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/free-pro-team@latest/rest/reference/apps#update-a-webhook-configuration-for-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 {

View file

@ -12,7 +12,7 @@ import (
// ListHookDeliveries lists deliveries of an App webhook.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/apps#list-deliveries-for-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 {
@ -35,7 +35,7 @@ func (s *AppsService) ListHookDeliveries(ctx context.Context, opts *ListCursorOp
// GetHookDelivery returns the App webhook delivery with the specified ID.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/apps#get-a-delivery-for-an-app-webhook
// 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)
@ -54,7 +54,7 @@ func (s *AppsService) GetHookDelivery(ctx context.Context, deliveryID int64) (*H
// RedeliverHookDelivery redelivers a delivery for an App webhook.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/apps#redeliver-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)

View file

@ -19,7 +19,7 @@ type ListRepositories struct {
// ListRepos lists the repositories that are accessible to the authenticated installation.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#list-repositories-accessible-to-the-app-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 {
@ -52,7 +52,7 @@ func (s *AppsService) ListRepos(ctx context.Context, opts *ListOptions) (*ListRe
// ListUserRepos lists repositories that are accessible
// to the authenticated user for an installation.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#list-repositories-accessible-to-the-user-access-token
// 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)
@ -84,7 +84,7 @@ func (s *AppsService) ListUserRepos(ctx context.Context, id int64, opts *ListOpt
// AddRepository adds a single repository to an installation.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#add-a-repository-to-an-app-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)
@ -103,7 +103,7 @@ func (s *AppsService) AddRepository(ctx context.Context, instID, repoID int64) (
// RemoveRepository removes a single repository from an installation.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#remove-a-repository-from-an-app-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)
@ -116,7 +116,7 @@ func (s *AppsService) RemoveRepository(ctx context.Context, instID, repoID int64
// RevokeInstallationToken revokes an installation token.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#revoke-an-installation-access-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)

View file

@ -31,7 +31,7 @@ type AppConfig struct {
// CompleteAppManifest completes the App manifest handshake flow for the given
// code.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#create-a-github-app-from-a-manifest
// 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)

View file

@ -13,7 +13,7 @@ import (
// MarketplaceService handles communication with the marketplace related
// methods of the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps#marketplace
// 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
@ -21,7 +21,7 @@ type MarketplaceService struct {
// 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/free-pro-team@latest/rest/reference/apps#testing-with-stubbed-endpoints
// GitHub API docs: https://docs.github.com/en/rest/apps#testing-with-stubbed-endpoints
Stubbed bool
}
@ -77,7 +77,7 @@ type MarketplacePlanAccount struct {
// ListPlans lists all plans for your Marketplace listing.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps#list-plans
// 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)
@ -101,7 +101,7 @@ func (s *MarketplaceService) ListPlans(ctx context.Context, opts *ListOptions) (
// ListPlanAccountsForPlan lists all GitHub accounts (user or organization) on a specific plan.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/apps#list-accounts-for-a-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)
@ -125,7 +125,7 @@ func (s *MarketplaceService) ListPlanAccountsForPlan(ctx context.Context, planID
// GetPlanAccountForAccount get GitHub account (user or organization) associated with an account.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/apps#get-a-subscription-plan-for-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))
@ -145,8 +145,8 @@ func (s *MarketplaceService) GetPlanAccountForAccount(ctx context.Context, accou
// ListMarketplacePurchasesForUser lists all GitHub marketplace purchases made by a user.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#list-subscriptions-for-the-authenticated-user-stubbed
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#list-subscriptions-for-the-authenticated-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 {

View file

@ -12,7 +12,7 @@ import (
// Scope models a GitHub authorization scope.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/oauth/#scopes
// GitHub API docs: https://docs.github.com/en/rest/oauth/#scopes
type Scope string
// This is the set of scopes for GitHub API V3
@ -50,7 +50,7 @@ const (
// This service requires HTTP Basic Authentication; it cannot be accessed using
// an OAuth token.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/oauth_authorizations/
// GitHub API docs: https://docs.github.com/en/rest/oauth-authorizations
type AuthorizationsService service
// Authorization represents an individual GitHub authorization.
@ -121,7 +121,7 @@ func (a AuthorizationRequest) String() string {
// fields. That is, you may provide only one of "Scopes", or "AddScopes", or
// "RemoveScopes".
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/oauth_authorizations/#update-an-existing-authorization
// 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"`
@ -143,7 +143,7 @@ func (a AuthorizationUpdateRequest) String() string {
//
// The returned Authorization.User field will be populated.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#check-a-token
// 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)
@ -176,7 +176,7 @@ func (s *AuthorizationsService) Check(ctx context.Context, clientID, accessToken
//
// The returned Authorization.User field will be populated.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#reset-a-token
// 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)
@ -205,7 +205,7 @@ func (s *AuthorizationsService) Reset(ctx context.Context, clientID, accessToken
// 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/free-pro-team@latest/rest/reference/apps/#delete-an-app-token
// 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)
@ -226,7 +226,7 @@ func (s *AuthorizationsService) Revoke(ctx context.Context, clientID, accessToke
// grant will also delete all OAuth tokens associated with the application for
// the user.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/apps/#delete-an-app-authorization
// 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)

View file

@ -13,7 +13,7 @@ import (
// BillingService provides access to the billing related functions
// in the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/billing
// GitHub API docs: https://docs.github.com/en/rest/billing
type BillingService service
// ActionBilling represents a GitHub Action billing.
@ -65,7 +65,7 @@ type AdvancedSecurityCommittersBreakdown struct {
// 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/reference/billing#get-github-actions-billing-for-an-organization
// 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)
@ -79,12 +79,12 @@ func (s *BillingService) GetActionsBillingOrg(ctx context.Context, org string) (
return nil, resp, err
}
return actionsOrgBilling, 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/reference/billing#get-github-packages-billing-for-an-organization
// 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)
@ -98,13 +98,13 @@ func (s *BillingService) GetPackagesBillingOrg(ctx context.Context, org string)
return nil, resp, err
}
return packagesOrgBilling, 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/reference/billing#get-shared-storage-billing-for-an-organization
// 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)
@ -118,12 +118,12 @@ func (s *BillingService) GetStorageBillingOrg(ctx context.Context, org string) (
return nil, resp, err
}
return storageOrgBilling, 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/reference/billing#get-github-advanced-security-active-committers-for-an-organization
// 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)
@ -137,12 +137,12 @@ func (s *BillingService) GetAdvancedSecurityActiveCommittersOrg(ctx context.Cont
return nil, resp, err
}
return activeOrgCommitters, 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/reference/billing#get-github-actions-billing-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)
@ -156,12 +156,12 @@ func (s *BillingService) GetActionsBillingUser(ctx context.Context, user string)
return nil, resp, err
}
return actionsUserBilling, 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/reference/billing#get-github-packages-billing-for-an-organization
// 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)
@ -175,13 +175,13 @@ func (s *BillingService) GetPackagesBillingUser(ctx context.Context, user string
return nil, resp, err
}
return packagesUserBilling, 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/reference/billing#get-shared-storage-billing-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)
@ -195,5 +195,5 @@ func (s *BillingService) GetStorageBillingUser(ctx context.Context, user string)
return nil, resp, err
}
return storageUserBilling, resp, err
return storageUserBilling, resp, nil
}

View file

@ -13,7 +13,7 @@ import (
// ChecksService provides access to the Checks API in the
// GitHub API.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/checks/
// 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.
@ -98,7 +98,7 @@ func (c CheckSuite) String() string {
// GetCheckRun gets a check-run for a repository.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/checks/#get-a-check-run
// 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)
@ -119,7 +119,7 @@ func (s *ChecksService) GetCheckRun(ctx context.Context, owner, repo string, che
// GetCheckSuite gets a single check suite.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/checks/#get-a-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)
@ -161,7 +161,7 @@ type CheckRunAction struct {
// CreateCheckRun creates a check run for repository.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/checks/#create-a-check-run
// 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)
@ -194,7 +194,7 @@ type UpdateCheckRunOptions struct {
// UpdateCheckRun updates a check run for a specific commit in a repository.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/checks/#update-a-check-run
// 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)
@ -215,7 +215,7 @@ func (s *ChecksService) UpdateCheckRun(ctx context.Context, owner, repo string,
// ListCheckRunAnnotations lists the annotations for a check run.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/checks/#list-check-run-annotations
// 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)
@ -257,7 +257,7 @@ type ListCheckRunsResults struct {
// ListCheckRunsForRef lists check runs for a specific ref.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/checks/#list-check-runs-for-a-git-reference
// 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)
@ -283,7 +283,7 @@ func (s *ChecksService) ListCheckRunsForRef(ctx context.Context, owner, repo, re
// ListCheckRunsCheckSuite lists check runs for a check suite.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/checks/#list-check-runs-in-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)
@ -307,6 +307,22 @@ func (s *ChecksService) ListCheckRunsCheckSuite(ctx context.Context, owner, repo
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.
@ -323,7 +339,7 @@ type ListCheckSuiteResults struct {
// ListCheckSuitesForRef lists check suite for a specific ref.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/checks/#list-check-suites-for-a-git-reference
// 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)
@ -371,7 +387,7 @@ type PreferenceList struct {
// SetCheckSuitePreferences changes the default automatic flow when creating check suites.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/checks/#update-repository-preferences-for-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)
@ -398,7 +414,7 @@ type CreateCheckSuiteOptions struct {
// CreateCheckSuite manually creates a check suite for a repository.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/checks/#create-a-check-suite
// 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)
@ -419,7 +435,7 @@ func (s *ChecksService) CreateCheckSuite(ctx context.Context, owner, repo string
// ReRequestCheckSuite triggers GitHub to rerequest an existing check suite, without pushing new code to a repository.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/checks/#rerequest-a-check-suite
// 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)

View file

@ -15,7 +15,7 @@ import (
// CodeScanningService handles communication with the code scanning related
// methods of the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/code-scanning/
// 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.
@ -65,24 +65,29 @@ type Tool struct {
// Alert represents an individual GitHub Code Scanning Alert on a single repository.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/code-scanning#list-code-scanning-alerts-for-a-repository
// GitHub API docs: https://docs.github.com/en/rest/code-scanning
type Alert struct {
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"`
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"`
DismissedBy *User `json:"dismissed_by,omitempty"`
DismissedAt *Timestamp `json:"dismissed_at,omitempty"`
DismissedReason *string `json:"dismissed_reason,omitempty"`
InstancesURL *string `json:"instances_url,omitempty"`
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"`
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.
@ -132,7 +137,7 @@ type AnalysesListOptions struct {
// ScanningAnalysis represents an individual GitHub Code Scanning ScanningAnalysis on a single repository.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/code-scanning#list-code-scanning-analyses-for-a-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"`
@ -153,7 +158,7 @@ type ScanningAnalysis struct {
// SarifAnalysis specifies the results of a code scanning job.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/code-scanning#upload-an-analysis-as-sarif-data
// 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"`
@ -165,19 +170,46 @@ type SarifAnalysis struct {
// SarifID identifies a sarif analysis upload.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/code-scanning#upload-an-analysis-as-sarif-data
// 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/free-pro-team@latest/rest/reference/code-scanning/#list-code-scanning-alerts-for-a-repository
// 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)
@ -206,7 +238,7 @@ func (s *CodeScanningService) ListAlertsForRepo(ctx context.Context, owner, repo
//
// The security alert_id is the number at the end of the security alert's URL.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/code-scanning/#get-a-code-scanning-alert
// 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)
@ -230,7 +262,7 @@ func (s *CodeScanningService) GetAlert(ctx context.Context, owner, repo 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/reference/code-scanning#upload-an-analysis-as-sarif-data
// 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)
@ -254,7 +286,7 @@ func (s *CodeScanningService) UploadSarif(ctx context.Context, owner, repo strin
// 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/reference/code-scanning#list-code-scanning-analyses-for-a-repository
// 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)
@ -283,7 +315,7 @@ func (s *CodeScanningService) ListAnalysesForRepo(ctx context.Context, owner, re
//
// 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/reference/code-scanning#get-a-code-scanning-analysis-for-a-repository
// 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)

View file

@ -8,5 +8,5 @@ package github
// DependabotService handles communication with the Dependabot related
// methods of the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/dependabot/
// GitHub API docs: https://docs.github.com/en/rest/dependabot/
type DependabotService service

View file

@ -27,7 +27,7 @@ func (s *DependabotService) getPublicKey(ctx context.Context, url string) (*Publ
// GetRepoPublicKey gets a public key that should be used for Dependabot secret encryption.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/dependabot#get-a-repository-public-key
// GitHub API docs: https://docs.github.com/en/rest/dependabot/secrets#get-a-repository-public-key
func (s *DependabotService) GetRepoPublicKey(ctx context.Context, owner, repo string) (*PublicKey, *Response, error) {
url := fmt.Sprintf("repos/%v/%v/dependabot/secrets/public-key", owner, repo)
return s.getPublicKey(ctx, url)
@ -35,7 +35,7 @@ func (s *DependabotService) GetRepoPublicKey(ctx context.Context, owner, repo st
// GetOrgPublicKey gets a public key that should be used for Dependabot secret encryption.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/dependabot#get-an-organization-public-key
// GitHub API docs: https://docs.github.com/en/rest/dependabot/secrets#get-an-organization-public-key
func (s *DependabotService) GetOrgPublicKey(ctx context.Context, org string) (*PublicKey, *Response, error) {
url := fmt.Sprintf("orgs/%v/dependabot/secrets/public-key", org)
return s.getPublicKey(ctx, url)
@ -64,7 +64,7 @@ func (s *DependabotService) listSecrets(ctx context.Context, url string, opts *L
// ListRepoSecrets lists all Dependabot secrets available in a repository
// without revealing their encrypted values.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/dependabot#list-repository-secrets
// GitHub API docs: https://docs.github.com/en/rest/dependabot/secrets#list-repository-secrets
func (s *DependabotService) ListRepoSecrets(ctx context.Context, owner, repo string, opts *ListOptions) (*Secrets, *Response, error) {
url := fmt.Sprintf("repos/%v/%v/dependabot/secrets", owner, repo)
return s.listSecrets(ctx, url, opts)
@ -73,7 +73,7 @@ func (s *DependabotService) ListRepoSecrets(ctx context.Context, owner, repo str
// ListOrgSecrets lists all Dependabot secrets available in an organization
// without revealing their encrypted values.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/dependabot#list-organization-secrets
// GitHub API docs: https://docs.github.com/en/rest/dependabot/secrets#list-organization-secrets
func (s *DependabotService) ListOrgSecrets(ctx context.Context, org string, opts *ListOptions) (*Secrets, *Response, error) {
url := fmt.Sprintf("orgs/%v/dependabot/secrets", org)
return s.listSecrets(ctx, url, opts)
@ -96,7 +96,7 @@ func (s *DependabotService) getSecret(ctx context.Context, url string) (*Secret,
// GetRepoSecret gets a single repository Dependabot secret without revealing its encrypted value.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/dependabot#get-a-repository-secret
// GitHub API docs: https://docs.github.com/en/rest/dependabot/secrets#get-a-repository-secret
func (s *DependabotService) GetRepoSecret(ctx context.Context, owner, repo, name string) (*Secret, *Response, error) {
url := fmt.Sprintf("repos/%v/%v/dependabot/secrets/%v", owner, repo, name)
return s.getSecret(ctx, url)
@ -104,13 +104,26 @@ func (s *DependabotService) GetRepoSecret(ctx context.Context, owner, repo, name
// GetOrgSecret gets a single organization Dependabot secret without revealing its encrypted value.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/dependabot#get-an-organization-secret
// GitHub API docs: https://docs.github.com/en/rest/dependabot/secrets#get-an-organization-secret
func (s *DependabotService) GetOrgSecret(ctx context.Context, org, name string) (*Secret, *Response, error) {
url := fmt.Sprintf("orgs/%v/dependabot/secrets/%v", org, name)
return s.getSecret(ctx, url)
}
func (s *DependabotService) putSecret(ctx context.Context, url string, eSecret *EncryptedSecret) (*Response, error) {
// DependabotEncryptedSecret represents a secret that is encrypted using a public key for Dependabot.
//
// 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 DependabotEncryptedSecret struct {
Name string `json:"-"`
KeyID string `json:"key_id"`
EncryptedValue string `json:"encrypted_value"`
Visibility string `json:"visibility,omitempty"`
SelectedRepositoryIDs DependabotSecretsSelectedRepoIDs `json:"selected_repository_ids,omitempty"`
}
func (s *DependabotService) putSecret(ctx context.Context, url string, eSecret *DependabotEncryptedSecret) (*Response, error) {
req, err := s.client.NewRequest("PUT", url, eSecret)
if err != nil {
return nil, err
@ -121,16 +134,16 @@ func (s *DependabotService) putSecret(ctx context.Context, url string, eSecret *
// CreateOrUpdateRepoSecret creates or updates a repository Dependabot secret with an encrypted value.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/dependabot#create-or-update-a-repository-secret
func (s *DependabotService) CreateOrUpdateRepoSecret(ctx context.Context, owner, repo string, eSecret *EncryptedSecret) (*Response, error) {
// GitHub API docs: https://docs.github.com/en/rest/dependabot/secrets#create-or-update-a-repository-secret
func (s *DependabotService) CreateOrUpdateRepoSecret(ctx context.Context, owner, repo string, eSecret *DependabotEncryptedSecret) (*Response, error) {
url := fmt.Sprintf("repos/%v/%v/dependabot/secrets/%v", owner, repo, eSecret.Name)
return s.putSecret(ctx, url, eSecret)
}
// CreateOrUpdateOrgSecret creates or updates an organization Dependabot secret with an encrypted value.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/dependabot#create-or-update-an-organization-secret
func (s *DependabotService) CreateOrUpdateOrgSecret(ctx context.Context, org string, eSecret *EncryptedSecret) (*Response, error) {
// GitHub API docs: https://docs.github.com/en/rest/dependabot/secrets#create-or-update-an-organization-secret
func (s *DependabotService) CreateOrUpdateOrgSecret(ctx context.Context, org string, eSecret *DependabotEncryptedSecret) (*Response, error) {
url := fmt.Sprintf("orgs/%v/dependabot/secrets/%v", org, eSecret.Name)
return s.putSecret(ctx, url, eSecret)
}
@ -146,7 +159,7 @@ func (s *DependabotService) deleteSecret(ctx context.Context, url string) (*Resp
// DeleteRepoSecret deletes a Dependabot secret in a repository using the secret name.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/dependabot#delete-a-repository-secret
// GitHub API docs: https://docs.github.com/en/rest/dependabot/secrets#delete-a-repository-secret
func (s *DependabotService) DeleteRepoSecret(ctx context.Context, owner, repo, name string) (*Response, error) {
url := fmt.Sprintf("repos/%v/%v/dependabot/secrets/%v", owner, repo, name)
return s.deleteSecret(ctx, url)
@ -154,7 +167,7 @@ func (s *DependabotService) DeleteRepoSecret(ctx context.Context, owner, repo, n
// DeleteOrgSecret deletes a Dependabot secret in an organization using the secret name.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/dependabot#delete-an-organization-secret
// GitHub API docs: https://docs.github.com/en/rest/dependabot/secrets#delete-an-organization-secret
func (s *DependabotService) DeleteOrgSecret(ctx context.Context, org, name string) (*Response, error) {
url := fmt.Sprintf("orgs/%v/dependabot/secrets/%v", org, name)
return s.deleteSecret(ctx, url)
@ -162,7 +175,7 @@ func (s *DependabotService) DeleteOrgSecret(ctx context.Context, org, name strin
// ListSelectedReposForOrgSecret lists all repositories that have access to a Dependabot secret.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/dependabot#list-selected-repositories-for-an-organization-secret
// GitHub API docs: https://docs.github.com/en/rest/dependabot/secrets#list-selected-repositories-for-an-organization-secret
func (s *DependabotService) ListSelectedReposForOrgSecret(ctx context.Context, org, name string, opts *ListOptions) (*SelectedReposList, *Response, error) {
url := fmt.Sprintf("orgs/%v/dependabot/secrets/%v/repositories", org, name)
u, err := addOptions(url, opts)
@ -184,13 +197,16 @@ func (s *DependabotService) ListSelectedReposForOrgSecret(ctx context.Context, o
return result, resp, nil
}
// DependabotSecretsSelectedRepoIDs are the repository IDs that have access to the dependabot secrets.
type DependabotSecretsSelectedRepoIDs []string
// SetSelectedReposForOrgSecret sets the repositories that have access to a Dependabot secret.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/dependabot#set-selected-repositories-for-an-organization-secret
func (s *DependabotService) SetSelectedReposForOrgSecret(ctx context.Context, org, name string, ids SelectedRepoIDs) (*Response, error) {
// GitHub API docs: https://docs.github.com/en/rest/dependabot/secrets#set-selected-repositories-for-an-organization-secret
func (s *DependabotService) SetSelectedReposForOrgSecret(ctx context.Context, org, name string, ids DependabotSecretsSelectedRepoIDs) (*Response, error) {
url := fmt.Sprintf("orgs/%v/dependabot/secrets/%v/repositories", org, name)
type repoIDs struct {
SelectedIDs SelectedRepoIDs `json:"selected_repository_ids"`
SelectedIDs DependabotSecretsSelectedRepoIDs `json:"selected_repository_ids"`
}
req, err := s.client.NewRequest("PUT", url, repoIDs{SelectedIDs: ids})
@ -203,7 +219,7 @@ func (s *DependabotService) SetSelectedReposForOrgSecret(ctx context.Context, or
// AddSelectedRepoToOrgSecret adds a repository to an organization Dependabot secret.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/dependabot#add-selected-repository-to-an-organization-secret
// GitHub API docs: https://docs.github.com/en/rest/dependabot/secrets#add-selected-repository-to-an-organization-secret
func (s *DependabotService) AddSelectedRepoToOrgSecret(ctx context.Context, org, name string, repo *Repository) (*Response, error) {
url := fmt.Sprintf("orgs/%v/dependabot/secrets/%v/repositories/%v", org, name, *repo.ID)
req, err := s.client.NewRequest("PUT", url, nil)
@ -216,7 +232,7 @@ func (s *DependabotService) AddSelectedRepoToOrgSecret(ctx context.Context, org,
// RemoveSelectedRepoFromOrgSecret removes a repository from an organization Dependabot secret.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/dependabot#remove-selected-repository-from-an-organization-secret
// GitHub API docs: https://docs.github.com/en/rest/dependabot/secrets#remove-selected-repository-from-an-organization-secret
func (s *DependabotService) RemoveSelectedRepoFromOrgSecret(ctx context.Context, org, name string, repo *Repository) (*Response, error) {
url := fmt.Sprintf("orgs/%v/dependabot/secrets/%v/repositories/%v", org, name, *repo.ID)
req, err := s.client.NewRequest("DELETE", url, nil)

View file

@ -8,7 +8,7 @@ Package github provides a client for using the GitHub API.
Usage:
import "github.com/google/go-github/v43/github" // with go modules enabled (GO111MODULE=on or outside GOPATH)
import "github.com/google/go-github/v48/github" // with go modules enabled (GO111MODULE=on or outside GOPATH)
import "github.com/google/go-github/github" // with go modules disabled
Construct a new GitHub client, then use the various services on the client to
@ -29,7 +29,7 @@ Some API methods have optional parameters that can be passed. For example:
The services of a client divide the API into logical chunks and correspond to
the structure of the GitHub API documentation at
https://docs.github.com/en/free-pro-team@latest/rest/reference/.
https://docs.github.com/en/rest .
NOTE: Using the https://godoc.org/context package, one can easily
pass cancelation signals and deadlines to various services of the client for
@ -38,7 +38,7 @@ can be used as a starting point.
For more sample code snippets, head over to the https://github.com/google/go-github/tree/master/example directory.
Authentication
# Authentication
The go-github library does not directly handle authentication. Instead, when
creating a new client, pass an http.Client that can handle authentication for
@ -110,7 +110,7 @@ To authenticate as an app, using a JWT:
// Use client...
}
Rate Limiting
# Rate Limiting
GitHub imposes a rate limit on all API clients. Unauthenticated clients are
limited to 60 requests per hour, while authenticated clients can make up to
@ -137,9 +137,9 @@ For secondary rate limits, you can check if its type is *github.AbuseRateLimitEr
}
Learn more about GitHub rate limiting at
https://docs.github.com/en/free-pro-team@latest/rest/overview/resources-in-the-rest-api#rate-limiting.
https://docs.github.com/en/rest/rate-limit .
Accepted Status
# Accepted Status
Some endpoints may return a 202 Accepted status code, meaning that the
information required is not yet ready and was scheduled to be gathered on
@ -154,7 +154,7 @@ To detect this condition of error, you can check if its type is
log.Println("scheduled on GitHub side")
}
Conditional Requests
# Conditional Requests
The GitHub API has good support for conditional requests which will help
prevent you from burning through your rate limit, as well as help speed up your
@ -163,9 +163,9 @@ instead designed to work with a caching http.Transport. We recommend using
https://github.com/gregjones/httpcache for that.
Learn more about GitHub conditional requests at
https://docs.github.com/en/free-pro-team@latest/rest/overview/resources-in-the-rest-api#conditional-requests.
https://docs.github.com/en/rest/overview/resources-in-the-rest-api#conditional-requests.
Creating and Updating Resources
# Creating and Updating Resources
All structs for GitHub resources use pointer values for all non-repeated fields.
This allows distinguishing between unset fields and those set to a zero-value.
@ -181,7 +181,7 @@ bool, and int values. For example:
Users who have worked with protocol buffers should find this pattern familiar.
Pagination
# Pagination
All requests for resource collections (repos, pull requests, issues, etc.)
support pagination. Pagination options are described in the
@ -208,6 +208,5 @@ github.Response struct.
}
opt.Page = resp.NextPage
}
*/
package github

View file

@ -8,5 +8,5 @@ package github
// EnterpriseService provides access to the enterprise related functions
// in the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/enterprise-admin/
// GitHub API docs: https://docs.github.com/en/rest/enterprise-admin/
type EnterpriseService service

View file

@ -10,9 +10,28 @@ import (
"fmt"
)
// 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-an-enterprise
func (s *EnterpriseService) ListRunnerApplicationDownloads(ctx context.Context, enterprise string) ([]*RunnerApplicationDownload, *Response, error) {
u := fmt.Sprintf("enterprises/%v/actions/runners/downloads", enterprise)
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
}
// CreateRegistrationToken creates a token that can be used to add a self-hosted runner.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/enterprise-admin/#create-a-registration-token-for-an-enterprise
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners#create-a-registration-token-for-an-enterprise
func (s *EnterpriseService) CreateRegistrationToken(ctx context.Context, enterprise string) (*RegistrationToken, *Response, error) {
u := fmt.Sprintf("enterprises/%v/actions/runners/registration-token", enterprise)
@ -32,7 +51,7 @@ func (s *EnterpriseService) CreateRegistrationToken(ctx context.Context, enterpr
// ListRunners lists all the self-hosted runners for a enterprise.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/enterprise-admin/#list-self-hosted-runners-for-an-enterprise
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners#list-self-hosted-runners-for-an-enterprise
func (s *EnterpriseService) ListRunners(ctx context.Context, enterprise string, opts *ListOptions) (*Runners, *Response, error) {
u := fmt.Sprintf("enterprises/%v/actions/runners", enterprise)
u, err := addOptions(u, opts)
@ -56,7 +75,7 @@ func (s *EnterpriseService) ListRunners(ctx context.Context, enterprise string,
// RemoveRunner forces the removal of a self-hosted runner from an enterprise using the runner id.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/enterprise-admin/#delete-a-self-hosted-runner-from-an-enterprise
// GitHub API docs: https://docs.github.com/en/rest/actions/self-hosted-runners#delete-a-self-hosted-runner-from-an-enterprise
func (s *EnterpriseService) RemoveRunner(ctx context.Context, enterprise string, runnerID int64) (*Response, error) {
u := fmt.Sprintf("enterprises/%v/actions/runners/%v", enterprise, runnerID)

View file

@ -12,7 +12,7 @@ import (
// GetAuditLog gets the audit-log entries for an organization.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/enterprise-admin#get-the-audit-log-for-an-enterprise
// GitHub API docs: https://docs.github.com/en/rest/enterprise-admin/audit-log#get-the-audit-log-for-an-enterprise
func (s *EnterpriseService) GetAuditLog(ctx context.Context, enterprise string, opts *GetAuditLogOptions) ([]*AuditEntry, *Response, error) {
u := fmt.Sprintf("enterprises/%v/audit-log", enterprise)
u, err := addOptions(u, opts)

View file

@ -36,6 +36,8 @@ func (e *Event) ParsePayload() (payload interface{}, err error) {
payload = &CheckRunEvent{}
case "CheckSuiteEvent":
payload = &CheckSuiteEvent{}
case "CodeScanningAlertEvent":
payload = &CodeScanningAlertEvent{}
case "CommitCommentEvent":
payload = &CommitCommentEvent{}
case "ContentReferenceEvent":
@ -102,6 +104,8 @@ func (e *Event) ParsePayload() (payload interface{}, err error) {
payload = &PullRequestReviewEvent{}
case "PullRequestReviewCommentEvent":
payload = &PullRequestReviewCommentEvent{}
case "PullRequestReviewThreadEvent":
payload = &PullRequestReviewThreadEvent{}
case "PullRequestTargetEvent":
payload = &PullRequestTargetEvent{}
case "PushEvent":
@ -112,6 +116,8 @@ func (e *Event) ParsePayload() (payload interface{}, err error) {
payload = &RepositoryEvent{}
case "RepositoryDispatchEvent":
payload = &RepositoryDispatchEvent{}
case "RepositoryImportEvent":
payload = &RepositoryImportEvent{}
case "RepositoryVulnerabilityAlertEvent":
payload = &RepositoryVulnerabilityAlertEvent{}
case "SecretScanningAlertEvent":

View file

@ -106,10 +106,11 @@ type CreateEvent struct {
RefType *string `json:"ref_type,omitempty"`
MasterBranch *string `json:"master_branch,omitempty"`
Description *string `json:"description,omitempty"`
PusherType *string `json:"pusher_type,omitempty"`
// The following fields are only populated by Webhook events.
PusherType *string `json:"pusher_type,omitempty"`
Repo *Repository `json:"repository,omitempty"`
Org *Organization `json:"organization,omitempty"`
Sender *User `json:"sender,omitempty"`
Installation *Installation `json:"installation,omitempty"`
}
@ -493,13 +494,14 @@ type IssuesEvent struct {
type LabelEvent struct {
// Action is the action that was performed. Possible values are:
// "created", "edited", "deleted"
Action *string `json:"action,omitempty"`
Label *Label `json:"label,omitempty"`
Action *string `json:"action,omitempty"`
Label *Label `json:"label,omitempty"`
Changes *EditChange `json:"changes,omitempty"`
// The following fields are only populated by Webhook events.
Changes *EditChange `json:"changes,omitempty"`
Repo *Repository `json:"repository,omitempty"`
Org *Organization `json:"organization,omitempty"`
Sender *User `json:"sender,omitempty"`
Installation *Installation `json:"installation,omitempty"`
}
@ -574,6 +576,9 @@ type MetaEvent struct {
Hook *Hook `json:"hook,omitempty"`
// The following fields are only populated by Webhook events.
Repo *Repository `json:"repository,omitempty"`
Org *Organization `json:"organization,omitempty"`
Sender *User `json:"sender,omitempty"`
Installation *Installation `json:"installation,omitempty"`
}
@ -682,7 +687,12 @@ type PingEvent struct {
// The ID of the webhook that triggered the ping.
HookID *int64 `json:"hook_id,omitempty"`
// The webhook configuration.
Hook *Hook `json:"hook,omitempty"`
Hook *Hook `json:"hook,omitempty"`
// The following fields are only populated by Webhook events.
Repo *Repository `json:"repository,omitempty"`
Org *Organization `json:"organization,omitempty"`
Sender *User `json:"sender,omitempty"`
Installation *Installation `json:"installation,omitempty"`
}
@ -830,12 +840,30 @@ type PullRequestReviewCommentEvent struct {
Installation *Installation `json:"installation,omitempty"`
}
// PullRequestReviewThreadEvent is triggered when a comment made as part of a
// review of a pull request is marked resolved or unresolved.
// The Webhook event name is "pull_request_review_thread".
//
// GitHub API docs: https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#pull_request_review_thread
type PullRequestReviewThreadEvent struct {
// Action is the action that was performed on the comment.
// Possible values are: "resolved", "unresolved".
Action *string `json:"action,omitempty"`
Thread *PullRequestThread `json:"thread,omitempty"`
PullRequest *PullRequest `json:"pull_request,omitempty"`
// The following fields are only populated by Webhook events.
Repo *Repository `json:"repository,omitempty"`
Sender *User `json:"sender,omitempty"`
Installation *Installation `json:"installation,omitempty"`
}
// PullRequestTargetEvent is triggered when a pull request is assigned, unassigned, labeled,
// unlabeled, opened, edited, closed, reopened, synchronize, ready_for_review,
// locked, unlocked, a pull request review is requested, or a review request is removed.
// The Webhook event name is "pull_request_target".
//
// GitHub API docs: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request_target
// GitHub API docs: https://docs.github.com/en/actions/events-that-trigger-workflows#pull_request_target
type PullRequestTargetEvent struct {
// Action is the action that was performed. Possible values are:
// "assigned", "unassigned", "labeled", "unlabeled", "opened", "edited", "closed", "reopened",
@ -885,6 +913,7 @@ type PushEvent struct {
DistinctSize *int `json:"distinct_size,omitempty"`
// The following fields are only populated by Webhook events.
Action *string `json:"action,omitempty"`
After *string `json:"after,omitempty"`
Created *bool `json:"created,omitempty"`
Deleted *bool `json:"deleted,omitempty"`
@ -926,8 +955,8 @@ type HeadCommit struct {
Modified []string `json:"modified,omitempty"`
}
func (p HeadCommit) String() string {
return Stringify(p)
func (h HeadCommit) String() string {
return Stringify(h)
}
// PushEventRepository represents the repo object in a PushEvent payload.
@ -1032,6 +1061,17 @@ type RepositoryDispatchEvent struct {
Installation *Installation `json:"installation,omitempty"`
}
// RepositoryImportEvent represents the activity related to a repository being imported to GitHub.
//
// GitHub API docs: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#repository_import
type RepositoryImportEvent struct {
// Status represents the final state of the import. This can be one of "success", "cancelled", or "failure".
Status *string `json:"status,omitempty"`
Repo *Repository `json:"repository,omitempty"`
Org *Organization `json:"organization,omitempty"`
Sender *User `json:"sender,omitempty"`
}
// RepositoryVulnerabilityAlertEvent is triggered when a security alert is created, dismissed, or resolved.
//
// GitHub API docs: https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#repository_vulnerability_alert
@ -1039,14 +1079,17 @@ type RepositoryVulnerabilityAlertEvent struct {
// Action is the action that was performed. Possible values are: "create", "dismiss", "resolve".
Action *string `json:"action,omitempty"`
//The security alert of the vulnerable dependency.
// The security alert of the vulnerable dependency.
Alert *RepositoryVulnerabilityAlert `json:"alert,omitempty"`
//The repository of the vulnerable dependency.
// The repository of the vulnerable dependency.
Repository *Repository `json:"repository,omitempty"`
// The following fields are only populated by Webhook events.
Installation *Installation `json:"installation,omitempty"`
// The user that triggered the event.
Sender *User `json:"sender,omitempty"`
}
// RepositoryVulnerabilityAlert represents a repository security alert.
@ -1248,3 +1291,71 @@ type WorkflowRunEvent struct {
Sender *User `json:"sender,omitempty"`
Installation *Installation `json:"installation,omitempty"`
}
// SecurityAdvisory represents the advisory object in SecurityAdvisoryEvent payload.
//
// GitHub API docs: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#security_advisory
type SecurityAdvisory struct {
GHSAID *string `json:"ghsa_id,omitempty"`
Summary *string `json:"summary,omitempty"`
Description *string `json:"description,omitempty"`
Severity *string `json:"severity,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"`
Vulnerabilities []*AdvisoryVulnerability `json:"vulnerabilities,omitempty"`
}
// AdvisoryIdentifier represents the identifier for a Security Advisory.
type AdvisoryIdentifier struct {
Value *string `json:"value,omitempty"`
Type *string `json:"type,omitempty"`
}
// AdvisoryReference represents the reference url for the security advisory.
type AdvisoryReference struct {
URL *string `json:"url,omitempty"`
}
// AdvisoryVulnerability represents the vulnerability object for a Security Advisory.
type AdvisoryVulnerability struct {
Package *VulnerabilityPackage `json:"package,omitempty"`
Severity *string `json:"severity,omitempty"`
VulnerableVersionRange *string `json:"vulnerable_version_range,omitempty"`
FirstPatchedVersion *FirstPatchedVersion `json:"first_patched_version,omitempty"`
}
// VulnerabilityPackage represents the package object for an Advisory Vulnerability.
type VulnerabilityPackage struct {
Ecosystem *string `json:"ecosystem,omitempty"`
Name *string `json:"name,omitempty"`
}
// FirstPatchedVersion represents the identifier for the first patched version of that vulnerability.
type FirstPatchedVersion struct {
Identifier *string `json:"identifier,omitempty"`
}
// SecurityAdvisoryEvent is triggered when a security-related vulnerability is found in software on GitHub.
//
// GitHub API docs: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#security_advisory
type SecurityAdvisoryEvent struct {
Action *string `json:"action,omitempty"`
SecurityAdvisory *SecurityAdvisory `json:"security_advisory,omitempty"`
}
// CodeScanningAlertEvent is triggered when a code scanning finds a potential vulnerability or error in your code.
//
// GitHub API docs: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#code_scanning_alert
type CodeScanningAlertEvent struct {
Action *string `json:"action,omitempty"`
Alert *Alert `json:"alert,omitempty"`
Ref *string `json:"ref,omitempty"`
// CommitOID is the commit SHA of the code scanning alert
CommitOID *string `json:"commit_oid,omitempty"`
Repo *Repository `json:"repository,omitempty"`
Org *Organization `json:"organization,omitempty"`
Sender *User `json:"sender,omitempty"`
}

View file

@ -14,7 +14,7 @@ import (
// GistsService handles communication with the Gist related
// methods of the GitHub API.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/
// GitHub API docs: https://docs.github.com/en/rest/gists
type GistsService service
// Gist represents a GitHub's gist.
@ -96,8 +96,8 @@ type GistListOptions struct {
// is authenticated, it will returns all gists for the authenticated
// user.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/#list-gists-for-the-authenticated-user
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/#list-gists-for-a-user
// GitHub API docs: https://docs.github.com/en/rest/gists/gists#list-gists-for-the-authenticated-user
// GitHub API docs: https://docs.github.com/en/rest/gists/gists#list-gists-for-a-user
func (s *GistsService) List(ctx context.Context, user string, opts *GistListOptions) ([]*Gist, *Response, error) {
var u string
if user != "" {
@ -126,7 +126,7 @@ func (s *GistsService) List(ctx context.Context, user string, opts *GistListOpti
// ListAll lists all public gists.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/#list-public-gists
// GitHub API docs: https://docs.github.com/en/rest/gists/gists#list-public-gists
func (s *GistsService) ListAll(ctx context.Context, opts *GistListOptions) ([]*Gist, *Response, error) {
u, err := addOptions("gists/public", opts)
if err != nil {
@ -149,7 +149,7 @@ func (s *GistsService) ListAll(ctx context.Context, opts *GistListOptions) ([]*G
// ListStarred lists starred gists of authenticated user.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/#list-starred-gists
// GitHub API docs: https://docs.github.com/en/rest/gists/gists#list-starred-gists
func (s *GistsService) ListStarred(ctx context.Context, opts *GistListOptions) ([]*Gist, *Response, error) {
u, err := addOptions("gists/starred", opts)
if err != nil {
@ -172,7 +172,7 @@ func (s *GistsService) ListStarred(ctx context.Context, opts *GistListOptions) (
// Get a single gist.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/#get-a-gist
// GitHub API docs: https://docs.github.com/en/rest/gists/gists#get-a-gist
func (s *GistsService) Get(ctx context.Context, id string) (*Gist, *Response, error) {
u := fmt.Sprintf("gists/%v", id)
req, err := s.client.NewRequest("GET", u, nil)
@ -191,7 +191,7 @@ func (s *GistsService) Get(ctx context.Context, id string) (*Gist, *Response, er
// GetRevision gets a specific revision of a gist.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/#get-a-gist-revision
// GitHub API docs: https://docs.github.com/en/rest/gists/gists#get-a-gist-revision
func (s *GistsService) GetRevision(ctx context.Context, id, sha string) (*Gist, *Response, error) {
u := fmt.Sprintf("gists/%v/%v", id, sha)
req, err := s.client.NewRequest("GET", u, nil)
@ -210,7 +210,7 @@ func (s *GistsService) GetRevision(ctx context.Context, id, sha string) (*Gist,
// Create a gist for authenticated user.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/#create-a-gist
// GitHub API docs: https://docs.github.com/en/rest/gists/gists#create-a-gist
func (s *GistsService) Create(ctx context.Context, gist *Gist) (*Gist, *Response, error) {
u := "gists"
req, err := s.client.NewRequest("POST", u, gist)
@ -229,7 +229,7 @@ func (s *GistsService) Create(ctx context.Context, gist *Gist) (*Gist, *Response
// Edit a gist.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/#update-a-gist
// GitHub API docs: https://docs.github.com/en/rest/gists/gists#update-a-gist
func (s *GistsService) Edit(ctx context.Context, id string, gist *Gist) (*Gist, *Response, error) {
u := fmt.Sprintf("gists/%v", id)
req, err := s.client.NewRequest("PATCH", u, gist)
@ -248,7 +248,7 @@ func (s *GistsService) Edit(ctx context.Context, id string, gist *Gist) (*Gist,
// ListCommits lists commits of a gist.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/#list-gist-commits
// GitHub API docs: https://docs.github.com/en/rest/gists/gists#list-gist-commits
func (s *GistsService) ListCommits(ctx context.Context, id string, opts *ListOptions) ([]*GistCommit, *Response, error) {
u := fmt.Sprintf("gists/%v/commits", id)
u, err := addOptions(u, opts)
@ -272,49 +272,53 @@ func (s *GistsService) ListCommits(ctx context.Context, id string, opts *ListOpt
// Delete a gist.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/#delete-a-gist
// GitHub API docs: https://docs.github.com/en/rest/gists/gists#delete-a-gist
func (s *GistsService) Delete(ctx context.Context, id string) (*Response, error) {
u := fmt.Sprintf("gists/%v", id)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// Star a gist on behalf of authenticated user.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/#star-a-gist
// GitHub API docs: https://docs.github.com/en/rest/gists/gists#star-a-gist
func (s *GistsService) Star(ctx context.Context, id string) (*Response, error) {
u := fmt.Sprintf("gists/%v/star", id)
req, err := s.client.NewRequest("PUT", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// Unstar a gist on a behalf of authenticated user.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/#unstar-a-gist
// GitHub API docs: https://docs.github.com/en/rest/gists/gists#unstar-a-gist
func (s *GistsService) Unstar(ctx context.Context, id string) (*Response, error) {
u := fmt.Sprintf("gists/%v/star", id)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(ctx, req, nil)
}
// IsStarred checks if a gist is starred by authenticated user.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/#check-if-a-gist-is-starred
// GitHub API docs: https://docs.github.com/en/rest/gists/gists#check-if-a-gist-is-starred
func (s *GistsService) IsStarred(ctx context.Context, id string) (bool, *Response, error) {
u := fmt.Sprintf("gists/%v/star", id)
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
@ -322,7 +326,7 @@ func (s *GistsService) IsStarred(ctx context.Context, id string) (bool, *Respons
// Fork a gist.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/#fork-a-gist
// GitHub API docs: https://docs.github.com/en/rest/gists/gists#fork-a-gist
func (s *GistsService) Fork(ctx context.Context, id string) (*Gist, *Response, error) {
u := fmt.Sprintf("gists/%v/forks", id)
req, err := s.client.NewRequest("POST", u, nil)
@ -341,7 +345,7 @@ func (s *GistsService) Fork(ctx context.Context, id string) (*Gist, *Response, e
// ListForks lists forks of a gist.
//
// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/gists/#list-gist-forks
// GitHub API docs: https://docs.github.com/en/rest/gists/gists#list-gist-forks
func (s *GistsService) ListForks(ctx context.Context, id string, opts *ListOptions) ([]*GistFork, *Response, error) {
u := fmt.Sprintf("gists/%v/forks", id)
u, err := addOptions(u, opts)

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